Chat-With-AI / src /app.ts
NathMen12's picture
Upload 56 files
ef73937 verified
Raw
History Blame Contribute Delete
5.14 kB
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 };