require('dotenv').config(); const { SECURITY, SERVER, CRON } = require('./config/constants'); // ============ SECURITY VALIDATION ============ // Validate JWT_SECRET at startup if (!process.env.JWT_SECRET || process.env.JWT_SECRET.length < SECURITY.JWT_MIN_SECRET_LENGTH) { console.error(`JWT_SECRET must be at least ${SECURITY.JWT_MIN_SECRET_LENGTH} characters!`); process.exit(1); } const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const path = require('path'); const cron = require('node-cron'); const apiRouter = require('./routes/api'); const authRouter = require('./routes/auth'); const beneficiariesRouter = require('./routes/beneficiaries'); const deploymentsRouter = require('./routes/deployments'); const invitationsRouter = require('./routes/invitations'); const pushTokensRouter = require('./routes/push-tokens'); const notificationSettingsRouter = require('./routes/notification-settings'); const ordersRouter = require('./routes/orders'); const stripeRouter = require('./routes/stripe'); const webhookRouter = require('./routes/webhook'); const adminRouter = require('./routes/admin'); const mqttRouter = require('./routes/mqtt'); const { syncAllSubscriptions } = require('./services/subscription-sync'); const mqttService = require('./services/mqtt'); const logger = require('./utils/logger'); const app = express(); const PORT = process.env.PORT || SERVER.DEFAULT_PORT; // Trust proxy for correct IP when behind nginx app.set('trust proxy', SERVER.TRUST_PROXY_LEVEL); // ============ SECURITY ============ // Helmet - добавляет security headers app.use(helmet({ contentSecurityPolicy: false // отключаем для admin panel })); // CORS - разрешаем только наши домены const allowedOrigins = [ 'https://wellnuo.smartlaunchhub.com', 'https://wellnuo.com', 'http://localhost:3000', 'http://localhost:8081', // Expo dev 'exp://192.168.1.*' // Expo local ]; app.use(cors({ origin: function(origin, callback) { // Разрешаем запросы без origin (mobile apps, Postman) if (!origin) return callback(null, true); // Проверяем разрешённые домены if (allowedOrigins.some(allowed => { if (allowed.includes('*')) { const regex = new RegExp(allowed.replace('*', '.*')); return regex.test(origin); } return allowed === origin; })) { return callback(null, true); } callback(new Error('Not allowed by CORS')); }, credentials: true })); // Rate Limiting - защита от DDoS const limiter = rateLimit({ windowMs: SECURITY.RATE_LIMIT.WINDOW_MS, max: SECURITY.RATE_LIMIT.MAX_REQUESTS, message: { error: 'Too many requests, please try again later' }, standardHeaders: true, legacyHeaders: false }); // Лимит для auth endpoints const authLimiter = rateLimit({ windowMs: SECURITY.RATE_LIMIT.WINDOW_MS, max: SECURITY.RATE_LIMIT.AUTH_MAX_REQUESTS, message: { error: 'Too many login attempts, please try again later' }, // Пропускаем админов без лимита skip: (req) => { const adminEmails = ['serter2069@gmail.com', 'ezmrzli@gmail.com', 'apple@zmrinc.com']; return adminEmails.includes(req.body?.email?.toLowerCase()); } }); // Применяем rate limiting app.use(limiter); app.use('/api/auth', authLimiter); // Auth endpoints app.use('/function/well-api/api', authLimiter); // Legacy API с auth // Stripe webhooks need raw body for signature verification // Must be before express.json() app.use('/api/webhook/stripe', express.raw({ type: 'application/json' })); // JSON body parser for other routes // Increased limit for base64 avatar uploads app.use(express.json({ limit: SERVER.JSON_BODY_LIMIT })); app.use(express.urlencoded({ extended: true, limit: SERVER.JSON_BODY_LIMIT })); // ============ ROUTES ============ // Legacy API route - matches old API structure // POST /function/well-api/api with function parameter app.use('/function/well-api/api', apiRouter); // New REST API routes app.use('/api/auth', authRouter); app.use('/api/me/beneficiaries', beneficiariesRouter); app.use('/api/me', deploymentsRouter); // Deployment routes app.use('/api/invitations', invitationsRouter); app.use('/api/push-tokens', pushTokensRouter); app.use('/api/notification-settings', notificationSettingsRouter); app.use('/api/orders', ordersRouter); app.use('/api/stripe', stripeRouter); app.use('/api/webhook', webhookRouter); app.use('/api/admin', adminRouter); app.use('/api/mqtt', mqttRouter); // Admin UI app.get('/admin', (req, res) => { res.sendFile(path.join(__dirname, 'admin', 'index.html')); }); // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString(), stripe: process.env.STRIPE_SECRET_KEY ? 'configured' : 'missing' }); }); // API info app.get('/api', (req, res) => { res.json({ name: 'WellNuo API', version: '2.0.0', endpoints: { auth: '/api/auth', beneficiaries: '/api/me/beneficiaries', deployments: '/api/me/beneficiaries/:id/deployments', invitations: '/api/invitations', pushTokens: '/api/push-tokens', notificationSettings: '/api/notification-settings', orders: '/api/orders', stripe: '/api/stripe', webhook: '/api/webhook/stripe', admin: '/api/admin', mqtt: '/api/mqtt', legacy: '/function/well-api/api' } }); }); // ============ CRON JOBS ============ // Sync subscriptions from Stripe every hour cron.schedule(CRON.SYNC_CRON_SCHEDULE, async () => { logger.info('CRON', 'Running subscription sync...'); const result = await syncAllSubscriptions(); logger.info('CRON', 'Subscription sync result', result); }); // Run sync on startup (after delay to let everything initialize) setTimeout(async () => { logger.info('STARTUP', 'Running initial subscription sync...'); const result = await syncAllSubscriptions(); logger.info('STARTUP', 'Initial sync result', result); }, SERVER.INITIAL_SYNC_DELAY_MS); // Manual sync endpoint (for admin) app.post('/api/admin/sync-subscriptions', async (req, res) => { logger.info('ADMIN', 'Manual subscription sync requested'); const result = await syncAllSubscriptions(); res.json(result); }); app.listen(PORT, () => { logger.info('SERVER', `WellNuo API running on port ${PORT}`); logger.info('SERVER', `Stripe: ${process.env.STRIPE_SECRET_KEY ? 'configured' : 'missing'}`); // Initialize MQTT connection mqttService.init(); // Subscribe to ALL active deployments from database setTimeout(async () => { const deployments = await mqttService.subscribeToAllDeployments(); logger.info('MQTT', `Subscribed to ${deployments.length} deployments`, { deployments }); }, SERVER.MQTT_SUBSCRIBE_DELAY_MS); }); // Graceful shutdown process.on('SIGTERM', () => { logger.info('SERVER', 'SIGTERM received, shutting down gracefully...'); mqttService.shutdown(); process.exit(0); }); process.on('SIGINT', () => { logger.info('SERVER', 'SIGINT received, shutting down gracefully...'); mqttService.shutdown(); process.exit(0); });