/* * 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 {AdminAuditService} from '@fluxer/api/src/admin/services/AdminAuditService'; import type {UserID} from '@fluxer/api/src/BrandedTypes'; import {SNOWFLAKE_RESERVATION_REFRESH_CHANNEL} from '@fluxer/api/src/constants/InstanceConfig'; import type {SnowflakeReservationRepository} from '@fluxer/api/src/instance/SnowflakeReservationRepository'; import type {ICacheService} from '@fluxer/cache/src/ICacheService'; import {ValidationErrorCodes} from '@fluxer/constants/src/ValidationErrorCodes'; import {InputValidationError} from '@fluxer/errors/src/domains/core/InputValidationError'; interface AdminSnowflakeReservationServiceDeps { repository: SnowflakeReservationRepository; cacheService: ICacheService; auditService: AdminAuditService; } export class AdminSnowflakeReservationService { constructor(private readonly deps: AdminSnowflakeReservationServiceDeps) {} async listReservations() { const {repository} = this.deps; const entries = await repository.listReservations(); return entries.map((entry) => ({ email: entry.emailKey, snowflake: entry.snowflake.toString(), updated_at: entry.updatedAt ? entry.updatedAt.toISOString() : null, })); } async setReservation(data: {email: string; snowflake: string}, adminUserId: UserID, auditLogReason: string | null) { const {repository, cacheService, auditService} = this.deps; const emailLower = data.email.toLowerCase(); if (!emailLower) { throw InputValidationError.fromCode('email', ValidationErrorCodes.INVALID_EMAIL_ADDRESS); } let snowflakeValue: bigint; try { snowflakeValue = BigInt(data.snowflake); } catch { throw InputValidationError.fromCode('snowflake', ValidationErrorCodes.INVALID_SNOWFLAKE); } await repository.setReservation(emailLower, snowflakeValue); await cacheService.publish(SNOWFLAKE_RESERVATION_REFRESH_CHANNEL, 'refresh'); await auditService.createAuditLog({ adminUserId, targetType: 'snowflake_reservation', targetId: BigInt(0), action: 'set_snowflake_reservation', auditLogReason, metadata: new Map([ ['email', emailLower], ['snowflake', snowflakeValue.toString()], ]), }); } async deleteReservation(data: {email: string}, adminUserId: UserID, auditLogReason: string | null) { const {repository, cacheService, auditService} = this.deps; const emailLower = data.email.toLowerCase(); if (!emailLower) { throw InputValidationError.fromCode('email', ValidationErrorCodes.INVALID_EMAIL_ADDRESS); } await repository.deleteReservation(emailLower); await cacheService.publish(SNOWFLAKE_RESERVATION_REFRESH_CHANNEL, 'refresh'); await auditService.createAuditLog({ adminUserId, targetType: 'snowflake_reservation', targetId: BigInt(0), action: 'delete_snowflake_reservation', auditLogReason, metadata: new Map([['email', emailLower]]), }); } }