Feature #231
Updated by Chakkaphon Noinang (Jay) 6 days ago
System (5xxx)
* [x] 5001 – masterdata (PIPE)
* [ ] 5002 – jobpost (GONG)
* [ ] 5003 – candidates (JAY)
* [ ] 5004 – users (PIPE)
* [ ] 5006 – jobapplication
* [ ] 5007 – batch
* [ ] 5011 – job-appointment
* [ ] 5012 – notification
* [ ] 5013 – report
# แก้ query FROM, JOIN เป็น app.{table} ถ้าเป็น user auth.{table}
# แก้ไฟล์ main.ts
```javascript
import { NestFactory, Reflector } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ResponseEnvelopeInterceptor } from "@shared/http/response-envelope.interceptor";
import { AllExceptionsFilter } from "@shared/http/all-exceptions.filter";
import { WINSTON_MODULE_NEST_PROVIDER } from "nest-winston";
import helmet from "helmet";
import { ValidationPipe, Logger, VersioningType } from "@nestjs/common";
import type { NestExpressApplication } from "@nestjs/platform-express";
import { AppEnv, ENV } from "../config/env";
import { Pool } from "pg";
export async function bootstrap(): Promise<void> {
let logger: Logger | null = null;
try {
// Test Database Connection First
await testDatabaseConnection();
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
bufferLogs: true,
});
app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));
logger = app.get<Logger>(WINSTON_MODULE_NEST_PROVIDER);
const { PORT, APP_NAME, NODE_ENV, ERROR_PREFIX, CORS_ORIGIN } = AppEnv;
const port: number = PORT;
const appName: string = APP_NAME;
const nodeEnv: string = NODE_ENV;
const errorPrefix: string = ERROR_PREFIX;
const corsOrigin: string = CORS_ORIGIN;
const enumNodeEnv: ENV = nodeEnv.toLowerCase() as ENV;
app.use(
helmet({
contentSecurityPolicy: enumNodeEnv === ENV.PROD ? undefined : false,
crossOriginEmbedderPolicy: enumNodeEnv === ENV.PROD,
}),
);
app.enableCors({
origin: corsOrigin,
credentials: true,
methods: ["GET", "POST", "OPTIONS"],
allowedHeaders: [
"Content-Type",
"Authorization",
"sender",
"refer",
"forward",
"sendDate",
"clientId",
"x-transaction-id",
"x-request-timestamp",
],
});
app.set("trust proxy", 1);
// Enable API versioning
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: "1",
prefix: "v",
});
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
}),
);
const reflector = app.get(Reflector);
app.useGlobalInterceptors(new ResponseEnvelopeInterceptor(reflector));
app.useGlobalFilters(new AllExceptionsFilter(errorPrefix));
app.enableShutdownHooks();
await app.listen(port, "0.0.0.0");
const appUrl = await app.getUrl();
logger.log("=".repeat(50), "Bootstrap");
logger.log(`Application: ${appName}`, "Bootstrap");
logger.log(`Environment: ${nodeEnv}`, "Bootstrap");
logger.log(`URL: ${appUrl}`, "Bootstrap");
logger.log(`Error Prefix: ${errorPrefix}`, "Bootstrap");
logger.log(`CORS Origin: ${corsOrigin}`, "Bootstrap");
logger.log(`Database: Connected ✓`, "Bootstrap");
logger.log(`Started at: ${new Date().toISOString()}`, "Bootstrap");
logger.log("=".repeat(50), "Bootstrap");
setupGracefulShutdown(app, logger);
} catch (error) {
if (logger) {
if (error instanceof Error) {
logger.error("Failed to start application", error.stack ?? "", {
error: error.message,
});
} else {
logger.error("Failed to start application", "", {
error: String(error),
});
}
} else if (error instanceof Error) {
// If logger is not available, use console.error
console.error("Failed to start application:", error.message);
} else {
console.error("Failed to start application:", String(error));
}
process.exit(1);
return; // Return after exit to prevent further execution in tests
}
}
export function setupGracefulShutdown(
app: NestExpressApplication,
logger: Logger,
): void {
const signals: NodeJS.Signals[] = ["SIGTERM", "SIGINT", "SIGUSR2"];
for (const signal of signals) {
process.on(signal, () => {
void (async () => {
logger.warn(
`Received ${signal}, starting graceful shutdown`,
"Shutdown",
);
try {
await app.close();
logger.log("Application closed successfully", "Shutdown");
process.exit(0);
} catch (error) {
if (error instanceof Error) {
logger.error(
"Error during shutdown",
error.stack ?? "",
"Shutdown",
);
} else {
logger.error("Error during shutdown", "", "Shutdown");
}
process.exit(1);
}
})();
});
}
process.on("uncaughtException", (error: Error) => {
logger.error("Uncaught Exception", error.stack ?? "", {
error: error.message,
});
process.exit(1);
});
process.on("unhandledRejection", (reason: unknown) => {
const errorStack = reason instanceof Error ? reason.stack : undefined;
const errorMessage =
reason instanceof Error ? reason.message : String(reason);
logger.error("Unhandled Promise Rejection", errorStack ?? "", {
reason: errorMessage,
});
process.exit(1);
});
}
async function testDatabaseConnection(): Promise<void> {
const dbConfig = {
host: AppEnv.DB_HOST,
port: AppEnv.DB_PORT,
user: AppEnv.DB_USERNAME,
password: AppEnv.DB_PASSWORD,
database: AppEnv.DB_NAME,
};
console.log("=".repeat(50));
console.log("[DB TEST] Testing Database Connection...");
console.log(` Host: ${dbConfig.host}`);
console.log(` Port: ${dbConfig.port}`);
console.log(` Database: ${dbConfig.database}`);
console.log(` User: ${dbConfig.user}`);
console.log("=".repeat(50));
let pool: Pool | null = null;
try {
pool = new Pool(dbConfig);
// Test connection
const client = await pool.connect();
// Run a simple query to verify connection
const result = await client.query<{
current_time: Date;
pg_version: string;
}>("SELECT NOW() as current_time, version() as pg_version");
console.log("[SUCCESS] Database Connection Successful!");
console.log(
` PostgreSQL Version: ${result.rows[0].pg_version.split(" ")[0] + " " + result.rows[0].pg_version.split(" ")[1]}`,
);
console.log(` Server Time: ${String(result.rows[0].current_time)}`);
console.log("=".repeat(50));
client.release();
} catch (error) {
console.error("=".repeat(50));
console.error("[ERROR] Database Connection Failed!");
console.error("=".repeat(50));
if (error instanceof Error) {
console.error(`Error Type: ${error.name}`);
console.error(`Error Message: ${error.message}`);
// Provide helpful error messages based on error type
if (error.message.includes("ECONNREFUSED")) {
console.error("\n[INFO] Possible Causes:");
console.error(" - Database server is not running");
console.error(" - Wrong host or port");
console.error(" - Firewall blocking connection");
} else if (error.message.includes("password authentication failed")) {
console.error("\n[INFO] Possible Causes:");
console.error(" - Wrong username or password");
console.error(" - User does not have access to the database");
} else if (error.message.includes("does not exist")) {
console.error("\n[INFO] Possible Causes:");
console.error(" - Database does not exist");
console.error(" - Wrong database name in configuration");
} else if (error.message.includes("timeout")) {
console.error("\n[INFO] Possible Causes:");
console.error(" - Network connectivity issues");
console.error(" - Database server is overloaded");
console.error(" - Connection timeout settings too low");
}
console.error("\n[CONFIG] Configuration:");
console.error(` DB_HOST: ${dbConfig.host}`);
console.error(` DB_PORT: ${dbConfig.port}`);
console.error(` DB_NAME: ${dbConfig.database}`);
console.error(` DB_USERNAME: ${dbConfig.user}`);
console.error("=".repeat(50));
}
throw error; // Re-throw to stop application startup
} finally {
// Clean up the test pool
if (pool) {
await pool.end();
}
}
}
// Only run bootstrap when not in test environment
/* istanbul ignore next */
if (
process.env.NODE_ENV !== "test" &&
process.env.JEST_WORKER_ID === undefined
) {
void bootstrap(); // NOSONAR - Cannot use top-level await in CommonJS module
}
```