| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- 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}`);
- });
|