/* * 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 {AuthService} from '~/auth/AuthService'; import type {SudoVerificationResult} from '~/auth/services/SudoVerificationService'; import type {ChannelID, GuildID, MessageID, UserID} from '~/BrandedTypes'; import type {IChannelRepository} from '~/channel/IChannelRepository'; import type {ChannelService} from '~/channel/services/ChannelService'; import type {GuildMemberResponse} from '~/guild/GuildModel'; import type {IGuildRepository} from '~/guild/IGuildRepository'; import type {GuildService} from '~/guild/services/GuildService'; import type {IDiscriminatorService} from '~/infrastructure/DiscriminatorService'; import type {EntityAssetService} from '~/infrastructure/EntityAssetService'; import type {IEmailService} from '~/infrastructure/IEmailService'; import type {IGatewayService} from '~/infrastructure/IGatewayService'; import type {IMediaService} from '~/infrastructure/IMediaService'; import type {IRateLimitService} from '~/infrastructure/IRateLimitService'; import type {IStorageService} from '~/infrastructure/IStorageService'; import type {RedisAccountDeletionQueueService} from '~/infrastructure/RedisAccountDeletionQueueService'; import type {RedisBulkMessageDeletionQueueService} from '~/infrastructure/RedisBulkMessageDeletionQueueService'; import type {SnowflakeService} from '~/infrastructure/SnowflakeService'; import type {UserCacheService} from '~/infrastructure/UserCacheService'; import type { AuthSession, BetaCode, Channel, GuildMember, Message, MfaBackupCode, PushSubscription, Relationship, User, UserGuildSettings, UserSettings, } from '~/Models'; import type {RequestCache} from '~/middleware/RequestCacheMiddleware'; import type {BotMfaMirrorService} from '~/oauth/BotMfaMirrorService'; import type {PackService} from '~/pack/PackService'; import type { CreatePrivateChannelRequest, FriendRequestByTagRequest, UserGuildSettingsUpdateRequest, UserSettingsUpdateRequest, UserUpdateRequest, } from '~/user/UserModel'; import type {SavedMessageStatus} from '~/user/UserTypes'; import type {UserPermissionUtils} from '~/utils/UserPermissionUtils'; import type {IWorkerService} from '~/worker/IWorkerService'; import type {IUserAccountRepository} from '../repositories/IUserAccountRepository'; import type {IUserAuthRepository} from '../repositories/IUserAuthRepository'; import type {IUserChannelRepository} from '../repositories/IUserChannelRepository'; import type {IUserContentRepository} from '../repositories/IUserContentRepository'; import type {IUserRelationshipRepository} from '../repositories/IUserRelationshipRepository'; import type {IUserSettingsRepository} from '../repositories/IUserSettingsRepository'; import type {UserHarvestResponse} from '../UserHarvestModel'; import {UserAccountService} from './UserAccountService'; import {UserAuthService} from './UserAuthService'; import {UserChannelService} from './UserChannelService'; import type {UserContactChangeLogService} from './UserContactChangeLogService'; import {UserContentService} from './UserContentService'; import {UserRelationshipService} from './UserRelationshipService'; interface UpdateUserParams { user: User; oldAuthSession: AuthSession; data: UserUpdateRequest; request: Request; sudoContext?: SudoVerificationResult; emailVerifiedViaToken?: boolean; } export class UserService { private accountService: UserAccountService; private authService: UserAuthService; private relationshipService: UserRelationshipService; private channelService: UserChannelService; private contentService: UserContentService; constructor( userAccountRepository: IUserAccountRepository, userSettingsRepository: IUserSettingsRepository, userAuthRepository: IUserAuthRepository, userRelationshipRepository: IUserRelationshipRepository, userChannelRepository: IUserChannelRepository, userContentRepository: IUserContentRepository, authService: AuthService, userCacheService: UserCacheService, channelService: ChannelService, channelRepository: IChannelRepository, guildService: GuildService, gatewayService: IGatewayService, entityAssetService: EntityAssetService, mediaService: IMediaService, packService: PackService, emailService: IEmailService, snowflakeService: SnowflakeService, discriminatorService: IDiscriminatorService, rateLimitService: IRateLimitService, guildRepository: IGuildRepository, workerService: IWorkerService, userPermissionUtils: UserPermissionUtils, redisDeletionQueue: RedisAccountDeletionQueueService, bulkMessageDeletionQueue: RedisBulkMessageDeletionQueueService, botMfaMirrorService: BotMfaMirrorService, contactChangeLogService: UserContactChangeLogService, ) { this.accountService = new UserAccountService( userAccountRepository, userSettingsRepository, userRelationshipRepository, userChannelRepository, authService, userCacheService, guildService, gatewayService, entityAssetService, mediaService, packService, emailService, rateLimitService, guildRepository, discriminatorService, redisDeletionQueue, contactChangeLogService, ); this.authService = new UserAuthService( userAccountRepository, userAuthRepository, authService, emailService, gatewayService, botMfaMirrorService, ); this.relationshipService = new UserRelationshipService( userAccountRepository, userRelationshipRepository, gatewayService, userPermissionUtils, ); this.channelService = new UserChannelService( userAccountRepository, userChannelRepository, userRelationshipRepository, channelService, channelRepository, gatewayService, mediaService, snowflakeService, userPermissionUtils, ); this.contentService = new UserContentService( userAccountRepository, userContentRepository, userCacheService, channelService, channelRepository, gatewayService, mediaService, workerService, snowflakeService, bulkMessageDeletionQueue, ); } async findUnique(userId: UserID): Promise { return await this.accountService.findUnique(userId); } async findUniqueAssert(userId: UserID): Promise { return await this.accountService.findUniqueAssert(userId); } async getUserProfile(params: { userId: UserID; targetId: UserID; guildId?: GuildID; withMutualFriends?: boolean; withMutualGuilds?: boolean; requestCache: RequestCache; }): Promise<{ user: User; guildMember?: GuildMemberResponse | null; guildMemberDomain?: GuildMember | null; premiumType?: number; premiumSince?: Date; premiumLifetimeSequence?: number; mutualFriends?: Array; mutualGuilds?: Array<{id: string; nick: string | null}>; }> { return await this.accountService.getUserProfile(params); } async update(params: UpdateUserParams): Promise { return await this.accountService.update(params); } async generateUniqueDiscriminator(username: string): Promise { return await this.accountService.generateUniqueDiscriminator(username); } async checkUsernameDiscriminatorAvailability(params: {username: string; discriminator: number}): Promise { return await this.accountService.checkUsernameDiscriminatorAvailability(params); } async findSettings(userId: UserID): Promise { return await this.accountService.findSettings(userId); } async updateSettings(params: {userId: UserID; data: UserSettingsUpdateRequest}): Promise { return await this.accountService.updateSettings(params); } async findGuildSettings(userId: UserID, guildId: GuildID | null): Promise { return await this.accountService.findGuildSettings(userId, guildId); } async updateGuildSettings(params: { userId: UserID; guildId: GuildID | null; data: UserGuildSettingsUpdateRequest; }): Promise { return await this.accountService.updateGuildSettings(params); } async getUserNote(params: {userId: UserID; targetId: UserID}): Promise<{note: string} | null> { return await this.accountService.getUserNote(params); } async getUserNotes(userId: UserID): Promise> { return await this.accountService.getUserNotes(userId); } async setUserNote(params: {userId: UserID; targetId: UserID; note: string | null}): Promise { return await this.accountService.setUserNote(params); } async selfDisable(userId: UserID): Promise { return await this.accountService.selfDisable(userId); } async selfDelete(userId: UserID): Promise { return await this.accountService.selfDelete(userId); } async dispatchUserUpdate(user: User): Promise { return await this.accountService.dispatchUserUpdate(user); } async dispatchUserSettingsUpdate({userId, settings}: {userId: UserID; settings: UserSettings}): Promise { return await this.accountService.dispatchUserSettingsUpdate({userId, settings}); } async dispatchUserGuildSettingsUpdate({ userId, settings, }: { userId: UserID; settings: UserGuildSettings; }): Promise { return await this.accountService.dispatchUserGuildSettingsUpdate({userId, settings}); } async dispatchUserNoteUpdate(params: {userId: UserID; targetId: UserID; note: string}): Promise { return await this.accountService.dispatchUserNoteUpdate(params); } async enableMfaTotp(params: {user: User; secret: string; code: string}): Promise> { return await this.authService.enableMfaTotp(params); } async disableMfaTotp(params: { user: User; code: string; sudoContext: SudoVerificationResult; password?: string; }): Promise { return await this.authService.disableMfaTotp(params); } async getMfaBackupCodes(params: { user: User; regenerate: boolean; sudoContext: SudoVerificationResult; password?: string; }): Promise> { return await this.authService.getMfaBackupCodes(params); } async regenerateMfaBackupCodes(user: User): Promise> { return await this.authService.regenerateMfaBackupCodes(user); } async verifyEmail(token: string): Promise { return await this.authService.verifyEmail(token); } async resendVerificationEmail(user: User): Promise { return await this.authService.resendVerificationEmail(user); } async getRelationships(userId: UserID): Promise> { return await this.relationshipService.getRelationships(userId); } async sendFriendRequestByTag({ userId, data, userCacheService, requestCache, }: { userId: UserID; data: FriendRequestByTagRequest; userCacheService: UserCacheService; requestCache: RequestCache; }): Promise { return await this.relationshipService.sendFriendRequestByTag({userId, data, userCacheService, requestCache}); } async sendFriendRequest({ userId, targetId, userCacheService, requestCache, }: { userId: UserID; targetId: UserID; userCacheService: UserCacheService; requestCache: RequestCache; }): Promise { return await this.relationshipService.sendFriendRequest({userId, targetId, userCacheService, requestCache}); } async acceptFriendRequest({ userId, targetId, userCacheService, requestCache, }: { userId: UserID; targetId: UserID; userCacheService: UserCacheService; requestCache: RequestCache; }): Promise { const relationship = await this.relationshipService.acceptFriendRequest({ userId, targetId, userCacheService, requestCache, }); await this.channelService.ensureDmOpenForBothUsers({ userId, recipientId: targetId, userCacheService, requestCache, }); return relationship; } async blockUser({ userId, targetId, userCacheService, requestCache, }: { userId: UserID; targetId: UserID; userCacheService: UserCacheService; requestCache: RequestCache; }): Promise { return await this.relationshipService.blockUser({userId, targetId, userCacheService, requestCache}); } async updateFriendNickname({ userId, targetId, nickname, userCacheService, requestCache, }: { userId: UserID; targetId: UserID; nickname: string | null; userCacheService: UserCacheService; requestCache: RequestCache; }): Promise { return await this.relationshipService.updateFriendNickname({ userId, targetId, nickname, userCacheService, requestCache, }); } async removeRelationship({userId, targetId}: {userId: UserID; targetId: UserID}): Promise { return await this.relationshipService.removeRelationship({userId, targetId}); } async dispatchRelationshipCreate({ userId, relationship, userCacheService, requestCache, }: { userId: UserID; relationship: Relationship; userCacheService: UserCacheService; requestCache: RequestCache; }): Promise { return await this.relationshipService.dispatchRelationshipCreate({ userId, relationship, userCacheService, requestCache, }); } async dispatchRelationshipUpdate({ userId, relationship, userCacheService, requestCache, }: { userId: UserID; relationship: Relationship; userCacheService: UserCacheService; requestCache: RequestCache; }): Promise { return await this.relationshipService.dispatchRelationshipUpdate({ userId, relationship, userCacheService, requestCache, }); } async dispatchRelationshipRemove({userId, targetId}: {userId: UserID; targetId: string}): Promise { return await this.relationshipService.dispatchRelationshipRemove({userId, targetId}); } async getPrivateChannels(userId: UserID): Promise> { return await this.channelService.getPrivateChannels(userId); } async createOrOpenDMChannel({ userId, data, userCacheService, requestCache, }: { userId: UserID; data: CreatePrivateChannelRequest; userCacheService: UserCacheService; requestCache: RequestCache; }): Promise { return await this.channelService.createOrOpenDMChannel({userId, data, userCacheService, requestCache}); } async pinDmChannel({userId, channelId}: {userId: UserID; channelId: ChannelID}): Promise { return await this.channelService.pinDmChannel({userId, channelId}); } async unpinDmChannel({userId, channelId}: {userId: UserID; channelId: ChannelID}): Promise { return await this.channelService.unpinDmChannel({userId, channelId}); } async preloadDMMessages(params: { userId: UserID; channelIds: Array; }): Promise> { return await this.channelService.preloadDMMessages(params); } async getBetaCodes(userId: UserID): Promise> { return await this.contentService.getBetaCodes(userId); } async getBetaCodeAllowanceInfo(userId: UserID): Promise<{allowance: number; nextResetAt: Date | null}> { return await this.contentService.getBetaCodeAllowanceInfo(userId); } async createBetaCode(userId: UserID): Promise { return await this.contentService.createBetaCode(userId); } async deleteBetaCode(params: {userId: UserID; code: string}): Promise { return await this.contentService.deleteBetaCode(params); } async getRecentMentions(params: { userId: UserID; limit: number; everyone: boolean; roles: boolean; guilds: boolean; before?: MessageID; }): Promise> { return await this.contentService.getRecentMentions(params); } async deleteRecentMention({userId, messageId}: {userId: UserID; messageId: MessageID}): Promise { return await this.contentService.deleteRecentMention({userId, messageId}); } async getSavedMessages({userId, limit}: {userId: UserID; limit: number}): Promise< Array<{ channelId: ChannelID; messageId: MessageID; status: SavedMessageStatus; message: Message | null; }> > { return await this.contentService.getSavedMessages({userId, limit}); } async saveMessage({ userId, channelId, messageId, userCacheService, requestCache, }: { userId: UserID; channelId: ChannelID; messageId: MessageID; userCacheService: UserCacheService; requestCache: RequestCache; }): Promise { return await this.contentService.saveMessage({userId, channelId, messageId, userCacheService, requestCache}); } async unsaveMessage({userId, messageId}: {userId: UserID; messageId: MessageID}): Promise { return await this.contentService.unsaveMessage({userId, messageId}); } async registerPushSubscription(params: { userId: UserID; endpoint: string; keys: {p256dh: string; auth: string}; userAgent?: string; }): Promise { return await this.contentService.registerPushSubscription(params); } async listPushSubscriptions(userId: UserID): Promise> { return await this.contentService.listPushSubscriptions(userId); } async deletePushSubscription(userId: UserID, subscriptionId: string): Promise { return await this.contentService.deletePushSubscription(userId, subscriptionId); } async requestDataHarvest(userId: UserID): Promise<{harvestId: string}> { return await this.contentService.requestDataHarvest(userId); } async getHarvestStatus(userId: UserID, harvestId: bigint): Promise { return await this.contentService.getHarvestStatus(userId, harvestId); } async getLatestHarvest(userId: UserID): Promise { return await this.contentService.getLatestHarvest(userId); } async getHarvestDownloadUrl( userId: UserID, harvestId: bigint, storageService: IStorageService, ): Promise<{downloadUrl: string; expiresAt: string}> { return await this.contentService.getHarvestDownloadUrl(userId, harvestId, storageService); } async requestBulkMessageDeletion(params: {userId: UserID; delayMs?: number}): Promise { return await this.contentService.requestBulkMessageDeletion(params); } async cancelBulkMessageDeletion(userId: UserID): Promise { return await this.contentService.cancelBulkMessageDeletion(userId); } async dispatchRecentMentionDelete({userId, messageId}: {userId: UserID; messageId: MessageID}): Promise { return await this.contentService.dispatchRecentMentionDelete({userId, messageId}); } async dispatchSavedMessageCreate({ userId, message, userCacheService, requestCache, }: { userId: UserID; message: Message; userCacheService: UserCacheService; requestCache: RequestCache; }): Promise { return await this.contentService.dispatchSavedMessageCreate({userId, message, userCacheService, requestCache}); } async dispatchSavedMessageDelete({userId, messageId}: {userId: UserID; messageId: MessageID}): Promise { return await this.contentService.dispatchSavedMessageDelete({userId, messageId}); } }