This repository has no description
Something went wrong. Try again.
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198// SPDX-License-Identifier: AGPL-3.0-or-later
import {UnknownGuildError} from '@fluxer/errors/src/domains/guild/UnknownGuildError';import {UnknownApplicationError} from '@fluxer/errors/src/domains/oauth/UnknownApplicationError';import {UnknownUserError} from '@fluxer/errors/src/domains/user/UnknownUserError';import type { ApplicationAdminResponse, ListGuildApplicationsRequest, ListGuildApplicationsResponse, ListUserApplicationsRequest, ListUserApplicationsResponse, LookupApplicationRequest, LookupApplicationResponse, TransferApplicationOwnershipRequest,} from '@fluxer/schema/src/domains/admin/AdminApplicationSchemas';import type {ApiContext} from '../../ApiContext';import {createApplicationID, createGuildID, createUserID, type UserID} from '../../BrandedTypes';import type {IGuildRepositoryAggregate} from '../../guild/repositories/IGuildRepositoryAggregate';import type {Application} from '../../models/Application';import type {IApplicationRepository} from '../../oauth/repositories/IApplicationRepository';import type {AdminAuditService} from './AdminAuditService';
interface AdminApplicationServiceDeps { apiContext: ApiContext; applicationRepository: IApplicationRepository; auditService: AdminAuditService; guildRepository: IGuildRepositoryAggregate;}
interface UserDisplay { username: string | null; global_name: string | null; discriminator: string | null;}
export class AdminApplicationService { constructor(private readonly deps: AdminApplicationServiceDeps) {}
async lookupApplication(data: LookupApplicationRequest): Promise<LookupApplicationResponse> { const {applicationRepository} = this.deps; const applicationId = createApplicationID(data.application_id); const application = await applicationRepository.getApplication(applicationId); if (!application) { return {application: null}; } const userDisplays = await this.loadUserDisplays([application.ownerUserId, application.botUserId]); return { application: this.toResponse(application, userDisplays), }; }
async listUserApplications(data: ListUserApplicationsRequest): Promise<ListUserApplicationsResponse> { const {applicationRepository} = this.deps; const {users: userRepository} = this.deps.apiContext.services; const ownerUserId = createUserID(data.user_id); const owner = await userRepository.findUnique(ownerUserId); if (!owner) { throw new UnknownUserError(); } const applications = await applicationRepository.listApplicationsByOwner(ownerUserId); const ownerDisplay: UserDisplay = { username: owner.username, global_name: owner.globalName ?? null, discriminator: String(owner.discriminator).padStart(4, '0'), }; const botUserIds = applications.map((app) => app.botUserId).filter((id): id is UserID => id !== null); const botDisplays = await this.loadUserDisplays(botUserIds); const displayMap = new Map<string, UserDisplay>(); displayMap.set(ownerUserId.toString(), ownerDisplay); for (const [id, display] of botDisplays.entries()) { displayMap.set(id, display); } return { applications: applications.map((app) => this.toResponse(app, displayMap)), }; }
async listGuildApplications(data: ListGuildApplicationsRequest): Promise<ListGuildApplicationsResponse> { const {applicationRepository, guildRepository} = this.deps; const guildId = createGuildID(data.guild_id); const guild = await guildRepository.findUnique(guildId); if (!guild) { throw new UnknownGuildError(); } const members = await guildRepository.listMembers(guildId); const applications: Array<Application> = []; for (const member of members) { const application = await applicationRepository.getApplication( createApplicationID(BigInt(member.userId.toString())), ); if (!application?.botUserId || application.botUserId !== member.userId) continue; applications.push(application); } const userDisplays = await this.loadUserDisplays( applications.flatMap((application) => [application.ownerUserId, application.botUserId]), ); return { applications: applications.map((application) => this.toResponse(application, userDisplays)), }; }
async transferApplicationOwnership( data: TransferApplicationOwnershipRequest, adminUserId: UserID, auditLogReason: string | null, ) { const {applicationRepository, auditService} = this.deps; const {users: userRepository} = this.deps.apiContext.services; const applicationId = createApplicationID(data.application_id); const application = await applicationRepository.getApplication(applicationId); if (!application) { throw new UnknownApplicationError(); } const newOwnerId = createUserID(data.new_owner_id); const newOwner = await userRepository.findUnique(newOwnerId); if (!newOwner) { throw new UnknownUserError(); } const oldOwnerId = application.ownerUserId; const oldRow = application.toRow(); const updatedApplication = await applicationRepository.upsertApplication( {...oldRow, owner_user_id: newOwnerId}, oldRow, ); await auditService.createAuditLog({ adminUserId, targetType: 'application', targetId: BigInt(applicationId), action: 'transfer_ownership', auditLogReason, metadata: new Map([ ['old_owner_id', oldOwnerId.toString()], ['new_owner_id', newOwnerId.toString()], ]), }); const userDisplays = await this.loadUserDisplays([updatedApplication.ownerUserId, updatedApplication.botUserId]); return { application: this.toResponse(updatedApplication, userDisplays), }; }
private async loadUserDisplays(userIds: ReadonlyArray<UserID | null>): Promise<Map<string, UserDisplay>> { const {users: userRepository} = this.deps.apiContext.services; const uniqueIds = new Set<string>(); const nonNullIds: Array<UserID> = []; for (const userId of userIds) { if (userId === null) continue; const key = userId.toString(); if (uniqueIds.has(key)) continue; uniqueIds.add(key); nonNullIds.push(userId); } const users = await Promise.all(nonNullIds.map((id) => userRepository.findUnique(id))); const map = new Map<string, UserDisplay>(); for (let i = 0; i < nonNullIds.length; i += 1) { const id = nonNullIds[i]; if (!id) continue; const user = users[i]; if (!user) continue; map.set(id.toString(), { username: user.username, global_name: user.globalName ?? null, discriminator: String(user.discriminator).padStart(4, '0'), }); } return map; }
private toResponse(application: Application, userDisplays: Map<string, UserDisplay>): ApplicationAdminResponse { const emptyDisplay: UserDisplay = {username: null, global_name: null, discriminator: null}; const ownerDisplay = userDisplays.get(application.ownerUserId.toString()) ?? emptyDisplay; const botDisplay = application.botUserId ? (userDisplays.get(application.botUserId.toString()) ?? emptyDisplay) : emptyDisplay; return { id: application.applicationId.toString(), name: application.name, owner_user_id: application.ownerUserId.toString(), owner_username: ownerDisplay.username, owner_global_name: ownerDisplay.global_name, owner_discriminator: ownerDisplay.discriminator, bot_user_id: application.botUserId?.toString() ?? null, bot_username: botDisplay.username, bot_global_name: botDisplay.global_name, bot_discriminator: botDisplay.discriminator, bot_is_public: application.botIsPublic, bot_require_code_grant: application.botRequireCodeGrant, oauth2_redirect_uris: Array.from(application.oauth2RedirectUris), has_client_secret: application.clientSecretHash !== null, has_bot_token: application.botTokenHash !== null, bot_token_preview: application.botTokenPreview, bot_token_created_at: application.botTokenCreatedAt?.toISOString() ?? null, client_secret_created_at: application.clientSecretCreatedAt?.toISOString() ?? null, version: application.version, }; }}