WellNuo/backend/src/index.js
Sergei 7d9e7e37bf Remove console.log statements and add structured logging
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>
2026-01-29 11:58:06 -08:00

216 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
});