/* * 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 {createGuildIDSet, createUserIDSet} from '@fluxer/api/src/BrandedTypes'; import { BatchBuilder, defineTable, deleteOneOrMany, fetchMany, fetchOne, upsertOne, } from '@fluxer/api/src/database/Cassandra'; import { VOICE_REGION_COLUMNS, VOICE_SERVER_COLUMNS, type VoiceRegionRow, type VoiceServerRow, } from '@fluxer/api/src/database/types/VoiceTypes'; import type {IVoiceRepository} from '@fluxer/api/src/voice/IVoiceRepository'; import type {VoiceRegionRecord, VoiceRegionWithServers, VoiceServerRecord} from '@fluxer/api/src/voice/VoiceModel'; function toIterable(value: unknown): Array { if (value === null || value === undefined) return []; if (value instanceof Set) return Array.from(value) as Array; if (Array.isArray(value)) return value as Array; return [value as T]; } const VoiceRegions = defineTable({ name: 'voice_regions', columns: VOICE_REGION_COLUMNS, primaryKey: ['id'], }); const VoiceServers = defineTable({ name: 'voice_servers', columns: VOICE_SERVER_COLUMNS, primaryKey: ['region_id', 'server_id'], }); const LIST_REGIONS_CQL = VoiceRegions.selectCql(); const GET_REGION_CQL = VoiceRegions.selectCql({ where: VoiceRegions.where.eq('id'), }); const LIST_SERVERS_FOR_REGION_CQL = VoiceServers.selectCql({ where: VoiceServers.where.eq('region_id'), }); const GET_SERVER_CQL = VoiceServers.selectCql({ where: [VoiceServers.where.eq('region_id'), VoiceServers.where.eq('server_id')], }); export class VoiceRepository implements IVoiceRepository { async listRegions(): Promise> { const rows = await fetchMany(LIST_REGIONS_CQL, {}); return rows.map((row) => this.mapRegionRow(row)); } async listRegionsWithServers(): Promise> { const regions = await this.listRegions(); const results: Array = []; for (const region of regions) { const servers = await this.listServersForRegion(region.id); results.push({ ...region, servers, }); } return results; } async getRegion(id: string): Promise { const row = await fetchOne(GET_REGION_CQL, {id}); return row ? this.mapRegionRow(row) : null; } async getRegionWithServers(id: string): Promise { const region = await this.getRegion(id); if (!region) { return null; } const servers = await this.listServersForRegion(id); return {...region, servers}; } async upsertRegion(region: VoiceRegionRecord): Promise { const row: VoiceRegionRow = { id: region.id, name: region.name, emoji: region.emoji, latitude: region.latitude, longitude: region.longitude, is_default: region.isDefault, vip_only: region.restrictions.vipOnly, required_guild_features: new Set(region.restrictions.requiredGuildFeatures), allowed_guild_ids: new Set(Array.from(region.restrictions.allowedGuildIds).map((id) => BigInt(id))), allowed_user_ids: new Set(Array.from(region.restrictions.allowedUserIds).map((id) => BigInt(id))), created_at: region.createdAt ?? new Date(), updated_at: region.updatedAt ?? new Date(), }; await upsertOne(VoiceRegions.upsertAll(row)); } async deleteRegion(regionId: string): Promise { const batch = new BatchBuilder(); batch.addPrepared(VoiceRegions.deleteByPk({id: regionId})); const servers = await this.listServersForRegion(regionId); for (const server of servers) { batch.addPrepared(VoiceServers.deleteByPk({region_id: regionId, server_id: server.serverId})); } await batch.execute(); } async createRegion(region: Omit): Promise { const now = new Date(); const fullRegion: VoiceRegionRecord = { ...region, createdAt: now, updatedAt: now, }; await this.upsertRegion(fullRegion); return fullRegion; } async listServersForRegion(regionId: string): Promise> { const rows = await fetchMany(LIST_SERVERS_FOR_REGION_CQL, {region_id: regionId}); return rows.map((row) => this.mapServerRow(row)); } async listServers(regionId: string): Promise> { return this.listServersForRegion(regionId); } async getServer(regionId: string, serverId: string): Promise { const row = await fetchOne(GET_SERVER_CQL, {region_id: regionId, server_id: serverId}); return row ? this.mapServerRow(row) : null; } async createServer(server: Omit): Promise { const now = new Date(); const fullServer: VoiceServerRecord = { ...server, createdAt: now, updatedAt: now, }; await this.upsertServer(fullServer); return fullServer; } async upsertServer(server: VoiceServerRecord): Promise { const row: VoiceServerRow = { region_id: server.regionId, server_id: server.serverId, endpoint: server.endpoint, api_key: server.apiKey, api_secret: server.apiSecret, is_active: server.isActive, vip_only: server.restrictions.vipOnly, required_guild_features: new Set(server.restrictions.requiredGuildFeatures), allowed_guild_ids: new Set(Array.from(server.restrictions.allowedGuildIds).map((id) => BigInt(id))), allowed_user_ids: new Set(Array.from(server.restrictions.allowedUserIds).map((id) => BigInt(id))), created_at: server.createdAt ?? new Date(), updated_at: server.updatedAt ?? new Date(), }; await upsertOne(VoiceServers.upsertAll(row)); } async deleteServer(regionId: string, serverId: string): Promise { await deleteOneOrMany(VoiceServers.deleteByPk({region_id: regionId, server_id: serverId})); } private mapRegionRow(row: VoiceRegionRow): VoiceRegionRecord { return { id: row.id, name: row.name, emoji: row.emoji, latitude: row.latitude, longitude: row.longitude, isDefault: row.is_default ?? false, restrictions: { vipOnly: row.vip_only ?? false, requiredGuildFeatures: new Set(toIterable(row.required_guild_features)), allowedGuildIds: createGuildIDSet(new Set(toIterable(row.allowed_guild_ids))), allowedUserIds: createUserIDSet(new Set(toIterable(row.allowed_user_ids))), }, createdAt: row.created_at, updatedAt: row.updated_at, }; } private mapServerRow(row: VoiceServerRow): VoiceServerRecord { return { regionId: row.region_id, serverId: row.server_id, endpoint: row.endpoint, apiKey: row.api_key, apiSecret: row.api_secret, isActive: row.is_active ?? true, restrictions: { vipOnly: row.vip_only ?? false, requiredGuildFeatures: new Set(toIterable(row.required_guild_features)), allowedGuildIds: createGuildIDSet(new Set(toIterable(row.allowed_guild_ids))), allowedUserIds: createUserIDSet(new Set(toIterable(row.allowed_user_ids))), }, createdAt: row.created_at, updatedAt: row.updated_at, }; } }