Files
FarmaFinder/apps/parapharmacy-api/src/server.js
T
Antoni Nuñez Romeu 849763896d
Run Tests on Branches / Detect Changes (push) Successful in 12s
Run Tests on Branches / Frontend Tests (push) Successful in 2m12s
Run Tests on Branches / Frontend Mobile Tests (push) Has been skipped
Run Tests on Branches / Parapharmacy API Tests (push) Successful in 2m2s
Run Tests on Branches / PIP Platform Tests (push) Has been skipped
Run Tests on Branches / Backend Tests (push) Successful in 2m8s
security: harden production configuration and routes
2026-07-22 17:24:54 +02:00

139 lines
3.6 KiB
JavaScript

import express from 'express';
import cors from 'cors';
import mongoose from 'mongoose';
import morgan from 'morgan';
import rateLimit from 'express-rate-limit';
import swaggerJsdoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';
import { config } from './config.js';
import productsRouter from './routes/products.js';
import scraperRouter from './routes/scraper.js';
const app = express();
// Swagger configuration
const swaggerServerUrl = process.env.CORS_ORIGIN
? process.env.CORS_ORIGIN.replace(/\/$/, '') // strip trailing slash
: `http://localhost:${config.port}`;
const swaggerOptions = {
definition: {
openapi: '3.0.0',
info: {
title: 'FarmaFinder Parapharmacy API',
version: '1.0.0',
description: 'API for parapharmacy products from multiple Spanish pharmacies',
},
servers: [
{
url: swaggerServerUrl,
description: process.env.CORS_ORIGIN ? 'Production server' : 'Development server',
},
],
components: {
schemas: {
Product: {
type: 'object',
properties: {
_id: { type: 'string' },
name: { type: 'string' },
brand: { type: 'string' },
category: { type: 'string' },
price: { type: 'number' },
source: { type: 'string' },
},
},
},
},
},
apis: ['./src/routes/*.js'],
};
const swaggerSpec = swaggerJsdoc(swaggerOptions);
// Middleware
app.use(cors(config.cors));
app.use(express.json({ limit: '1mb' }));
app.use(morgan('combined'));
// Rate limiting
const limiter = rateLimit({
windowMs: config.rateLimit.windowMs,
max: config.rateLimit.max,
standardHeaders: true,
legacyHeaders: false,
});
app.use(limiter);
// Swagger UI
if (!['production', 'test'].includes(process.env.NODE_ENV)) {
app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
explorer: true,
customCss: '.swagger-ui .topbar { display: none }',
customSiteTitle: 'FarmaFinder Parapharmacy API',
}));
}
// Routes
app.use('/api/products', productsRouter);
app.use('/api', scraperRouter);
// Health check
app.get('/api/health', (req, res) => {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
mongodb: mongoose.connection.readyState === 1 ? 'connected' : 'disconnected',
});
});
// Sources info
app.get('/api/sources', (req, res) => {
res.json(config.sources);
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error('Unhandled error:', err);
res.status(500).json({ error: 'Internal server error' });
});
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Not found' });
});
// Connect to MongoDB and start server
async function start() {
try {
console.log(`[Parapharmacy API] Connecting to MongoDB: ${config.mongodb.uri}`);
await mongoose.connect(config.mongodb.uri);
console.log('[Parapharmacy API] Connected to MongoDB');
app.listen(config.port, () => {
console.log(`[Parapharmacy API] Server running on port ${config.port}`);
console.log(`[Parapharmacy API] Swagger docs: http://localhost:${config.port}/api/docs`);
});
} catch (error) {
console.error('[Parapharmacy API] Failed to start:', error);
process.exit(1);
}
}
// Handle graceful shutdown
process.on('SIGTERM', async () => {
console.log('[Parapharmacy API] SIGTERM received, shutting down...');
await mongoose.disconnect();
process.exit(0);
});
process.on('SIGINT', async () => {
console.log('[Parapharmacy API] SIGINT received, shutting down...');
await mongoose.disconnect();
process.exit(0);
});
if (process.env.NODE_ENV !== 'test') start();
export default app;