9eb174dc8b
Run Tests on Branches / Detect Changes (push) Successful in 13s
Run Tests on Branches / Backend Tests (push) Has been skipped
Run Tests on Branches / Frontend Tests (push) Has been skipped
Run Tests on Branches / Frontend Mobile Tests (push) Has been skipped
Run Tests on Branches / PIP Platform Tests (push) Has been skipped
Run Tests on Branches / Parapharmacy API Tests (push) Failing after 7s
Swagger UI hardcoded localhost:3002, causing NetworkError when accessed from the deployed server. Now reads CORS_ORIGIN to build the correct server URL for API calls from the Swagger UI.
137 lines
3.5 KiB
JavaScript
137 lines
3.5 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: '10mb' }));
|
|
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
|
|
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);
|
|
});
|
|
|
|
start();
|
|
|
|
export default app;
|