/*
* 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,
};
}
}