import express from 'express'; import cors from 'cors'; import helmet from 'helmet'; import rateLimit from 'express-rate-limit'; import path from 'path'; import fs from 'fs'; import { extractUser } from './middleware/authMiddleware.js'; import projectRoutes from './routes/projectRoutes.js'; import partRoutes from './routes/partRoutes.js'; import paintingSchemeRoutes from './routes/paintingSchemeRoutes.js'; import applicationRecordRoutes from './routes/applicationRecordRoutes.js'; import inspectionRoutes from './routes/inspectionRoutes.js'; import analysisRoutes from './routes/analysisRoutes.js'; import dataSheetRoutes from './routes/dataSheetRoutes.js'; import yieldStudyRoutes from './routes/yieldStudyRoutes.js'; import userRoutes from './routes/userRoutes.js'; import systemSettingsRoutes from './routes/systemSettingsRoutes.js'; import geometryTypeRoutes from './routes/geometryTypeRoutes.js'; import stockRoutes from './routes/stockRoutes.js'; import notificationRoutes from './routes/notificationRoutes.js'; import instrumentRoutes from './routes/instrumentRoutes.js'; import messageRoutes from './routes/messageRoutes.js'; import backupRoutes from './routes/backupRoutes.js'; const app = express(); // Security Headers app.use(helmet({ contentSecurityPolicy: false, // Pode interferir com o React em dev se não configurado direito crossOriginEmbedderPolicy: false })); // Rate Limiter const apiLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 1000, // limit each IP to 1000 requests per windowMs message: 'Too many requests from this IP, please try again later.' }); // Apply rate limiter only to API routes app.use('/api', apiLimiter); // CORS const allowedOrigins = [ 'https://gpi.reifonas.cloud', 'http://localhost:5173', 'http://localhost:3000' ]; app.use(cors({ origin: (origin, callback) => { if (!origin || allowedOrigins.includes(origin)) { callback(null, true); } else { callback(new Error('Not allowed by CORS')); } }, methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'x-organization-id', 'x-organization-name'] })); app.use(express.json({ limit: '5mb' })); app.use(express.urlencoded({ limit: '5mb', extended: true })); // Static Uploads const uploadsPath = path.join(process.cwd(), 'uploads'); if (!fs.existsSync(uploadsPath)) { fs.mkdirSync(uploadsPath, { recursive: true }); } app.use('/uploads', express.static(uploadsPath)); // Serve frontend static files const distPath = path.join(process.cwd(), 'dist'); app.use(express.static(distPath)); // API Routes (all API routes pass through extractUser here, avoid duplication in route files) const apiRouter = express.Router(); apiRouter.use(extractUser); apiRouter.use('/users', userRoutes); apiRouter.use('/projects', projectRoutes); apiRouter.use('/parts', partRoutes); apiRouter.use('/painting-schemes', paintingSchemeRoutes); apiRouter.use('/application-records', applicationRecordRoutes); apiRouter.use('/inspections', inspectionRoutes); apiRouter.use('/', analysisRoutes); apiRouter.use('/datasheets', dataSheetRoutes); apiRouter.use('/yield-studies', yieldStudyRoutes); apiRouter.use('/system-settings', systemSettingsRoutes); apiRouter.use('/geometry-types', geometryTypeRoutes); apiRouter.use('/stock', stockRoutes); apiRouter.use('/notifications', notificationRoutes); apiRouter.use('/instruments', instrumentRoutes); apiRouter.use('/messages', messageRoutes); apiRouter.use('/backup', backupRoutes); app.use('/api', apiRouter); app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date(), auth: 'logto' }); }); // SPA fallback - must be last app.use((req, res, next) => { if (req.path.startsWith('/api')) { return next(); // Do not return index.html for API 404s } res.sendFile(path.join(distPath, 'index.html')); }); // Error handler // eslint-disable-next-line @typescript-eslint/no-unused-vars app.use((err: Error, req: express.Request, res: express.Response, _next: express.NextFunction) => { console.error('Express Error:', err); if (err.message === 'Not allowed by CORS') { return res.status(403).json({ error: 'CORS error' }); } res.status(500).json({ error: 'Internal server error' }); }); export default app;