// SPDX-License-Identifier: AGPL-3.0-or-later import crypto from 'node:crypto'; import type {ApiContext} from '@app/api/ApiContext'; import {AdminService} from '@app/api/admin/AdminService'; import {AuthRequestService} from '@app/api/auth/AuthRequestService'; import {DesktopHandoffService} from '@app/api/auth/services/DesktopHandoffService'; import {SsoService} from '@app/api/auth/services/SsoService'; import type {IBlueskyOAuthService} from '@app/api/bluesky/IBlueskyOAuthService'; import {Config} from '@app/api/Config'; import {createApiContext} from '@app/api/CreateApiContext'; import {ChannelRepository} from '@app/api/channel/ChannelRepository'; import {ChannelRequestService} from '@app/api/channel/services/ChannelRequestService'; import {MessageRequestService} from '@app/api/channel/services/message/MessageRequestService'; import {createMessageResponseDataService} from '@app/api/channel/services/message/MessageResponseDataService'; import {StreamService} from '@app/api/channel/services/StreamService'; import {ConnectionRequestService} from '@app/api/connection/ConnectionRequestService'; import {ConnectionService} from '@app/api/connection/ConnectionService'; import {DonationService} from '@app/api/donation/DonationService'; import {DonationCheckoutService} from '@app/api/donation/services/DonationCheckoutService'; import {DonationMagicLinkService} from '@app/api/donation/services/DonationMagicLinkService'; import {FavoriteMemeRequestService} from '@app/api/favorite_meme/FavoriteMemeRequestService'; import {FavoriteMemeService} from '@app/api/favorite_meme/FavoriteMemeService'; import {GuildRepository} from '@app/api/guild/repositories/GuildRepository'; import {DisabledLiveKitService} from '@app/api/infrastructure/DisabledLiveKitService'; import type {IGatewayService} from '@app/api/infrastructure/IGatewayService'; import type {ILiveKitService} from '@app/api/infrastructure/ILiveKitService'; import type {IMediaService} from '@app/api/infrastructure/IMediaService'; import {InMemoryVoiceRoomStore} from '@app/api/infrastructure/InMemoryVoiceRoomStore'; import type {IVoiceRoomStore} from '@app/api/infrastructure/IVoiceRoomStore'; import {LiveKitService} from '@app/api/infrastructure/LiveKitService'; import {LiveKitWebhookService} from '@app/api/infrastructure/LiveKitWebhookService'; import {VoiceRoomStore} from '@app/api/infrastructure/VoiceRoomStore'; import {SingleCommunityService} from '@app/api/instance/SingleCommunityService'; import {InviteRequestService} from '@app/api/invite/InviteRequestService'; import {JobLedgerRepository} from '@app/api/jobs/JobLedgerRepository'; import {createGuildStackServices, type GuildStackServices} from '@app/api/middleware/GuildStackServiceFactory'; import {installLazyServices, type RequestScopedServices} from '@app/api/middleware/LazyServiceProvider'; import type {RequestCache} from '@app/api/middleware/RequestCacheMiddleware'; import { ensureVoiceResourcesInitialized, getBillingRepository, getGatewayService, getKVClient, getLiveKitServiceInstance, getMediaService, getSnowflakeService, getVoiceAvailabilityService, getVoiceRoomStoreInstance, getVoiceTopology, getWorkerService, resolveBlueskyOAuthService, } from '@app/api/middleware/ServiceRegistry'; import { ensureVirusScanInitialized, getAdminApiKeyService, getAdminArchiveService, getAdminRepository, getApplicationRepository, getAssetDeletionQueue, getAttachmentUploadTraceRepository, getAvatarService, getBotAuthService, getCacheService, getChannelRepository, getConnectionRepository, getContactChangeLogService, getDiscriminatorService, getDonationRepository, getEmailChangeRepository, getEmailDnsValidationService, getEmailService, getEmbedService, getEntityAssetService, getEntranceSoundPlayService, getEntranceSoundService, getErrorI18nService, getFavoriteMemeRepository, getGatewayRequestService, getGifService, getGuildAuditLogService, getGuildDiscoveryService, getGuildRepository, getInstanceConfigRepository, getInviteRepository, getKVAccountDeletionQueue, getKVActivityTracker, getKVBulkMessageDeletionQueue, getLimitConfigService, getNcmecSubmissionService, getOAuth2TokenRepository, getPasswordChangeRepository, getPremiumStateReconciliationQueueService, getPurgeQueue, getRateLimitService, getReadStateRequestService, getReadStateService, getReportRepository, getStorageService, getStoreBillingRepository, getStreamPreviewService, getSweegoWebhookService, getThemeService, getUnfurlerService, getUserActivityBuffer, getUserCacheService, getUserPermissionUtils, getUserRepository, getVirusScanServiceInstance, getVoiceRepository, getWebhookRepository, } from '@app/api/middleware/ServiceSingletons'; import {ApplicationService} from '@app/api/oauth/ApplicationService'; import {OAuth2ApplicationsRequestService} from '@app/api/oauth/OAuth2ApplicationsRequestService'; import {OAuth2RequestService} from '@app/api/oauth/OAuth2RequestService'; import {OAuth2Service} from '@app/api/oauth/OAuth2Service'; import {ReportRequestService} from '@app/api/report/ReportRequestService'; import {ReportService} from '@app/api/report/ReportService'; import {RpcService} from '@app/api/rpc/RpcService'; import {getReportSearchService} from '@app/api/SearchFactory'; import {SearchService} from '@app/api/search/SearchService'; import type {StoreEntitlementService} from '@app/api/store_billing/StoreEntitlementService'; import {createStoreEntitlementService} from '@app/api/store_billing/StoreEntitlementServiceFactory'; import {StripeService} from '@app/api/stripe/StripeService'; import {AgeVerificationService} from '@app/api/stripe/services/AgeVerificationService'; import type {HonoEnv} from '@app/api/types/HonoEnv'; import {EmailChangeService} from '@app/api/user/services/EmailChangeService'; import {MfaBackupCodesChallengeService} from '@app/api/user/services/MfaBackupCodesChallengeService'; import {PasswordChangeService} from '@app/api/user/services/PasswordChangeService'; import {UserAccountRequestService} from '@app/api/user/services/UserAccountRequestService'; import {UserAuthRequestService} from '@app/api/user/services/UserAuthRequestService'; import {UserChannelRequestService} from '@app/api/user/services/UserChannelRequestService'; import {UserContentRequestService} from '@app/api/user/services/UserContentRequestService'; import {UserRelationshipRequestService} from '@app/api/user/services/UserRelationshipRequestService'; import {UserService} from '@app/api/user/services/UserService'; import {getRequestClientIp} from '@app/api/utils/RequestClientIp'; import {VoiceService} from '@app/api/voice/VoiceService'; import {WebhookRequestService} from '@app/api/webhook/WebhookRequestService'; import {WebhookService} from '@app/api/webhook/WebhookService'; import {createMiddleware} from 'hono/factory'; export {initializeServiceSingletons} from '@app/api/middleware/ServiceSingletons'; let _reportService: ReportService | null = null; export function getReportServiceInstance(): ReportService { if (!_reportService) { _reportService = new ReportService( getReportRepository(), getChannelRepository(), getGuildRepository(), getUserRepository(), getInviteRepository(), getEmailService(), getEmailDnsValidationService(), getSnowflakeService(), getStorageService(), getGatewayService(), getRateLimitService(), getReportSearchService(), ); } return _reportService; } export function shutdownReportService(): void { if (_reportService) { _reportService.shutdown(); _reportService = null; } } let _liveKitWebhookService: LiveKitWebhookService | null = null; function getLiveKitWebhookService(): LiveKitWebhookService | null { if (!_liveKitWebhookService) { const voiceTopology = getVoiceTopology(); if (!voiceTopology) return null; const liveKitService: ILiveKitService = getLiveKitServiceInstance() ?? new DisabledLiveKitService(); const voiceRoomStore: IVoiceRoomStore = getVoiceRoomStoreInstance() ?? new InMemoryVoiceRoomStore(); const hasVoiceInfrastructure = Config.voice.enabled && voiceTopology !== null && liveKitService instanceof LiveKitService && voiceRoomStore instanceof VoiceRoomStore; if (hasVoiceInfrastructure && voiceTopology) { _liveKitWebhookService = new LiveKitWebhookService( voiceRoomStore, getGatewayService(), liveKitService, voiceTopology, ); } } return _liveKitWebhookService; } class RequestServices implements RequestScopedServices { private cachedGateway: IGatewayService | undefined; private cachedMedia: IMediaService | undefined; private cachedLiveKit: ILiveKitService | undefined; private cachedVoiceRooms: IVoiceRoomStore | undefined; private cachedVoice: VoiceService | null | undefined; private cachedGuildStack: GuildStackServices | undefined; private cachedChannelRepository: ChannelRepository | undefined; private cachedGuildRepository: GuildRepository | undefined; private cachedAdminService: AdminService | undefined; private cachedApplicationService: ApplicationService | undefined; private cachedAuthRequestService: AuthRequestService | undefined; private cachedSsoService: SsoService | undefined; private cachedDesktopHandoffService: DesktopHandoffService | undefined; private cachedChannelRequestService: ChannelRequestService | undefined; private cachedMessageRequestService: MessageRequestService | undefined; private cachedConnectionService: ConnectionService | undefined; private cachedConnectionRequestService: ConnectionRequestService | undefined; private cachedStreamService: StreamService | undefined; private cachedFavoriteMemeService: FavoriteMemeService | undefined; private cachedFavoriteMemeRequestService: FavoriteMemeRequestService | undefined; private cachedSingleCommunityService: SingleCommunityService | undefined; private cachedEmailChangeService: EmailChangeService | undefined; private cachedMfaBackupCodesChallengeService: MfaBackupCodesChallengeService | undefined; private cachedPasswordChangeService: PasswordChangeService | undefined; private cachedInviteRequestService: InviteRequestService | undefined; private cachedOAuth2Service: OAuth2Service | undefined; private cachedOAuth2RequestService: OAuth2RequestService | undefined; private cachedOAuth2ApplicationsRequestService: OAuth2ApplicationsRequestService | undefined; private cachedReportRequestService: ReportRequestService | undefined; private cachedRpcService: RpcService | undefined; private cachedSearchService: SearchService | undefined; private cachedStripeService: StripeService | undefined; private cachedStoreEntitlementService: StoreEntitlementService | undefined; private cachedAgeVerificationService: AgeVerificationService | undefined; private cachedDonationService: DonationService | undefined; private cachedUserService: UserService | undefined; private cachedUserAccountRequestService: UserAccountRequestService | undefined; private cachedUserAuthRequestService: UserAuthRequestService | undefined; private cachedUserChannelRequestService: UserChannelRequestService | undefined; private cachedUserContentRequestService: UserContentRequestService | undefined; private cachedUserRelationshipRequestService: UserRelationshipRequestService | undefined; private cachedWebhookService: WebhookService | undefined; private cachedWebhookRequestService: WebhookRequestService | undefined; constructor( private readonly context: ApiContext, private readonly bluesky: IBlueskyOAuthService, private readonly entityCache: RequestCache | undefined, ) {} private get requestGuildRepository(): GuildRepository { this.cachedGuildRepository ??= this.entityCache ? new GuildRepository(this.entityCache) : getGuildRepository(); return this.cachedGuildRepository; } get gatewayService(): IGatewayService { this.cachedGateway ??= getGatewayService(); return this.cachedGateway; } get mediaService(): IMediaService { this.cachedMedia ??= getMediaService(); return this.cachedMedia; } private get liveKit(): ILiveKitService { this.cachedLiveKit ??= getLiveKitServiceInstance() ?? new DisabledLiveKitService(); return this.cachedLiveKit; } private get voiceRooms(): IVoiceRoomStore { this.cachedVoiceRooms ??= getVoiceRoomStoreInstance() ?? new InMemoryVoiceRoomStore(); return this.cachedVoiceRooms; } private get voice(): VoiceService | null { if (this.cachedVoice === undefined) { const liveKitService = this.liveKit; const voiceRoomStore = this.voiceRooms; const voiceAvailabilityService = getVoiceAvailabilityService(); const hasVoiceInfrastructure = Config.voice.enabled && getVoiceTopology() !== null && liveKitService instanceof LiveKitService && voiceRoomStore instanceof VoiceRoomStore; this.cachedVoice = hasVoiceInfrastructure && voiceAvailabilityService !== null ? new VoiceService( liveKitService, getGuildRepository(), getUserRepository(), getChannelRepository(), voiceRoomStore, voiceAvailabilityService, ) : null; } return this.cachedVoice; } private get guildStack(): GuildStackServices { this.cachedGuildStack ??= createGuildStackServices({ apiContext: this.context, channelRepository: this.channelRepository, userRepository: getUserRepository(), guildRepository: this.requestGuildRepository, inviteRepository: getInviteRepository(), webhookRepository: getWebhookRepository(), favoriteMemeRepository: getFavoriteMemeRepository(), avatarService: getAvatarService(), entityAssetService: getEntityAssetService(), assetDeletionQueue: getAssetDeletionQueue(), userCacheService: getUserCacheService(), limitConfigService: getLimitConfigService(), embedService: getEmbedService(), readStateService: getReadStateService(), storageService: getStorageService(), attachmentUploadTraceRepository: getAttachmentUploadTraceRepository(), virusScanService: getVirusScanServiceInstance(), purgeQueue: getPurgeQueue(), guildAuditLogService: getGuildAuditLogService(), voiceRoomStore: this.voiceRooms, liveKitService: this.liveKit, voiceAvailabilityService: getVoiceAvailabilityService(), }); return this.cachedGuildStack; } get channelService() { return this.guildStack.channelService; } get guildService() { return this.guildStack.guildService; } get inviteService() { return this.guildStack.inviteService; } get blueskyOAuthService(): IBlueskyOAuthService { return this.bluesky; } get adminApiKeyService() { return getAdminApiKeyService(); } get adminArchiveService() { return getAdminArchiveService(); } get applicationRepository() { return getApplicationRepository(); } get botAuthService() { return getBotAuthService(); } get cacheService() { return getCacheService(); } get channelRepository(): ChannelRepository { this.cachedChannelRepository ??= this.entityCache ? new ChannelRepository(this.entityCache) : getChannelRepository(); return this.cachedChannelRepository; } get contactChangeLogService() { return getContactChangeLogService(); } get emailService() { return getEmailService(); } get embedService() { return getEmbedService(); } get entityAssetService() { return getEntityAssetService(); } get entranceSoundService() { return getEntranceSoundService(); } get entranceSoundPlayService() { return getEntranceSoundPlayService(); } get errorI18nService() { return getErrorI18nService(); } get gatewayRequestService() { return getGatewayRequestService(); } get gifService() { return getGifService(); } get discoveryService() { return getGuildDiscoveryService(); } get instanceConfigRepository() { return getInstanceConfigRepository(); } get kvActivityTracker() { return getKVActivityTracker(); } get limitConfigService() { return getLimitConfigService(); } get ncmecSubmissionService() { return getNcmecSubmissionService(); } get oauth2TokenRepository() { return getOAuth2TokenRepository(); } get rateLimitService() { return getRateLimitService(); } get readStateService() { return getReadStateService(); } get readStateRequestService() { return getReadStateRequestService(); } get reportService() { return getReportServiceInstance(); } get snowflakeService() { return getSnowflakeService(); } get storageService() { return getStorageService(); } get streamPreviewService() { return getStreamPreviewService(); } get sweegoWebhookService() { return getSweegoWebhookService(); } get themeService() { return getThemeService(); } get userActivityBuffer() { return getUserActivityBuffer(); } get userCacheService() { return getUserCacheService(); } get userRepository() { return getUserRepository(); } get workerService() { return getWorkerService(); } get liveKitWebhookService(): LiveKitWebhookService | undefined { const liveKitService = this.liveKit; const voiceRoomStore = this.voiceRooms; const hasVoiceInfrastructure = Config.voice.enabled && getVoiceTopology() !== null && liveKitService instanceof LiveKitService && voiceRoomStore instanceof VoiceRoomStore; if (!hasVoiceInfrastructure) { return undefined; } return getLiveKitWebhookService() ?? undefined; } get adminService(): AdminService { this.cachedAdminService ??= new AdminService( this.context, getGuildRepository(), getChannelRepository(), getAdminRepository(), getInviteRepository(), getDiscriminatorService(), this.guildService, getUserCacheService(), this.channelService, this.userService, getEntityAssetService(), getAssetDeletionQueue(), getStorageService(), getReportServiceInstance(), getVoiceRepository(), getKVBulkMessageDeletionQueue(), getApplicationRepository(), this.stripeService.getStripe(), new JobLedgerRepository(), this.storeEntitlementService, ); return this.cachedAdminService; } get applicationService(): ApplicationService { this.cachedApplicationService ??= new ApplicationService(this.context, { applicationRepository: getApplicationRepository(), channelRepository: getChannelRepository(), userCacheService: getUserCacheService(), entityAssetService: getEntityAssetService(), discriminatorService: getDiscriminatorService(), botAuthService: getBotAuthService(), }); return this.cachedApplicationService; } get ssoService(): SsoService { this.cachedSsoService ??= new SsoService( this.context, getInstanceConfigRepository(), getDiscriminatorService(), getKVActivityTracker(), ); return this.cachedSsoService; } get desktopHandoffService(): DesktopHandoffService { this.cachedDesktopHandoffService ??= new DesktopHandoffService(this.context); return this.cachedDesktopHandoffService; } get authRequestService(): AuthRequestService { if (!this.cachedAuthRequestService) { this.cachedAuthRequestService = new AuthRequestService( this.context, this.ssoService, this.desktopHandoffService, { inviteService: this.inviteService, instanceConfigRepository: getInstanceConfigRepository(), singleCommunityService: this.singleCommunityService, discriminatorService: getDiscriminatorService(), kvActivityTracker: getKVActivityTracker(), }, { inviteService: this.inviteService, kvDeletionQueue: getKVAccountDeletionQueue(), }, ); } return this.cachedAuthRequestService; } get singleCommunityService(): SingleCommunityService { this.cachedSingleCommunityService ??= new SingleCommunityService( getInstanceConfigRepository(), this.guildService.data, this.guildService.members, ); return this.cachedSingleCommunityService; } get channelRequestService(): ChannelRequestService { this.cachedChannelRequestService ??= new ChannelRequestService(this.channelService, getUserCacheService()); return this.cachedChannelRequestService; } get messageRequestService(): MessageRequestService { this.cachedMessageRequestService ??= new MessageRequestService( this.channelService, createMessageResponseDataService(), ); return this.cachedMessageRequestService; } get connectionService(): ConnectionService { this.cachedConnectionService ??= new ConnectionService(getConnectionRepository(), this.gatewayService); return this.cachedConnectionService; } get connectionRequestService(): ConnectionRequestService { this.cachedConnectionRequestService ??= new ConnectionRequestService( this.connectionService, Config.auth.connectionInitiationSecret, ); return this.cachedConnectionRequestService; } get streamService(): StreamService { this.cachedStreamService ??= new StreamService( getCacheService(), this.channelService, this.gatewayService, getStreamPreviewService(), ); return this.cachedStreamService; } get favoriteMemeService(): FavoriteMemeService { this.cachedFavoriteMemeService ??= new FavoriteMemeService( this.context, getFavoriteMemeRepository(), this.channelService, getStorageService(), getUnfurlerService(), getLimitConfigService(), getGifService(), ); return this.cachedFavoriteMemeService; } get favoriteMemeRequestService(): FavoriteMemeRequestService { this.cachedFavoriteMemeRequestService ??= new FavoriteMemeRequestService(this.favoriteMemeService); return this.cachedFavoriteMemeRequestService; } get emailChangeService(): EmailChangeService { this.cachedEmailChangeService ??= new EmailChangeService(this.context, getEmailChangeRepository()); return this.cachedEmailChangeService; } get mfaBackupCodesChallengeService(): MfaBackupCodesChallengeService { this.cachedMfaBackupCodesChallengeService ??= new MfaBackupCodesChallengeService(this.context); return this.cachedMfaBackupCodesChallengeService; } get passwordChangeService(): PasswordChangeService { this.cachedPasswordChangeService ??= new PasswordChangeService(this.context, getPasswordChangeRepository()); return this.cachedPasswordChangeService; } get inviteRequestService(): InviteRequestService { this.cachedInviteRequestService ??= new InviteRequestService( this.inviteService, this.channelService, this.guildService, this.gatewayService, getUserCacheService(), ); return this.cachedInviteRequestService; } get oauth2Service(): OAuth2Service { this.cachedOAuth2Service ??= new OAuth2Service(this.context, { applicationRepository: getApplicationRepository(), oauth2TokenRepository: getOAuth2TokenRepository(), }); return this.cachedOAuth2Service; } get oauth2RequestService(): OAuth2RequestService { this.cachedOAuth2RequestService ??= new OAuth2RequestService( this.context, this.oauth2Service, getApplicationRepository(), getOAuth2TokenRepository(), getBotAuthService(), this.applicationService, this.guildService, this.channelService, ); return this.cachedOAuth2RequestService; } get oauth2ApplicationsRequestService(): OAuth2ApplicationsRequestService { this.cachedOAuth2ApplicationsRequestService ??= new OAuth2ApplicationsRequestService( this.context, this.applicationService, getApplicationRepository(), ); return this.cachedOAuth2ApplicationsRequestService; } get reportRequestService(): ReportRequestService { this.cachedReportRequestService ??= new ReportRequestService(getReportServiceInstance()); return this.cachedReportRequestService; } get rpcService(): RpcService { this.cachedRpcService ??= new RpcService( getUserRepository(), getGuildRepository(), getChannelRepository(), getUserCacheService(), getReadStateService(), this.context, this.gatewayService, getDiscriminatorService(), getFavoriteMemeRepository(), getBotAuthService(), getInviteRepository(), getWebhookRepository(), getStorageService(), getAvatarService(), getRateLimitService(), getLimitConfigService(), getKVClient(), getWorkerService(), getPremiumStateReconciliationQueueService(), getInstanceConfigRepository(), this.voice, getVoiceAvailabilityService(), ); return this.cachedRpcService; } get searchService(): SearchService { this.cachedSearchService ??= new SearchService({ channelRepository: getChannelRepository(), channelService: this.channelService, guildService: this.guildService, userRepository: getUserRepository(), userCacheService: getUserCacheService(), workerService: getWorkerService(), }); return this.cachedSearchService; } get stripeService(): StripeService { this.cachedStripeService ??= new StripeService( getUserRepository(), this.gatewayService, getGuildRepository(), this.guildService, getCacheService(), getBillingRepository(), getStoreBillingRepository(), this.storeEntitlementService, ); return this.cachedStripeService; } get storeEntitlementService(): StoreEntitlementService { this.cachedStoreEntitlementService ??= createStoreEntitlementService({ userRepository: getUserRepository(), userCacheService: getUserCacheService(), gatewayService: this.gatewayService, kvClient: getKVClient(), snowflakeService: getSnowflakeService(), premiumStateReconciliationQueueService: getPremiumStateReconciliationQueueService(), }); return this.cachedStoreEntitlementService; } get ageVerificationService(): AgeVerificationService | undefined { if (Config.instance.selfHosted) { return undefined; } this.cachedAgeVerificationService ??= new AgeVerificationService( this.stripeService.getStripe(), getUserRepository(), this.gatewayService, getCacheService(), ); return this.cachedAgeVerificationService; } get donationService(): DonationService | undefined { if (Config.instance.selfHosted) { return undefined; } this.cachedDonationService ??= new DonationService( new DonationMagicLinkService(getDonationRepository(), getEmailService(), getEmailDnsValidationService()), new DonationCheckoutService( this.stripeService.getStripe(), getDonationRepository(), getEmailDnsValidationService(), ), ); return this.cachedDonationService; } get userService(): UserService { this.cachedUserService ??= new UserService( this.context, getUserCacheService(), this.channelService, getChannelRepository(), this.guildService, getEntityAssetService(), getDiscriminatorService(), getGuildRepository(), getUserPermissionUtils(), getKVAccountDeletionQueue(), getKVBulkMessageDeletionQueue(), getContactChangeLogService(), getConnectionRepository(), getLimitConfigService(), ); return this.cachedUserService; } get userAccountRequestService(): UserAccountRequestService { if (!this.cachedUserAccountRequestService) { this.cachedUserAccountRequestService = new UserAccountRequestService( this.emailChangeService, this.userService.accountService, this.userService.channelService, getUserRepository(), getUserCacheService(), ); } return this.cachedUserAccountRequestService; } get userAuthRequestService(): UserAuthRequestService { this.cachedUserAuthRequestService ??= new UserAuthRequestService( this.context, getUserRepository(), getGuildRepository(), ); return this.cachedUserAuthRequestService; } get userChannelRequestService(): UserChannelRequestService { this.cachedUserChannelRequestService ??= new UserChannelRequestService( this.userService.channelService, getUserCacheService(), ); return this.cachedUserChannelRequestService; } get userContentRequestService(): UserContentRequestService { this.cachedUserContentRequestService ??= new UserContentRequestService( this.userService.contentService, getUserCacheService(), ); return this.cachedUserContentRequestService; } get userRelationshipRequestService(): UserRelationshipRequestService { this.cachedUserRelationshipRequestService ??= new UserRelationshipRequestService( this.userService.relationshipService, this.userService.channelService, getUserCacheService(), ); return this.cachedUserRelationshipRequestService; } get webhookService(): WebhookService { this.cachedWebhookService ??= new WebhookService( getWebhookRepository(), this.guildService, this.channelService, getChannelRepository(), getCacheService(), this.gatewayService, getAvatarService(), this.mediaService, getSnowflakeService(), getGuildAuditLogService(), getLimitConfigService(), ); return this.cachedWebhookService; } get webhookRequestService(): WebhookRequestService { this.cachedWebhookRequestService ??= new WebhookRequestService( this.webhookService, getChannelRepository(), getUserCacheService(), this.liveKitWebhookService ?? null, getSweegoWebhookService(), ); return this.cachedWebhookRequestService; } } export const ServiceMiddleware = createMiddleware(async (ctx, next) => { const apiContext = createApiContext({ requestId: ctx.get('requestId') ?? crypto.randomUUID(), clientIp: getRequestClientIp(ctx), userAgent: ctx.req.header('user-agent') ?? null, }); ctx.set('apiContext', apiContext); ctx.set('sudoModeValid', false); await ensureVirusScanInitialized(); await ensureVoiceResourcesInitialized(); const blueskyOAuthService = await resolveBlueskyOAuthService(getInstanceConfigRepository()); installLazyServices(ctx, new RequestServices(apiContext, blueskyOAuthService, ctx.get('requestCache'))); await next(); }); export function resetServiceMiddlewareForTesting(): void { shutdownReportService(); _liveKitWebhookService = null; }