Project

General

Profile

Feature #231

Updated by Chakkaphon Noinang (Jay) 6 days ago

System (5xxx) 

 * [x] 5001 – masterdata (PIPE) 
 * [ ] 5002 – jobpost (GONG) 
 * [ ] 5003 – candidates (JAY) 
 * [x] 5004 – users (PIPE) 
 * [ ] 5006 – jobapplication (GONG) 
 * [x] [ ] 5007 – batch (JAY) 
 * [ ] 5011 – job-appointment (PIPE) 
 * [ ] 5012 – notification (GONG) 
 * [ ] 5013 – report (JAY) 

 # แก้ 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 
 } 
 ``` 

Back