refactor progress
This commit is contained in:
149
packages/api/src/app/APILifecycle.tsx
Normal file
149
packages/api/src/app/APILifecycle.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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 {randomUUID} from 'node:crypto';
|
||||
import type {APIConfig} from '@fluxer/api/src/config/APIConfig';
|
||||
import {GuildDataRepository} from '@fluxer/api/src/guild/repositories/GuildDataRepository';
|
||||
import type {ILogger} from '@fluxer/api/src/ILogger';
|
||||
import {KVAccountDeletionQueueService} from '@fluxer/api/src/infrastructure/KVAccountDeletionQueueService';
|
||||
import {initializeMetricsService} from '@fluxer/api/src/infrastructure/MetricsService';
|
||||
import {InstanceConfigRepository} from '@fluxer/api/src/instance/InstanceConfigRepository';
|
||||
import {ipBanCache} from '@fluxer/api/src/middleware/IpBanMiddleware';
|
||||
import {initializeServiceSingletons} from '@fluxer/api/src/middleware/ServiceMiddleware';
|
||||
import {ensureVoiceResourcesInitialized, getKVClient} from '@fluxer/api/src/middleware/ServiceRegistry';
|
||||
import {ReportRepository} from '@fluxer/api/src/report/ReportRepository';
|
||||
import {initializeSearch, shutdownSearch} from '@fluxer/api/src/SearchFactory';
|
||||
import {warmupAdminSearchIndexes} from '@fluxer/api/src/search/SearchWarmup';
|
||||
import {VisionarySlotInitializer} from '@fluxer/api/src/stripe/VisionarySlotInitializer';
|
||||
import {UserRepository} from '@fluxer/api/src/user/repositories/UserRepository';
|
||||
import {VoiceDataInitializer} from '@fluxer/api/src/voice/VoiceDataInitializer';
|
||||
|
||||
export function createInitializer(config: APIConfig, logger: ILogger): () => Promise<void> {
|
||||
return async (): Promise<void> => {
|
||||
logger.info('Initializing API service...');
|
||||
|
||||
const kvClient = getKVClient();
|
||||
ipBanCache.setRefreshSubscriber(kvClient);
|
||||
await ipBanCache.initialize();
|
||||
logger.info('IP ban cache initialized');
|
||||
|
||||
initializeMetricsService();
|
||||
logger.info('Metrics service initialized');
|
||||
|
||||
await initializeServiceSingletons();
|
||||
logger.info('Service singletons initialized');
|
||||
|
||||
try {
|
||||
const userRepository = new UserRepository();
|
||||
const kvDeletionQueue = new KVAccountDeletionQueueService(kvClient, userRepository);
|
||||
|
||||
if (await kvDeletionQueue.needsRebuild()) {
|
||||
logger.warn('KV deletion queue needs rebuild, rebuilding...');
|
||||
await kvDeletionQueue.rebuildState();
|
||||
} else {
|
||||
logger.info('KV deletion queue state is healthy');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error({error}, 'Failed to verify KV deletion queue state');
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.info({search_url: config.search.url}, 'Initializing search...');
|
||||
await initializeSearch();
|
||||
logger.info('Search initialized');
|
||||
|
||||
// All API replicas share the same Meilisearch cluster, so only one should warm it.
|
||||
const warmupLockKey = 'fluxer:search:warmup:admin';
|
||||
const warmupLockToken = randomUUID();
|
||||
const warmupLockTtlSeconds = 60 * 60;
|
||||
const acquiredWarmupLock = await kvClient.setnx(warmupLockKey, warmupLockToken, warmupLockTtlSeconds);
|
||||
if (!acquiredWarmupLock) {
|
||||
logger.info('Another API instance is warming search indexes, skipping warmup');
|
||||
} else {
|
||||
try {
|
||||
await warmupAdminSearchIndexes({
|
||||
userRepository: new UserRepository(),
|
||||
guildRepository: new GuildDataRepository(),
|
||||
reportRepository: new ReportRepository(),
|
||||
logger,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error({error}, 'Admin search warmup failed (continuing startup)');
|
||||
} finally {
|
||||
try {
|
||||
await kvClient.releaseLock(warmupLockKey, warmupLockToken);
|
||||
} catch (error) {
|
||||
logger.warn({error}, 'Failed to release admin search warmup lock');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.voice.enabled && config.voice.defaultRegion) {
|
||||
const voiceDataInitializer = new VoiceDataInitializer();
|
||||
await voiceDataInitializer.initialize();
|
||||
await ensureVoiceResourcesInitialized();
|
||||
logger.info('Voice data initialized');
|
||||
}
|
||||
|
||||
if (config.dev.testModeEnabled && config.stripe.enabled) {
|
||||
const visionarySlotInitializer = new VisionarySlotInitializer();
|
||||
await visionarySlotInitializer.initialize();
|
||||
logger.info('Stripe visionary slots initialized');
|
||||
}
|
||||
|
||||
if (config.dev.testModeEnabled) {
|
||||
const instanceConfigRepository = new InstanceConfigRepository();
|
||||
try {
|
||||
await instanceConfigRepository.setSsoConfig({
|
||||
enabled: false,
|
||||
authorizationUrl: null,
|
||||
tokenUrl: null,
|
||||
clientId: null,
|
||||
});
|
||||
logger.info('Reset SSO config to disabled for test mode');
|
||||
} catch (error) {
|
||||
logger.warn({error}, 'Failed to reset SSO config for test mode');
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('API service initialization complete');
|
||||
};
|
||||
}
|
||||
|
||||
export function createShutdown(logger: ILogger): () => Promise<void> {
|
||||
return async (): Promise<void> => {
|
||||
logger.info('Shutting down API service...');
|
||||
|
||||
try {
|
||||
await shutdownSearch();
|
||||
logger.info('Search service shut down');
|
||||
} catch (error) {
|
||||
logger.error({error}, 'Error shutting down search service');
|
||||
}
|
||||
|
||||
try {
|
||||
ipBanCache.shutdown();
|
||||
logger.info('IP ban cache shut down');
|
||||
} catch (error) {
|
||||
logger.error({error}, 'Error shutting down IP ban cache');
|
||||
}
|
||||
|
||||
logger.info('API service shutdown complete');
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user