234 lines
7.6 KiB
TypeScript
234 lines
7.6 KiB
TypeScript
/*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
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<T>(value: unknown): Array<T> {
|
|
if (value === null || value === undefined) return [];
|
|
if (value instanceof Set) return Array.from(value) as Array<T>;
|
|
if (Array.isArray(value)) return value as Array<T>;
|
|
return [value as T];
|
|
}
|
|
|
|
const VoiceRegions = defineTable<VoiceRegionRow, 'id'>({
|
|
name: 'voice_regions',
|
|
columns: VOICE_REGION_COLUMNS,
|
|
primaryKey: ['id'],
|
|
});
|
|
|
|
const VoiceServers = defineTable<VoiceServerRow, 'region_id' | 'server_id'>({
|
|
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<Array<VoiceRegionRecord>> {
|
|
const rows = await fetchMany<VoiceRegionRow>(LIST_REGIONS_CQL, {});
|
|
return rows.map((row) => this.mapRegionRow(row));
|
|
}
|
|
|
|
async listRegionsWithServers(): Promise<Array<VoiceRegionWithServers>> {
|
|
const regions = await this.listRegions();
|
|
const results: Array<VoiceRegionWithServers> = [];
|
|
|
|
for (const region of regions) {
|
|
const servers = await this.listServersForRegion(region.id);
|
|
results.push({
|
|
...region,
|
|
servers,
|
|
});
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
async getRegion(id: string): Promise<VoiceRegionRecord | null> {
|
|
const row = await fetchOne<VoiceRegionRow>(GET_REGION_CQL, {id});
|
|
return row ? this.mapRegionRow(row) : null;
|
|
}
|
|
|
|
async getRegionWithServers(id: string): Promise<VoiceRegionWithServers | null> {
|
|
const region = await this.getRegion(id);
|
|
if (!region) {
|
|
return null;
|
|
}
|
|
const servers = await this.listServersForRegion(id);
|
|
return {...region, servers};
|
|
}
|
|
|
|
async upsertRegion(region: VoiceRegionRecord): Promise<void> {
|
|
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<void> {
|
|
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<VoiceRegionRecord, 'createdAt' | 'updatedAt'>): Promise<VoiceRegionRecord> {
|
|
const now = new Date();
|
|
const fullRegion: VoiceRegionRecord = {
|
|
...region,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
await this.upsertRegion(fullRegion);
|
|
return fullRegion;
|
|
}
|
|
|
|
async listServersForRegion(regionId: string): Promise<Array<VoiceServerRecord>> {
|
|
const rows = await fetchMany<VoiceServerRow>(LIST_SERVERS_FOR_REGION_CQL, {region_id: regionId});
|
|
return rows.map((row) => this.mapServerRow(row));
|
|
}
|
|
|
|
async listServers(regionId: string): Promise<Array<VoiceServerRecord>> {
|
|
return this.listServersForRegion(regionId);
|
|
}
|
|
|
|
async getServer(regionId: string, serverId: string): Promise<VoiceServerRecord | null> {
|
|
const row = await fetchOne<VoiceServerRow>(GET_SERVER_CQL, {region_id: regionId, server_id: serverId});
|
|
return row ? this.mapServerRow(row) : null;
|
|
}
|
|
|
|
async createServer(server: Omit<VoiceServerRecord, 'createdAt' | 'updatedAt'>): Promise<VoiceServerRecord> {
|
|
const now = new Date();
|
|
const fullServer: VoiceServerRecord = {
|
|
...server,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
};
|
|
await this.upsertServer(fullServer);
|
|
return fullServer;
|
|
}
|
|
|
|
async upsertServer(server: VoiceServerRecord): Promise<void> {
|
|
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<void> {
|
|
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<string>(row.required_guild_features)),
|
|
allowedGuildIds: createGuildIDSet(new Set(toIterable<bigint>(row.allowed_guild_ids))),
|
|
allowedUserIds: createUserIDSet(new Set(toIterable<bigint>(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<string>(row.required_guild_features)),
|
|
allowedGuildIds: createGuildIDSet(new Set(toIterable<bigint>(row.allowed_guild_ids))),
|
|
allowedUserIds: createUserIDSet(new Set(toIterable<bigint>(row.allowed_user_ids))),
|
|
},
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
}
|