Spaces:
Build error
Build error
| import fastify from 'fastify'; | |
| import { config } from '@config/index.js'; | |
| import { setupAuthHooks } from '@core/auth/middleware.js'; | |
| import { sessionRepository } from '@core/storage/repositories/sessions.js'; | |
| import { scrapeQueueRepository } from '@core/storage/repositories/scrapeQueue.js'; | |
| import { channelRepository } from '@core/storage/repositories/channels.js'; | |
| import { userRepository } from '@core/storage/repositories/users.js'; | |
| import { voteRepository } from '@core/storage/repositories/votes.js'; | |
| import { banRepository } from '@core/storage/repositories/bans.js'; | |
| import { logRepository } from '@core/storage/repositories/logs.js'; | |
| // Import routes | |
| import { authRoutes } from '@modules/auth/routes.js'; | |
| import { channelRoutes } from '@modules/channels/routes.js'; | |
| import { searchRoutes } from '@modules/search/routes.js'; | |
| import { userRoutes } from '@modules/user/routes.js'; | |
| import { adminRoutes } from '@modules/admin/routes.js'; | |
| async function buildApp() { | |
| const app = fastify({ | |
| logger: { | |
| level: config.LOG_LEVEL, | |
| transport: config.NODE_ENV === 'development' ? { target: 'pino-pretty' } : undefined, | |
| }, | |
| trustProxy: true, | |
| }); | |
| // Register plugins | |
| await app.register(import('@fastify/cookie'), { | |
| secret: config.SESSION_SECRET, | |
| parseOptions: {}, | |
| }); | |
| await app.register(import('@fastify/session'), { | |
| secret: config.SESSION_SECRET, | |
| cookie: { | |
| secure: config.NODE_ENV === 'production', | |
| httpOnly: true, | |
| sameSite: 'lax', | |
| maxAge: config.sessionTtlMs, | |
| path: '/', | |
| }, | |
| saveUninitialized: false, | |
| rolling: true, | |
| store: { | |
| get: async (sid: string) => { | |
| const session = await sessionRepository.findValidSession(sid); | |
| return session?.data || null; | |
| }, | |
| set: async (sid: string, data: Record<string, unknown>, ttl: number) => { | |
| // Handled by sessionRepository | |
| }, | |
| destroy: async (sid: string) => { | |
| await sessionRepository.deleteSession(sid); | |
| }, | |
| }, | |
| }); | |
| await app.register(import('@fastify/csrf-protection'), { | |
| sessionPlugin: '@fastify/session', | |
| cookieOpts: { signed: true }, | |
| }); | |
| await app.register(import('@fastify/rate-limit'), { | |
| max: 100, | |
| timeWindow: '1 minute', | |
| keyGenerator: (req) => req.ip, | |
| }); | |
| await app.register(import('@fastify/helmet'), { | |
| contentSecurityPolicy: { | |
| directives: { | |
| defaultSrc: ["'self'"], | |
| scriptSrc: ["'self'", "'unsafe-inline'"], | |
| styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'], | |
| fontSrc: ["'self'", 'https://fonts.gstatic.com'], | |
| imgSrc: ["'self'", 'data:', 'https:'], | |
| connectSrc: ["'self'"], | |
| }, | |
| }, | |
| }); | |
| await app.register(import('@fastify/static'), { | |
| root: './public', | |
| prefix: '/public/', | |
| }); | |
| await app.register(import('@fastify/view'), { | |
| engine: { | |
| nunjucks: await import('nunjucks'), | |
| }, | |
| templates: './src/views', | |
| options: { | |
| autoescape: true, | |
| noCache: config.NODE_ENV === 'development', | |
| }, | |
| }); | |
| // Initialize repositories | |
| await Promise.all([ | |
| userRepository.initialize(), | |
| channelRepository.initialize(), | |
| voteRepository.initialize(), | |
| banRepository.initialize(), | |
| logRepository.initialize(), | |
| sessionRepository.initialize(), | |
| scrapeQueueRepository.initialize(), | |
| ]); | |
| // Setup auth hooks | |
| setupAuthHooks(app); | |
| // Health check | |
| app.get('/health', async () => ({ status: 'ok', timestamp: new Date().toISOString() })); | |
| // Register modules | |
| await app.register(authRoutes, { prefix: '/auth' }); | |
| await app.register(channelRoutes, { prefix: '/channels' }); | |
| await app.register(searchRoutes, { prefix: '/' }); | |
| await app.register(userRoutes, { prefix: '/user' }); | |
| await app.register(adminRoutes, { prefix: '/admin' }); | |
| // 404 handler | |
| app.setNotFoundHandler(async (request, reply) => { | |
| return reply.status(404).view('pages/404.njk', { url: request.url }); | |
| }); | |
| // Error handler | |
| app.setErrorHandler(async (error, request, reply) => { | |
| request.log.error(error); | |
| await logRepository.log({ | |
| level: 'error', | |
| message: error.message, | |
| context: { url: request.url, method: request.method }, | |
| error: { name: error.name, message: error.message, stack: error.stack }, | |
| userId: request.user?.id, | |
| ip: request.ip, | |
| userAgent: request.headers['user-agent'], | |
| url: request.url, | |
| method: request.method, | |
| statusCode: error.statusCode || 500, | |
| }); | |
| const statusCode = error.statusCode || 500; | |
| return reply.status(statusCode).view('pages/error.njk', { | |
| error: config.NODE_ENV === 'development' ? error.message : 'Internal Server Error', | |
| statusCode, | |
| }); | |
| }); | |
| return app; | |
| } | |
| async function start() { | |
| try { | |
| const app = await buildApp(); | |
| await app.listen({ port: config.PORT, host: '0.0.0.0' }); | |
| console.log(`🚀 Server running at ${config.APP_URL}`); | |
| } catch (err) { | |
| console.error('Failed to start server:', err); | |
| process.exit(1); | |
| } | |
| } | |
| if (import.meta.url === `file://${process.argv[1]}`) { | |
| start(); | |
| } | |
| export { buildApp }; |