feat: stabilize API startup and lifecycle management

- Refactor server initialization to separate concerns and improve error handling.
- Implement centralized environment validation using Zod.
- Introduce database, Redis, and queue lifecycle management.
- Add health check endpoints for liveness and readiness.
- Enhance error handling middleware for better response structure.
- Implement rate limiting for API endpoints.
- Add request ID middleware for traceability.
- Create Sequelize CLI configuration and baseline migration for schema management.
- Establish CI workflow with Gitea for testing and syntax checks.
- Document foundational changes and migration strategy in PHASE_0_FOUNDATION_STABILIZATION.md.
- Add Docker Compose configuration for local development and testing.
- Implement unit and integration tests for critical functionality.
This commit is contained in:
Sathira Sri Sathara
2026-09-03 13:33:26 +05:30
parent 624fce31c5
commit 267e80e2ec
40 changed files with 1682 additions and 261 deletions
+42 -72
View File
@@ -1,99 +1,69 @@
/**
* Copyright (c) 2026 Niolla
* All rights reserved.
*
* This source code is proprietary and confidential.
* Unauthorized copying, modification, distribution, or use
* of this file, via any medium, is strictly prohibited.
*/
// app.js
const express = require("express");
const cors = require("cors");
const helmet = require("helmet");
const morgan = require("morgan");
const routes = require("./app/routes");
const cookieParser = require("cookie-parser");
const { bullBoardRouter } = require("./app/config/bullBoard.config");
const path = require("path");
const startAllCrons = require("./cron");
const db = require("./app/models");
// Test DB connection and sync models
(async () => {
try {
await db.sequelize.authenticate();
console.log("Database connected.");
await db.sequelize.sync();
console.log("Tables synced.");
// Start all cron jobs
startAllCrons();
} catch (error) {
console.error("DB error:", error);
}
})();
const routes = require("./app/routes");
const { healthRouter, live } = require("./app/routes/health.routes");
const { bullBoardRouter } = require("./app/config/bullBoard.config");
const { authenticate } = require("./app/middleware/auth.middleware");
const { authorizedAccountType } = require("./app/middleware/permission.middleware");
const requestId = require("./app/middleware/requestId.middleware");
const { generalApiLimiter } = require("./app/middleware/rateLimit.middleware");
const { notFound, errorHandler } = require("./app/middleware/error.middleware");
const app = express();
const trustProxy = Number(process.env.TRUST_PROXY || 0);
if (trustProxy > 0) app.set("trust proxy", trustProxy);
app.disable("x-powered-by");
app.use(requestId);
app.use(helmet({
contentSecurityPolicy: false,
hsts: process.env.NODE_ENV === "production" ? undefined : false,
}));
app.use(cookieParser());
// CORS configuration
const corsOptions = {
origin: process.env.FRONTEND_URL || "https://oceanic-demo.vercel.app",
app.use(cors({
origin: process.env.FRONTEND_URL,
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization"],
allowedHeaders: ["Content-Type", "Authorization", "X-Request-ID"],
exposedHeaders: ["X-Request-ID"],
credentials: true,
};
app.use(cors(corsOptions));
app.options(/.*/, cors(corsOptions));
}));
app.use(express.json({ limit: process.env.JSON_BODY_LIMIT || "1mb" }));
app.use(express.urlencoded({ extended: false, limit: process.env.JSON_BODY_LIMIT || "1mb" }));
app.use(express.json());
app.use(morgan("dev"));
morgan.token("request-id", (req) => req.id);
app.use(morgan(process.env.NODE_ENV === "production" ? ':remote-addr - :method :url :status :response-time ms req-id=:request-id' : "dev"));
app.get("/health", (req, res) => {
let dbStatus = "N/A";
let emailStatus = "N/A";
let redisStatus = "N/A";
app.get("/health", live);
app.use("/health", healthRouter);
db.sequelize
.authenticate()
.then(() => {
console.log("DB connection successful.");
dbStatus = "OK";
})
.catch((err) => {
console.error("DB connection error:", err);
res.status(500).send("Internal Server Error");
});
// Keep the legacy path while all new clients migrate to the versioned path.
app.use("/api", generalApiLimiter, routes);
app.use("/api/v1", generalApiLimiter, routes);
emailStatus = "OK";
redisStatus = "OK";
res.send({
status: "Online ✅",
database: dbStatus,
emailService: emailStatus,
redis: redisStatus,
});
});
app.use("/api", routes);
app.use(
"/Documentation",
(req, res, next) => {
if (req.path.endsWith(".md")) {
return res.status(403).send("Forbidden");
}
next();
},
(req, res, next) => req.path.endsWith(".md") ? res.status(403).send("Forbidden") : next(),
express.static(path.join(__dirname, "Documentation")),
);
app.use("/admin/queues", bullBoardRouter);
app.use(
"/admin/queues",
authenticate,
authorizedAccountType(["admin", "superadmin"]),
bullBoardRouter,
);
app.use(notFound);
app.use(errorHandler);
module.exports = app;