index.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import 'dotenv/config';
  2. import express from 'express';
  3. import cors from 'cors';
  4. import path from 'path';
  5. import cookieParser from 'cookie-parser';
  6. import { bigintToNumber } from './lib/prisma';
  7. import authRoutes from './routes/auth';
  8. import projectRoutes from './routes/projects';
  9. import assetRoutes from './routes/assets';
  10. import commentRoutes from './routes/comments';
  11. import userRoutes from './routes/users';
  12. import invitationRoutes from './routes/invitations';
  13. import settingsRoutes from './routes/settings';
  14. import shareRoutes from './routes/share';
  15. import folderRoutes from './routes/folders';
  16. const app = express();
  17. const PORT = process.env.API_PORT || 3001;
  18. // ── Middleware ────────────────────────────────────────────────────────────────
  19. const allowedOrigins = (process.env.ALLOWED_ORIGINS || '').split(',').filter(Boolean);
  20. app.use(cors({
  21. origin: (origin, callback) => {
  22. // Allow if: no origin (server-side), OR '*', OR origin is in allowed list
  23. if (!origin || allowedOrigins.includes('*') || allowedOrigins.includes(origin)) {
  24. callback(null, true);
  25. } else {
  26. callback(new Error(`Origin ${origin} not allowed by CORS policy`));
  27. }
  28. },
  29. credentials: true,
  30. }));
  31. app.use(express.json());
  32. app.use(cookieParser());
  33. // ── Serve uploaded files ──────────────────────────────────────────────────────
  34. const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
  35. app.use('/uploads', express.static(path.resolve(UPLOAD_DIR)));
  36. // ── Routes ───────────────────────────────────────────────────────────────────
  37. app.get('/health', (_req, res) => res.json({ status: 'ok', timestamp: new Date().toISOString() }));
  38. app.use('/api/auth', authRoutes);
  39. app.use('/api/projects', projectRoutes);
  40. app.use('/api/assets', assetRoutes);
  41. app.use('/api/assets', commentRoutes);
  42. app.use('/api/comments', commentRoutes);
  43. app.use('/api/users', userRoutes);
  44. app.use('/api/invitations', invitationRoutes);
  45. app.use('/api/settings', settingsRoutes);
  46. app.use('/api/share', shareRoutes);
  47. app.use('/api/folders', folderRoutes);
  48. // ── BigInt-safe res.json() — patch Response prototype ─────────────────────────
  49. // Adding toJSON to BigInt prototype auto-converts BigInt → Number in JSON.stringify
  50. // This works because Express uses JSON.stringify internally for res.json()
  51. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  52. (BigInt.prototype as any).toJSON = function () { return Number(this); };
  53. // ── 404 handler ─────────────────────────────────────────────────────────────
  54. app.use((_req, res) => {
  55. res.status(404).json({ error: 'Not found' });
  56. });
  57. // ── Error handler ─────────────────────────────────────────────────────────────
  58. app.use((err: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
  59. console.error('Unhandled error:', err);
  60. const status = (err as any).statusCode ?? 500;
  61. const message = (err as any).statusCode ? err.message : 'Internal server error';
  62. res.status(status).json({ error: message });
  63. });
  64. // ── Start ────────────────────────────────────────────────────────────────────
  65. app.listen(PORT, () => {
  66. console.log(`🚀 VidReview API running on http://localhost:${PORT}`);
  67. });