import 'dotenv/config'; import express from 'express'; import cors from 'cors'; import path from 'path'; import cookieParser from 'cookie-parser'; import { bigintToNumber } from './lib/prisma'; import authRoutes from './routes/auth'; import projectRoutes from './routes/projects'; import assetRoutes from './routes/assets'; import commentRoutes from './routes/comments'; import userRoutes from './routes/users'; import invitationRoutes from './routes/invitations'; import settingsRoutes from './routes/settings'; import shareRoutes from './routes/share'; import folderRoutes from './routes/folders'; const app = express(); const PORT = process.env.API_PORT || 3001; // ── Middleware ──────────────────────────────────────────────────────────────── const allowedOrigins = (process.env.ALLOWED_ORIGINS || '').split(',').filter(Boolean); app.use(cors({ origin: (origin, callback) => { // Allow if: no origin (server-side), OR '*', OR origin is in allowed list if (!origin || allowedOrigins.includes('*') || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error(`Origin ${origin} not allowed by CORS policy`)); } }, credentials: true, })); app.use(express.json()); app.use(cookieParser()); // ── Serve uploaded files ────────────────────────────────────────────────────── const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads'; app.use('/uploads', express.static(path.resolve(UPLOAD_DIR))); // ── Routes ─────────────────────────────────────────────────────────────────── app.get('/health', (_req, res) => res.json({ status: 'ok', timestamp: new Date().toISOString() })); app.use('/api/auth', authRoutes); app.use('/api/projects', projectRoutes); app.use('/api/assets', assetRoutes); app.use('/api/assets', commentRoutes); app.use('/api/comments', commentRoutes); app.use('/api/users', userRoutes); app.use('/api/invitations', invitationRoutes); app.use('/api/settings', settingsRoutes); app.use('/api/share', shareRoutes); app.use('/api/folders', folderRoutes); // ── BigInt-safe res.json() — patch Response prototype ───────────────────────── // Adding toJSON to BigInt prototype auto-converts BigInt → Number in JSON.stringify // This works because Express uses JSON.stringify internally for res.json() // eslint-disable-next-line @typescript-eslint/no-explicit-any (BigInt.prototype as any).toJSON = function () { return Number(this); }; // ── 404 handler ───────────────────────────────────────────────────────────── app.use((_req, res) => { res.status(404).json({ error: 'Not found' }); }); // ── Error handler ───────────────────────────────────────────────────────────── app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => { console.error('Unhandled error:', err); const status = (err as any).statusCode ?? 500; const message = (err as any).statusCode ? err.message : 'Internal server error'; res.status(status).json({ error: message }); }); // ── Start ──────────────────────────────────────────────────────────────────── app.listen(PORT, () => { console.log(`🚀 VidReview API running on http://localhost:${PORT}`); });