/** * server.js — API-only точка входа (ветка api-v1). * * Обслуживает только /api/v1/* (Bearer JWT, JSON). * UI — отдельный слой, работает через API. * * Зависимости: * src/auth.js — initAuth (валидация Bearer) * src/queries.js — все SQL-запросы * src/api/ — REST API router */ 'use strict'; const express = require('express'); const helmet = require('helmet'); require('dotenv').config(); const { checkConnection } = require('./src/db'); const { initAuth } = require('./src/auth'); const q = require('./src/queries'); const { createApiRouter } = require('./src/api/index'); const app = express(); const PORT = process.env.PORT || 3000; // Безопасные HTTP-заголовки (без CSP браузерного — API не отдаёт HTML) app.use(helmet({ contentSecurityPolicy: false })); // За reverse-proxy (nginx/k8s ingress) if (process.env.NODE_ENV === 'production') app.set('trust proxy', 1); async function start() { const auth = await initAuth(); // k8s liveness probe app.get('/healthz', (req, res) => res.send('OK')); // JWKS endpoint (только в mock-режиме, для тестов) if (auth.jwksHandler) app.get('/.well-known/jwks.json', auth.jwksHandler); // REST API v1 app.use('/api/v1', createApiRouter({ auth, q })); // 404 для всего остального app.use((req, res) => res.status(404).json({ error: 'Not found' })); // Общий error handler // eslint-disable-next-line no-unused-vars app.use((err, req, res, next) => { console.error('Unhandled error:', err); res.status(500).json({ error: 'Internal server error' }); }); checkConnection() .then(() => console.log('DB connected')) .catch(e => console.error('DB not ready:', e.message)); return app; } if (require.main === module) { start() .then(a => a.listen(PORT, () => console.log('Server on port ' + PORT))) .catch(e => { console.error('Startup error:', e); process.exit(1); }); } module.exports = { start };