267e80e2ec
- 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.
82 lines
2.6 KiB
JavaScript
82 lines
2.6 KiB
JavaScript
/**
|
|
* Copyright (c) 2026 Niolla
|
|
* All rights reserved.
|
|
*/
|
|
|
|
require("dotenv").config();
|
|
const { validateEnvironment } = require("./app/config/env.config");
|
|
|
|
const bootstrap = async () => {
|
|
const env = validateEnvironment();
|
|
const { initializeDatabase, closeDatabase } = require("./app/config/database.lifecycle");
|
|
const { initializeRedis, closeRedis } = require("./app/config/redis.lifecycle");
|
|
try {
|
|
await initializeDatabase();
|
|
console.log("Database connection ready.");
|
|
await initializeRedis();
|
|
console.log("Redis connection ready.");
|
|
} catch (error) {
|
|
await Promise.allSettled([closeRedis(), closeDatabase()]);
|
|
throw error;
|
|
}
|
|
|
|
const { closeQueues } = require("./app/config/queue.lifecycle");
|
|
const startAllCrons = require("./cron");
|
|
const app = require("./app");
|
|
|
|
let stopCrons = async () => {};
|
|
if (env.RUN_CRON) stopCrons = startAllCrons();
|
|
|
|
const server = await new Promise((resolve, reject) => {
|
|
const listener = app.listen(env.PORT, "0.0.0.0", () => resolve(listener));
|
|
listener.once("error", reject);
|
|
});
|
|
console.log(`ZUMRI API listening on port ${env.PORT}.`);
|
|
|
|
let shuttingDown = false;
|
|
const shutdown = async (reason, exitCode = 0) => {
|
|
if (shuttingDown) return;
|
|
shuttingDown = true;
|
|
console.log(`Shutting down (${reason}).`);
|
|
const forceTimer = setTimeout(() => {
|
|
console.error("Graceful shutdown timed out.");
|
|
process.exit(1);
|
|
}, env.SHUTDOWN_TIMEOUT_MS);
|
|
forceTimer.unref();
|
|
try {
|
|
await new Promise((resolve) => server.close(resolve));
|
|
await stopCrons();
|
|
await closeQueues();
|
|
await closeRedis();
|
|
await closeDatabase();
|
|
clearTimeout(forceTimer);
|
|
process.exit(exitCode);
|
|
} catch (error) {
|
|
console.error("Shutdown failed", { name: error.name, message: error.message });
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
process.once("SIGTERM", () => shutdown("SIGTERM"));
|
|
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
process.once("unhandledRejection", (reason) => {
|
|
const error = reason instanceof Error ? reason : new Error("Unhandled rejection");
|
|
console.error("Unhandled rejection", { name: error.name, message: error.message });
|
|
shutdown("unhandledRejection", 1);
|
|
});
|
|
process.once("uncaughtException", (error) => {
|
|
console.error("Uncaught exception", { name: error.name, message: error.message });
|
|
shutdown("uncaughtException", 1);
|
|
});
|
|
return { server, shutdown };
|
|
};
|
|
|
|
if (require.main === module) {
|
|
bootstrap().catch((error) => {
|
|
console.error("API startup failed", { name: error.name, message: error.message });
|
|
process.exitCode = 1;
|
|
});
|
|
}
|
|
|
|
module.exports = { bootstrap };
|