- Full grant strategy framework for renewable energy & green hydrogen - AI-powered grant studio, partner outreach, financial modeling - Umami analytics with data-performance tracking - Live Degelas metrics connected to solar.degelas.be - Trilingual (EN/FR/AR) with i18n support - Dockerized with Nginx frontend + Express API proxy
81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
import express from "express";
|
|
import cors from "cors";
|
|
import helmet from "helmet";
|
|
import "dotenv/config";
|
|
import { validateEnv, getEnv } from "./lib/env";
|
|
import { generateRoute } from "./routes/generate";
|
|
import { degelasMetricsRoute } from "./routes/degelas";
|
|
import { vaultSaveRoute, vaultDocsRoute } from "./routes/vault";
|
|
import { apiLimiter, healthLimiter } from "./middleware/rateLimit";
|
|
import { requestLogger } from "./middleware/logger";
|
|
import { errorHandler, notFoundHandler } from "./middleware/errorHandler";
|
|
|
|
// Validate environment on startup
|
|
validateEnv();
|
|
const { PORT, CLIENT_ORIGIN } = getEnv();
|
|
|
|
const app = express();
|
|
|
|
// ── Security Middleware ───────────────────────────────────────────────────────
|
|
app.use(helmet({
|
|
contentSecurityPolicy: {
|
|
directives: {
|
|
defaultSrc: ["'self'"],
|
|
scriptSrc: ["'self'", "'unsafe-inline'", "blob:"],
|
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
imgSrc: ["'self'", "data:", "blob:"],
|
|
connectSrc: ["'self'"],
|
|
fontSrc: ["'self'"],
|
|
objectSrc: ["'none'"],
|
|
mediaSrc: ["'self'"],
|
|
frameSrc: ["'none'"],
|
|
},
|
|
},
|
|
crossOriginEmbedderPolicy: false, // Needed for some frontend features
|
|
}));
|
|
|
|
// CORS with validated origin
|
|
app.use(cors({
|
|
origin: CLIENT_ORIGIN === "*" ? true : CLIENT_ORIGIN.split(",").map((o) => o.trim()),
|
|
methods: ["GET", "POST"],
|
|
allowedHeaders: ["Content-Type", "X-Request-ID"],
|
|
exposedHeaders: ["X-Request-ID"],
|
|
credentials: false,
|
|
maxAge: 600, // 10 minutes
|
|
}));
|
|
|
|
// Request logging
|
|
app.use(requestLogger);
|
|
|
|
// Body parser with size limit
|
|
app.use(express.json({ limit: "1mb" }));
|
|
app.use(express.urlencoded({ extended: true, limit: "1mb" }));
|
|
|
|
// ── Routes ────────────────────────────────────────────────────────────────────
|
|
// Health check (high rate limit)
|
|
app.get("/api/health", healthLimiter, (_req, res) => {
|
|
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
|
});
|
|
|
|
// AI generation (rate limited)
|
|
app.post("/api/ai/generate", apiLimiter, generateRoute);
|
|
|
|
// Degelas metrics (rate limited)
|
|
app.get("/api/degelas/metrics", apiLimiter, degelasMetricsRoute);
|
|
|
|
// Vault persistence
|
|
app.post("/api/vault/save", apiLimiter, vaultSaveRoute);
|
|
app.get("/api/vault/docs", apiLimiter, vaultDocsRoute);
|
|
|
|
// 404 handler
|
|
app.use(notFoundHandler);
|
|
|
|
// Global error handler
|
|
app.use(errorHandler);
|
|
|
|
// ── Start Server ──────────────────────────────────────────────────────────────
|
|
app.listen(PORT, () => {
|
|
console.log(`✅ Atlas Green API proxy running on port ${PORT}`);
|
|
console.log(` Environment: ${process.env.NODE_ENV || "development"}`);
|
|
});
|