Created a centralized logger utility (src/utils/logger.js) that provides: - Structured logging with context labels - Log levels (ERROR, WARN, INFO, DEBUG) - Environment-based log level control via LOG_LEVEL env variable - Consistent timestamp and JSON data formatting Removed console.log/error/warn statements from: - All service files (mqtt, notifications, legacyAPI, email, storage, subscription-sync) - All route handlers (auth, beneficiaries, deployments, webhook, admin, etc) - Controllers (dashboard, auth, alarm) - Database connection handler - Main server file (index.js) Preserved: - Critical startup validation error for JWT_SECRET in index.js Benefits: - Production-ready logging that can be easily integrated with log aggregation services - Reduced noise in production logs - Easier debugging with structured context and data - Configurable log levels per environment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
216 lines
7.1 KiB
JavaScript
216 lines
7.1 KiB
JavaScript
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);
|
||
});
|