- Extract shared utils: asSafeChunkSize (validation.ts), S3 detection (s3-detection.ts) - Remove dead _handleMaybeS3Root from index.ts and routes/index.ts - Remove dead _asArray from file-controller.ts - Consolidate maybeCompressChunk into shared compress.ts - Extract checkConditionalHeaders helper, remove ~120 lines dupe in s3-controller - Remove 500+ lines dead code from s3-object.ts (unused use cases + helpers) - Delegate upload-file.ts chunked path to ChunkedStorage, remove dupe - Fix broken dynamic import in file-controller.ts → proper DI - Fix test/files.test.ts import path and mocks [skip ci]
77 lines
2.4 KiB
TypeScript
77 lines
2.4 KiB
TypeScript
import { serve } from 'bun';
|
|
import { config } from './env';
|
|
import { fileInfoCache } from './infrastructure/cache/index';
|
|
import { startBot } from './interfaces/bot/handler';
|
|
import { handleS3Request } from './interfaces/http/controllers/s3-controller';
|
|
import { cleanupRateLimitCache } from './interfaces/http/middleware/rate-limit';
|
|
import { routes } from './interfaces/http/routes/index';
|
|
import { logger } from './shared/logger/index';
|
|
import { metricsCollector } from './shared/metrics/index';
|
|
import { getS3RouteBucket, shouldHandleS3 } from './shared/utils/s3-detection';
|
|
|
|
// ─── Auto-run migration at startup ──────────────────────────────────────────
|
|
try {
|
|
const { runMigration } = await import('./infrastructure/persistence/drizzle/migrate');
|
|
await runMigration();
|
|
} catch {
|
|
logger.warn('Auto-migration skipped (non-fatal)');
|
|
}
|
|
|
|
const server = serve({
|
|
port: config.port,
|
|
routes,
|
|
fetch: async (req: Request) => {
|
|
const headers = Object.fromEntries(req.headers);
|
|
if (shouldHandleS3(req, headers)) {
|
|
return handleS3Request(req, getS3RouteBucket(req));
|
|
}
|
|
return new Response('Not Found', { status: 404 });
|
|
},
|
|
});
|
|
|
|
const bot = await startBot();
|
|
|
|
logger.info('Server started', { port: config.port, url: config.baseUrl });
|
|
|
|
const gracefulShutdown = async (signal: string): Promise<void> => {
|
|
logger.info('Graceful shutdown signal received', { signal });
|
|
|
|
logger.info('Closing HTTP server — no new requests accepted');
|
|
server.stop();
|
|
|
|
logger.info('Stopping Telegram bot');
|
|
bot.stop(signal);
|
|
|
|
logger.info('Server shutdown complete');
|
|
process.exit(0);
|
|
};
|
|
|
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
|
|
// Periodic maintenance intervals
|
|
setInterval(cleanupRateLimitCache, 60000);
|
|
setInterval(
|
|
() => {
|
|
const removed = fileInfoCache.cleanup();
|
|
if (removed > 0) {
|
|
logger.info(`Cleaned up ${removed} expired cache entries`);
|
|
}
|
|
},
|
|
5 * 60 * 1000,
|
|
);
|
|
setInterval(
|
|
() => {
|
|
const snapshot = metricsCollector.getSnapshot();
|
|
logger.info('Metrics snapshot', {
|
|
uploadLatency: snapshot.uploadLatency,
|
|
uploadThroughput: snapshot.uploadThroughput.toFixed(2),
|
|
errorRate: snapshot.errorRate.toFixed(2),
|
|
cacheHitRate: snapshot.cacheHitRate.toFixed(2),
|
|
});
|
|
},
|
|
5 * 60 * 1000,
|
|
);
|
|
|
|
logger.info('Application running successfully');
|