- API REST completa con Node.js + Express + TypeScript - Autenticación JWT con roles (Player/Admin) - CRUD completo de canchas - Sistema de reservas con validaciones - Base de datos SQLite con Prisma ORM - Notificaciones por email (Nodemailer) - Seed de datos de prueba - Documentación de arquitectura Endpoints implementados: - Auth: register, login, refresh, me - Courts: CRUD + disponibilidad - Bookings: CRUD + confirmación/cancelación Credenciales de prueba: - admin@padel.com / admin123 - user@padel.com / user123
117 lines
2.6 KiB
TypeScript
117 lines
2.6 KiB
TypeScript
import express from 'express';
|
|
import cors from 'cors';
|
|
import helmet from 'helmet';
|
|
import morgan from 'morgan';
|
|
import rateLimit from 'express-rate-limit';
|
|
import path from 'path';
|
|
|
|
import config from './config';
|
|
import logger from './config/logger';
|
|
import { connectDB } from './config/database';
|
|
import routes from './routes';
|
|
import { errorHandler, notFoundHandler } from './middleware/errorHandler';
|
|
|
|
const app = express();
|
|
|
|
// Crear directorio de logs si no existe
|
|
const fs = require('fs');
|
|
const logsDir = path.join(__dirname, '../logs');
|
|
if (!fs.existsSync(logsDir)) {
|
|
fs.mkdirSync(logsDir);
|
|
}
|
|
|
|
// Middleware de seguridad
|
|
app.use(helmet());
|
|
|
|
// CORS
|
|
app.use(cors({
|
|
origin: config.FRONTEND_URL,
|
|
credentials: true,
|
|
}));
|
|
|
|
// Rate limiting
|
|
const limiter = rateLimit({
|
|
windowMs: config.RATE_LIMIT.WINDOW_MS,
|
|
max: config.RATE_LIMIT.MAX_REQUESTS,
|
|
message: {
|
|
success: false,
|
|
message: 'Demasiadas peticiones, por favor intenta más tarde',
|
|
},
|
|
});
|
|
app.use('/api/', limiter);
|
|
|
|
// Logging HTTP
|
|
app.use(morgan('combined', {
|
|
stream: {
|
|
write: (message: string) => logger.info(message.trim()),
|
|
},
|
|
}));
|
|
|
|
// Parsing de body
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
// Rutas API
|
|
app.use('/api/v1', routes);
|
|
|
|
// Ruta raíz
|
|
app.get('/', (_req, res) => {
|
|
res.json({
|
|
success: true,
|
|
message: '🎾 API de Canchas de Pádel',
|
|
version: '1.0.0',
|
|
docs: '/api/v1/health',
|
|
});
|
|
});
|
|
|
|
// Handler de rutas no encontradas
|
|
app.use(notFoundHandler);
|
|
|
|
// Handler de errores global
|
|
app.use(errorHandler);
|
|
|
|
// Conectar a BD y iniciar servidor
|
|
const startServer = async () => {
|
|
try {
|
|
// Conectar a base de datos
|
|
await connectDB();
|
|
|
|
// Iniciar servidor
|
|
app.listen(config.PORT, () => {
|
|
logger.info(`🚀 Servidor corriendo en http://localhost:${config.PORT}`);
|
|
logger.info(`📚 API disponible en http://localhost:${config.PORT}/api/v1`);
|
|
logger.info(`🏥 Health check: http://localhost:${config.PORT}/api/v1/health`);
|
|
});
|
|
} catch (error) {
|
|
logger.error('Error al iniciar el servidor:', error);
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
// Manejo de errores no capturados
|
|
process.on('uncaughtException', (error) => {
|
|
logger.error('Uncaught Exception:', error);
|
|
process.exit(1);
|
|
});
|
|
|
|
process.on('unhandledRejection', (reason) => {
|
|
logger.error('Unhandled Rejection:', reason);
|
|
process.exit(1);
|
|
});
|
|
|
|
// Graceful shutdown
|
|
process.on('SIGTERM', async () => {
|
|
logger.info('SIGTERM recibido, cerrando servidor...');
|
|
process.exit(0);
|
|
});
|
|
|
|
process.on('SIGINT', async () => {
|
|
logger.info('SIGINT recibido, cerrando servidor...');
|
|
process.exit(0);
|
|
});
|
|
|
|
// Iniciar
|
|
startServer();
|
|
|
|
export default app;
|