/* * Copyright (C) 2026 Fluxer Contributors * * This file is part of Fluxer. * * Fluxer is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Fluxer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Fluxer. If not, see . */ import type {ChannelID, GuildID, InviteCode, UserID} from '@fluxer/api/src/BrandedTypes'; import {createInviteCode} from '@fluxer/api/src/BrandedTypes'; import {BatchBuilder, Db, fetchMany, fetchOne, upsertOne} from '@fluxer/api/src/database/Cassandra'; import type {InviteRow} from '@fluxer/api/src/database/types/ChannelTypes'; import {IInviteRepository} from '@fluxer/api/src/invite/IInviteRepository'; import {Invite} from '@fluxer/api/src/models/Invite'; import {Invites, InvitesByChannel, InvitesByGuild} from '@fluxer/api/src/Tables'; const FETCH_INVITE_BY_CODE_CQL = Invites.selectCql({ where: Invites.where.eq('code'), limit: 1, }); const FETCH_INVITES_BY_CHANNEL_CQL = InvitesByChannel.selectCql({ columns: ['code'], where: InvitesByChannel.where.eq('channel_id'), }); const FETCH_INVITES_BY_GUILD_CQL = InvitesByGuild.selectCql({ columns: ['code'], where: InvitesByGuild.where.eq('guild_id'), }); interface CreateInviteParams { code: InviteCode; type: number; guild_id: GuildID; channel_id?: ChannelID | null; inviter_id?: UserID | null; uses: number; max_uses: number; max_age: number; temporary?: boolean; } export class InviteRepository extends IInviteRepository { async findUnique(code: InviteCode): Promise { const invite = await fetchOne(FETCH_INVITE_BY_CODE_CQL, {code}); return invite ? new Invite(invite) : null; } async listChannelInvites(channelId: ChannelID): Promise> { const inviteCodes = await fetchMany<{code: string}>(FETCH_INVITES_BY_CHANNEL_CQL, {channel_id: channelId}); if (inviteCodes.length === 0) return []; const invites: Array = []; for (const {code} of inviteCodes) { const invite = await this.findUnique(createInviteCode(code)); if (invite) invites.push(invite); } return invites; } async listGuildInvites(guildId: GuildID): Promise> { const inviteCodes = await fetchMany<{code: string}>(FETCH_INVITES_BY_GUILD_CQL, {guild_id: guildId}); if (inviteCodes.length === 0) return []; const invites: Array = []; for (const {code} of inviteCodes) { const invite = await this.findUnique(createInviteCode(code)); if (invite) invites.push(invite); } return invites; } async create(data: CreateInviteParams): Promise { const inviteRow: InviteRow = { code: data.code, type: data.type, guild_id: data.guild_id, channel_id: data.channel_id ?? null, inviter_id: data.inviter_id ?? null, created_at: new Date(), uses: data.uses, max_uses: data.max_uses, max_age: data.max_age, temporary: data.temporary ?? false, version: 1, }; const batch = new BatchBuilder(); const hasExpiry = inviteRow.max_age > 0; if (hasExpiry) { batch.addPrepared(Invites.insertWithTtlParam(inviteRow, 'max_age')); } else { batch.addPrepared(Invites.insert(inviteRow)); } if (inviteRow.guild_id) { batch.addPrepared( hasExpiry ? InvitesByGuild.insertWithTtl( { guild_id: inviteRow.guild_id, code: inviteRow.code, }, inviteRow.max_age, ) : InvitesByGuild.insert({ guild_id: inviteRow.guild_id, code: inviteRow.code, }), ); } if (inviteRow.channel_id) { batch.addPrepared( hasExpiry ? InvitesByChannel.insertWithTtl( { channel_id: inviteRow.channel_id, code: inviteRow.code, }, inviteRow.max_age, ) : InvitesByChannel.insert({ channel_id: inviteRow.channel_id, code: inviteRow.code, }), ); } await batch.execute(); await upsertOne(Invites.upsertAll(inviteRow)); if (inviteRow.guild_id) { await upsertOne( InvitesByGuild.upsertAll({ guild_id: inviteRow.guild_id, code: inviteRow.code, }), ); } if (inviteRow.channel_id) { await upsertOne( InvitesByChannel.upsertAll({ channel_id: inviteRow.channel_id, code: inviteRow.code, }), ); } return new Invite(inviteRow); } async updateInviteUses(code: InviteCode, uses: number): Promise { await fetchOne( Invites.patchByPk( {code}, { uses: Db.set(uses), }, ), ); } async delete(code: InviteCode): Promise { const invite = await this.findUnique(code); if (!invite) { return; } const batch = new BatchBuilder(); batch.addPrepared(Invites.deleteByPk({code})); if (invite.guildId) { batch.addPrepared(InvitesByGuild.deleteByPk({guild_id: invite.guildId, code})); } if (invite.channelId) { batch.addPrepared(InvitesByChannel.deleteByPk({channel_id: invite.channelId, code})); } await batch.execute(); } }