/**
* @module app
*/
import { env } from "@config/env";
import { globalLogger } from "@config/logger";
import { ChatService } from "@services/chat.service";
import { asyncLocalStorage, getLogger } from "@utils/asyncLocalStorage";
import { initializeVault, closeVault } from "@vault/index";
import cors from "cors";
import express from "express";
import helmet from "helmet";
import { createServer } from "http";
import { ObjectId } from "mongodb";
import morgan from "morgan";
import { randomUUID } from "node:crypto";
import { Server } from "socket.io";
import { initializeDatabases, closeDatabases } from "./database";
import { errorHandler, notFoundHandler } from "./middlewares/errorHandler";
import { requestIdMiddleware } from "./middlewares/requestId";
const logger = getLogger();
/**
* Create the Express application.
* @returns {Promise<Application>} The Express application.
*/
export async function createApp() {
const { investigationRoutes } = await import("@routes/investigation.routes");
const { healthRoutes } = await import("@routes/health.routes");
logger.info("Building application modules...");
const app = express();
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
}));
app.use(cors({
origin: "*",
credentials: false,
allowedHeaders: ["Content-Type", "Authorization", "x-request-id"],
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
}));
app.use(requestIdMiddleware);
morgan.token("request-id", (req) => {
const extReq = req;
const bindings = extReq.logger?.bindings?.();
return bindings?.requestId || "no-request-id";
});
app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" [requestId: :request-id]', {
stream: {
write: (message) => {
logger.info(message.trim());
},
},
}));
app.use(express.json({ limit: "10mb" }));
// API Routes
app.use("/api/health", healthRoutes);
app.use("/api/investigation", investigationRoutes);
// 404 handler
app.use(notFoundHandler);
app.use(errorHandler);
logger.info("Application modules configured successfully");
return app;
}
/**
* Start the application server.
* @returns {Promise<void>} A promise that resolves when the server is started.
*/
export async function startApp() {
logger.info("Starting Backend Core...");
try {
await initializeDatabases();
try {
await initializeVault();
}
catch (error) {
logger.error({ error }, "Error initializing Vault");
}
const app = await createApp();
const server = app.listen(env.PORT, () => {
logger.info({
port: env.PORT,
environment: env.NODE_ENV,
nodeVersion: process.version,
}, "Server started successfully");
});
const httpServer = createServer();
/* Socket.io server */
const io = new Server(httpServer, {
cors: {
origin: "*",
},
});
// Socket error handler
io.on("error", (error) => {
logger.error({
error: error instanceof Error ? error.message : "Unknown error",
}, "Socket error occurred");
});
io.on("connection", (socket) => {
try {
const connectionId = randomUUID();
const connectionLogger = globalLogger.child({ connectionId, socketId: socket.id });
connectionLogger.info(`Client connected: ${socket.id}`);
const investigationId = socket.handshake.query.investigationId;
const userId = socket.handshake.query.userId;
connectionLogger.info(`USER ID: ${userId}`);
if (!userId || typeof userId !== "string" || !ObjectId.isValid(userId)) {
connectionLogger.debug({
message: "User ID is invalid.",
userId,
});
socket.emit("accept", { message: "User ID is invalid.", error: true });
return;
}
const userObjectId = new ObjectId(userId);
let chatService;
if (investigationId) {
const investigationObjectId = new ObjectId(investigationId);
chatService = new ChatService(investigationObjectId, userObjectId);
}
else {
chatService = new ChatService(null, userObjectId);
}
void asyncLocalStorage.run({ logger: connectionLogger, requestId: connectionId }, async () => {
try {
const startConversationResult = await chatService.startConversation();
socket.emit("accept", startConversationResult);
}
catch (error) {
const logger = getLogger();
logger.error({ error }, "Error starting conversation");
socket.emit("accept", { message: "Error in socket connection.", error: true });
}
});
socket.on("message", (data) => {
void asyncLocalStorage.run({ logger: connectionLogger, requestId: connectionId }, async () => {
try {
if (!data.message) {
socket.emit("accept", { message: "Message must be exists.", error: true });
return;
}
const response = await chatService.continueConversation(data);
socket.emit("accept", response);
}
catch (error) {
const logger = getLogger();
logger.error({ error }, "Error processing message");
socket.emit("accept", { message: "Error processing message.", error: true });
}
});
});
socket.on("disconnect", () => {
connectionLogger.info(`Client disconnected: ${socket.id}`);
});
}
catch (error) {
logger.error({ error }, "Error in socket connection");
socket.emit("accept", { message: "Error in socket connection.", error: true });
return;
}
});
/* Start the server */
httpServer.listen(env.SOCKET_PORT, () => {
logger.info(`WebSocket server running at ws://localhost:${env.SOCKET_PORT}/`);
});
const gracefulShutdown = async (signal) => {
logger.info(`Received ${signal}. Starting graceful shutdown...`);
const forceShutdown = setTimeout(() => {
logger.error("Could not close connections in time, forcefully shutting down");
process.exit(1);
}, 10000);
try {
server.close(() => {
logger.info("HTTP server closed");
});
closeVault();
await closeDatabases();
clearTimeout(forceShutdown);
logger.info("Graceful shutdown completed");
process.exit(0);
}
catch (error) {
logger.error({ error }, "Error during graceful shutdown");
clearTimeout(forceShutdown);
process.exit(1);
}
};
const handleSignal = (signal) => {
gracefulShutdown(signal).catch((error) => {
logger.error({ error, signal }, "Unhandled error in signal handler");
process.exit(1);
});
};
process.on("SIGTERM", () => handleSignal("SIGTERM"));
process.on("SIGINT", () => handleSignal("SIGINT"));
}
catch (error) {
logger.fatal({
error: error instanceof Error ? error.message : "Unknown error",
}, "Failed to start application");
try {
closeVault();
await closeDatabases();
}
catch (closeError) {
logger.error({ closeError }, "Error closing services during startup failure");
}
process.exit(1);
}
}
Source