File size: 3,842 Bytes
94ad3aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b6ee67e
 
 
bce154c
b6ee67e
 
 
bce154c
 
 
 
 
714ef9d
737a6e6
b6ee67e
 
c918afd
b6ee67e
 
4a8f3ab
b6ee67e
d28338a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b6ee67e
714ef9d
 
 
bce154c
 
 
714ef9d
bce154c
 
 
 
 
b6ee67e
 
 
 
bce154c
 
 
 
 
714ef9d
737a6e6
b6ee67e
c918afd
 
 
dbdc105
c918afd
ff0376a
c918afd
dbdc105
 
 
b6ee67e
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
 * Aplicacion Express principal — configuracion de middlewares y montaje de rutas.
 *
 * Middlewares aplicados (en orden):
 *   1. helmet()            — headers de seguridad (X-Frame-Options, HSTS, etc.).
 *   2. cors()              — CORS con origen configurable (CORS_ORIGIN).
 *   3. rateLimit()         — 200 peticiones / 15 min por IP.
 *   4. express.json()      — parseo de JSON con limite de 1 MB.
 *
 * Rutas REST montadas bajo /api/v1:
 *   - /auth       → login, perfil (auth.routes.js)
 *   - /markets    → listado y detalle de mercados (markets.routes.js)
 *   - /markets    → senales IA por mercado (signals.routes.js, subruta)
 *   - /positions  → simulador de posiciones virtuales (positions.routes.js)
 *   - /watchlist  → lista de seguimiento (watchlist.routes.js)
 *   - /alerts     → historial de alertas (alerts.routes.js)
 *   - /health     → healthcheck basico
 *
 * Manejo de errores:
 *   - notFound      → 404 para rutas no definidas.
 *   - errorHandler  → 500 generico en produccion, detalles en desarrollo.
 */

import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import { config } from './config.js';
import { ok } from './utils/apiResponse.js';
import authRoutes from './auth/auth.routes.js';
import marketsRoutes from './markets/markets.routes.js';
import signalsRoutes from './signals/signals.routes.js';
import positionsRoutes from './positions/positions.routes.js';
import watchlistRoutes from './watchlist/watchlist.routes.js';
import alertsRoutes from './alerts/alerts.routes.js';
import statsRoutes from './stats/stats.routes.js';
import preferencesRoutes from './preferences/preferences.routes.js';
import { notFound } from './middlewares/notFound.js';
import { errorHandler } from './middlewares/errorHandler.js';
import { existsSync } from 'node:fs';

const app = express();
app.set('trust proxy', 1);

app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        baseUri: ["'self'"],
        fontSrc: ["'self'", 'https:', 'data:'],
        formAction: ["'self'"],
        frameAncestors: ["'self'"],
        imgSrc: ["'self'", 'data:', 'https:'],
        objectSrc: ["'none'"],
        scriptSrc: ["'self'"],
        scriptSrcAttr: ["'none'"],
        styleSrc: ["'self'", 'https:', "'unsafe-inline'"],
        upgradeInsecureRequests: [],
      },
    },
  }),
);
app.use(cors({ origin: config.CORS_ORIGIN, credentials: true }));

// Rate limit: muy permisivo en desarrollo, restrictivo en producción
const rateLimitMax = config.NODE_ENV === 'production' ? 200 : 5000;
app.use(
  rateLimit({
    windowMs: 15 * 60 * 1000,
    max: rateLimitMax,
    standardHeaders: true,
    legacyHeaders: false,
    message: { ok: false, error: { code: 'TOO_MANY_REQUESTS', message: 'Rate limit exceeded' } },
  }),
);
app.use(express.json({ limit: '1mb' }));

app.get('/api/v1/health', (_req, res) => ok(res, { status: 'up' }));
app.use('/api/v1/auth', authRoutes);
app.use('/api/v1/markets', marketsRoutes);
app.use('/api/v1/markets', signalsRoutes);
app.use('/api/v1/positions', positionsRoutes);
app.use('/api/v1/watchlist', watchlistRoutes);
app.use('/api/v1/alerts', alertsRoutes);
app.use('/api/v1/stats', statsRoutes);
app.use('/api/v1/preferences', preferencesRoutes);

// Servir frontend estático en producción (HuggingFace Spaces / Docker)
// Detecta si estamos en la raíz del proyecto o dentro de backend/
const frontendDist = existsSync('../frontend/dist') ? '../frontend/dist' : 'frontend/dist';
if (config.NODE_ENV === 'production') {
  app.use(express.static(frontendDist));
  app.get(/.*/, (_req, res) => {
    res.sendFile('index.html', { root: frontendDist });
  });
}

app.use(notFound);
app.use(errorHandler);

export default app;