Files
flyer-crawler.projectium.com/server.ts
Torben Sorensen 158778c2ec
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 5m16s
DB refactor for easier testsing
App.ts refactor into hooks
unit tests
2025-12-08 20:46:12 -08:00

160 lines
6.6 KiB
TypeScript

// server.ts
import express, { Request, Response, NextFunction } from 'express';
import timeout from 'connect-timeout';
import cookieParser from 'cookie-parser';
import listEndpoints from 'express-list-endpoints';
import { getPool } from './src/services/db/connection.db';
import passport from './src/routes/passport.routes';
import { logger } from './src/services/logger.server';
// Import routers
import authRouter from './src/routes/auth.routes';
import publicRouter from './src/routes/public.routes'; // This seems to be missing from the original file list, but is required.
import userRouter from './src/routes/user.routes';
import adminRouter from './src/routes/admin.routes';
import aiRouter from './src/routes/ai.routes';
import budgetRouter from './src/routes/budget.routes';
import gamificationRouter from './src/routes/gamification.routes';
import systemRouter from './src/routes/system.routes';
import healthRouter from './src/routes/health.routes';
import { errorHandler } from './src/middleware/errorHandler';
import { backgroundJobService, startBackgroundJobs } from './src/services/backgroundJobService.ts';
import * as db from './src/services/db/index.db';
import { analyticsQueue, weeklyAnalyticsQueue, gracefulShutdown } from './src/services/queueService.server';
// --- START DEBUG LOGGING ---
// Log the database connection details as seen by the SERVER PROCESS.
// This will confirm if the `--env-file` flag is working as expected.
logger.info('--- [SERVER PROCESS LOG] DATABASE CONNECTION ---');
logger.info(` NODE_ENV: ${process.env.NODE_ENV}`);
logger.info(` Host: ${process.env.DB_HOST}`);
logger.info(` Port: ${process.env.DB_PORT}`);
logger.info(` User: ${process.env.DB_USER}`);
logger.info(` Database: ${process.env.DB_NAME}`);
// Query the users table to see what the server process sees on startup.
// Corrected the query to be unambiguous by specifying the table alias for each column.
// `id` and `email` come from the `users` table (u), and `role` comes from the `profiles` table (p).
getPool().query('SELECT u.user_id, u.email, p.role FROM public.users u JOIN public.profiles p ON u.user_id = p.user_id')
.then(res => {
logger.debug('[SERVER PROCESS] Users found in DB on startup:');
console.table(res.rows);
})
.catch(err => {
logger.error('[SERVER PROCESS] Could not query users table on startup.', { err });
});
logger.info('-----------------------------------------------\n');
const app = express();
// --- Core Middleware ---
// Increase the limit for JSON and URL-encoded bodies. This is crucial for handling large file uploads
// that are part of multipart/form-data requests, as the overall request size is checked.
// Setting a 50MB limit to accommodate large flyer images.
app.use(express.json({ limit: '100mb' }));
app.use(express.urlencoded({ limit: '100mb', extended: true }));
app.use(cookieParser()); // Middleware to parse cookies
app.use(passport.initialize()); // Initialize Passport
// --- MOCK AUTH FOR TESTING ---
// This MUST come after passport.initialize() and BEFORE any of the API routes.
import { mockAuth } from './src/routes/passport.routes';
app.use(mockAuth);
// Add a request timeout middleware. This will help prevent requests from hanging indefinitely.
// We set a generous 5-minute timeout to accommodate slow AI processing for large flyers.
app.use(timeout('5m'));
// --- Logging Middleware ---
const getDurationInMilliseconds = (start: [number, number]): number => {
const NS_PER_SEC = 1e9;
const NS_TO_MS = 1e6;
const diff = process.hrtime(start);
return (diff[0] * NS_PER_SEC + diff[1]) / NS_TO_MS;
};
const requestLogger = (req: Request, res: Response, next: NextFunction) => {
const start = process.hrtime();
const { method, originalUrl } = req;
// If the request times out, log it.
if (req.timedout) {
logger.error(`REQUEST TIMEOUT: ${method} ${originalUrl} exceeded the 5m limit.`);
}
logger.debug(`[Request Logger] INCOMING: ${method} ${originalUrl}`);
res.on('finish', () => {
const user = req.user as { user_id?: string } | undefined;
const durationInMilliseconds = getDurationInMilliseconds(start);
const { statusCode } = res;
const userIdentifier = user?.user_id ? ` (User: ${user.user_id})` : '';
const logMessage = `${method} ${originalUrl} ${statusCode} ${durationInMilliseconds.toFixed(2)}ms${userIdentifier}`;
if (statusCode >= 500) logger.error(logMessage);
else if (statusCode >= 400) logger.warn(logMessage);
else logger.info(logMessage);
});
next();
};
app.use(requestLogger); // Use the logging middleware for all requests
// --- Security Warning ---
if (!process.env.JWT_SECRET) {
logger.error('CRITICAL: JWT_SECRET is not set. The application cannot start securely.');
process.exit(1);
}
// --- API Routes ---
// The order of route registration is critical.
// More specific routes should be registered before more general ones.
// 1. Authentication routes for login, registration, etc.
app.use('/api/auth', authRouter);
// 2. System routes for health checks, etc.
app.use('/api/health', healthRouter);
// 3. System routes for pm2 status, etc.
app.use('/api/system', systemRouter);
// 3. General authenticated user routes.
app.use('/api/users', userRouter);
// 4. AI routes, some of which use optional authentication.
app.use('/api/ai', aiRouter);
// 5. Admin routes, which are all protected by admin-level checks.
app.use('/api/admin', adminRouter); // This seems to be missing from the original file list, but is required.
// 6. Budgeting and spending analysis routes.
app.use('/api/budgets', budgetRouter);
// 7. Gamification routes for achievements.
app.use('/api/achievements', gamificationRouter);
// 8. Public routes that require no authentication. This should be last among the API routes.
app.use('/api', publicRouter);
// --- Error Handling and Server Startup ---
// Global error handling middleware. This must be the last `app.use()` call.
app.use(errorHandler);
if (process.env.NODE_ENV !== 'test') {
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
logger.info(`Authentication server started on port ${PORT}`);
console.log('--- REGISTERED API ROUTES ---');
console.table(listEndpoints(app));
console.log('-----------------------------');
});
// Start the scheduled background jobs
startBackgroundJobs(backgroundJobService, analyticsQueue, weeklyAnalyticsQueue);
// --- Graceful Shutdown Handling ---
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
}
// Export the app for integration testing
export default app;