Spaces:
Sleeping
Sleeping
| 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; | |