const dotenv = require('dotenv'); const parsed = dotenv.config().parsed; if (parsed) { for (const key in parsed) { process.env[key] = parsed[key]; } } // ── Startup Validations ────────────────────────────────────────────────────── if (!process.env.JWT_SECRET) { console.warn('⚠️ WARNING: JWT_SECRET is not set in .env — authentication will fail. Set it before using auth routes.'); } const express = require('express'); const cors = require('cors'); const connectDB = require('./config/db'); const authRoutes = require('./routes/authRoutes'); const contractRoutes = require('./routes/contractRoutes'); const paymentRoutes = require('./routes/paymentRoutes'); const feedbackRoutes = require('./routes/feedbackRoutes'); const adminRoutes = require('./routes/adminRoutes'); const ApiError = require('./utils/ApiError'); const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const User = require('./models/User'); const app = express(); app.set('trust proxy', 1); const PORT = process.env.PORT || 7860; // ── Database ───────────────────────────────────────────────────────────────── connectDB(); // ── Middleware ──────────────────────────────────────────────────────────────── app.use(helmet({ frameguard: false, contentSecurityPolicy: false, })); app.use(rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 1000, // limit each IP to 1000 requests per windowMs handler: (req, res) => { res.status(429).json({ success: false, message: 'Too many requests, please try again later.', errors: [] }); } })); const allowedOrigins = [ 'http://localhost:5173', 'http://localhost:5174', 'https://lexguard.vercel.app', 'https://adity251105-lexguard-backend.hf.space', process.env.FRONTEND_URL, // Allow custom override via env ].filter(Boolean); app.use(cors({ origin: (origin, cb) => { // Allow requests with no origin (server-to-server, curl, mobile apps) if (!origin || allowedOrigins.includes(origin)) return cb(null, true); cb(null, false); }, credentials: true })); app.use(express.json({ limit: '11mb' })); app.use(express.urlencoded({ extended: true, limit: '11mb' })); // ── Routes ──────────────────────────────────────────────────────────────────── app.use('/api/auth', authRoutes); app.use('/api/contracts', contractRoutes); app.use('/api/payments', paymentRoutes); app.use('/api/feedback', feedbackRoutes); app.use('/api/admin', adminRoutes); // Root landing endpoint to prevent 404 in Hugging Face Space iframe app.get('/', (_req, res) => { res.status(200).json({ status: 'ok', message: 'Welcome to LexGuard API backend. Use /health for service health status.', documentation: 'https://github.com/Aditya2514/LexGuard', }); }); // Health check endpoint app.get('/health', (_req, res) => { res.status(200).json({ status: 'ok', service: 'LexGuard API', version: '1.0.0', phase: 'Phase 1 – Core Backend & Parsing', }); }); // ── 404 Handler ─────────────────────────────────────────────────────────────── app.use((req, res) => { res.status(404).json({ success: false, message: `Route ${req.method} ${req.originalUrl} not found.`, }); }); // ── Global Error Handler ────────────────────────────────────────────────────── // eslint-disable-next-line no-unused-vars app.use((err, _req, res, _next) => { // Multer file size exceeded if (err.code === 'LIMIT_FILE_SIZE') { return res.status(413).json({ success: false, message: 'File too large. Maximum allowed upload size is 10 MB.', }); } // Multer fileFilter rejection (plain Error with statusCode set in fileFilter) if (err.statusCode && !(err instanceof ApiError)) { return res.status(err.statusCode).json({ success: false, message: err.message, }); } // Known application errors if (err instanceof ApiError) { return res.status(err.statusCode).json({ success: false, message: err.message, errors: err.errors, }); } // Mongoose: invalid ObjectId format in URL params (e.g. /api/contracts/bad-id) if (err.name === 'CastError') { return res.status(400).json({ success: false, message: `Invalid ID format: "${err.value}" is not a valid resource ID.`, }); } // Mongoose: buffering timed out — DB is unreachable if (err.name === 'MongooseError' && err.message.includes('buffering timed out')) { return res.status(503).json({ success: false, message: 'Database is currently unavailable. Please try again shortly.', }); } // Mongoose validation errors (e.g. required field missing) if (err.name === 'ValidationError') { const messages = Object.values(err.errors).map((e) => e.message); return res.status(400).json({ success: false, message: 'Validation failed.', errors: messages, }); } // Unknown/unexpected errors — log stack for debugging, hide details from client console.error('Unhandled error:', err); return res.status(500).json({ success: false, message: 'An unexpected internal error occurred. Please try again later.', }); }); // ── Start Server ────────────────────────────────────────────────────────────── if (!process.env.VERCEL) { app.listen(PORT, () => { console.log(`🚀 LexGuard API running at http://localhost:${PORT}`); console.log(`📋 Health check: http://localhost:${PORT}/health`); // Start background job queue processor const jobQueueService = require('./services/jobQueueService'); jobQueueService.startQueueWorker(); // Removed warmEmbeddingCache as it is no longer used // Daily quota reset cron (runs every hour) setInterval(async () => { try { const now = new Date(); const result = await User.updateMany( { quotaResetDate: { $lte: now } }, { usedThisMonth: 0, quotaResetDate: new Date(now.getTime() + 30 * 86400000) } ); if (result.modifiedCount > 0) { console.log(`[Cron] Reset quotas for ${result.modifiedCount} users.`); } } catch (err) { console.error('[Cron] Error resetting quotas:', err); } }, 60 * 60 * 1000); }); } module.exports = app;