WellNuo/backend/src/index.js
Sergei e74d1a4b26 Show user role under beneficiary name
- Added role field to Beneficiary type
- Display role (Custodian/Guardian/Caretaker) in small gray text under name
- Role comes from user_access table via API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-09 19:08:12 -08:00

175 lines
5.6 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 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 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 { syncAllSubscriptions } = require('./services/subscription-sync');
const app = express();
const PORT = process.env.PORT || 3000;
// Trust proxy for correct IP when behind nginx
app.set('trust proxy', 1);
// ============ 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: 15 * 60 * 1000, // 15 минут
max: 100, // максимум 100 запросов за 15 минут
message: { error: 'Too many requests, please try again later' },
standardHeaders: true,
legacyHeaders: false
});
// Лимит для auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 30, // 30 попыток логина за 15 минут
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
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// ============ 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/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);
// 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',
invitations: '/api/invitations',
pushTokens: '/api/push-tokens',
notificationSettings: '/api/notification-settings',
orders: '/api/orders',
stripe: '/api/stripe',
webhook: '/api/webhook/stripe',
admin: '/api/admin',
legacy: '/function/well-api/api'
}
});
});
// ============ CRON JOBS ============
// Sync subscriptions from Stripe every hour
cron.schedule('0 * * * *', async () => {
console.log('[CRON] Running subscription sync...');
const result = await syncAllSubscriptions();
console.log('[CRON] Subscription sync result:', result);
});
// Run sync on startup (after 10 seconds to let everything initialize)
setTimeout(async () => {
console.log('[STARTUP] Running initial subscription sync...');
const result = await syncAllSubscriptions();
console.log('[STARTUP] Initial sync result:', result);
}, 10000);
// Manual sync endpoint (for admin)
app.post('/api/admin/sync-subscriptions', async (req, res) => {
console.log('[ADMIN] Manual subscription sync requested');
const result = await syncAllSubscriptions();
res.json(result);
});
app.listen(PORT, () => {
console.log(`WellNuo API running on port ${PORT}`);
console.log(`Stripe: ${process.env.STRIPE_SECRET_KEY ? '✓ configured' : '✗ missing'}`);
});