Skip to content

3.3 · centralize-api Deep Dive

Volume นี้เจาะลึก centralize-api — service ตัวเดียวในกลุ่ม Legacy Core ที่ใช้ TypeScript + NestJS 9 + TypeORM 0.3 + MSSQL หน้าที่หลักคือเป็น ชั้นห่อหุ้มสำหรับอ่าน HR master data (HR data facade) จาก HRMI MSSQL โดยตรง และเปิด REST API พร้อม Swagger documentation ที่บริการอื่นในกลุ่มไม่มี


ทำไมต้องมี Volume นี้

ตาม audit/02-architecture-map.md centralize-api ถูก label เป็น "NestJS Central HR Data Service" แต่ label นั้นดูเหมือนไม่มีปัญหา จนกระทั่งลองอ่าน code จริงจะพบว่ารายละเอียดที่ซ่อนอยู่มีมาก

  • เป็น service ตัวเดียวที่ read-only 100% — ไม่มี INSERT/UPDATE/DELETE ใน code เลย ไม่มี mutation endpoint แต่ใช้ raw SQL string ทั้งระบบ TypeORM ใช้แค่ connection management ไม่ได้ใช้ query builder
  • ระบบ authentication ลงทะเบียนเป็น global guard แต่ถูก bypass ทุก route เพราะทุก controller ใช้ @Public() — auth machinery ทั้งหมดจึงเป็น dead code ใน production
  • .env committed มี plaintext DB password และ SECRET_KEY ที่เป็น SSH pubkey fragment ไม่ใช่ JWT secret — เป็น broken crypto design
  • ทุก HRMI entity มี SourceDB column เช่นเดียวกับ cu-central-api บ่งชี้ว่าเป็น multi-tenant pattern เดียวกัน
  • มี 17 endpoints ใน 4 controllers (Employee 14 GET + App 1 GET + PaySlip 1 POST + TimeTemp 1 POST) แต่ naming inconsistent (posManage, posTechnical, getByEmpCode, getEmployeeDisaster, bankAccount) และมี dead code จาก NestJS starter template ค้างอยู่หลายจุด

ถ้า debug เรื่อง "ทำไม employee ของ org X ไม่ return" หรือ "ทำไม time attendance ของ NBC group หาย" ต้องเริ่มที่ Volume นี้ เพราะเป็นทางผ่านของ HR master data ทั้งหมด


ขอบเขตของ Volume

centralize-api/
├── .env                          committed มี prod/UAT creds (3.3.2 + 3.3.8)
├── package.json                  NestJS 9, TypeORM 0.3, mssql 9 (3.3.1)
├── tsconfig.json                 strictNullChecks: false (3.3.1)
├── Dockerfile                    yarn install ทั้งที่เป็น npm repo (3.3.1)
├── config/configuration.ts       env var loader ไม่มี Joi (3.3.2)
├── docs/document.config.ts       Swagger config (3.3.7)
├── libs/
│   ├── jwt/                      guard + strategy ที่ไม่ทำงาน (3.3.5)
│   ├── exception/                filter จับแค่ HttpException (3.3.7)
│   ├── log/                      custom logger ไม่มี rotation (3.3.7)
│   └── common/                   validator ที่ไม่ได้ใช้ (3.3.1)
└── src/
    ├── main.ts                   bootstrap + globals (3.3.2)
    ├── app.module.ts             root module + 2 TypeORM connections (3.3.2)
    ├── app.service.ts            org hierarchy CTE ~130 lines (3.3.4)
    ├── dto/                      5 DTOs + 1 dead CreateCatDto (3.3.3)
    ├── entities/                 7 entities (6 live + 1 dead UserEntity) (3.3.6)
    └── modules/
        ├── employee/             14 GET endpoints — core (3.3.3 + 3.3.4)
        ├── hrPaySlip/            1 POST — payslip lookup (3.3.3)
        ├── hrTimeTemp/           1 POST — time attendance merge (3.3.3 + 3.3.4)
        ├── employeeGroup/        sub-module ไม่มี controller (3.3.6)
        ├── employeeLevel/        sub-module ไม่มี controller (3.3.6)
        └── technicalPosition/    sub-module ไม่มี controller (3.3.6)

ขนาดรวม ~2,243 LOC ใน 44 ไฟล์ TypeScript — เล็กมากเมื่อเทียบกับ vtrc-api (~25,800 LOC) แต่มีรายละเอียดที่ต้องเข้าใจก่อนแตะ code


ใครควรอ่าน

  • Engineer ที่แก้ centralize-api — อ่านตามลำดับ 3.3.1 → 3.3.9 ทั้งหมด เพราะระบบเล็กและเชื่อมโยงกันหมด
  • Engineer ที่ debug เรื่อง HR master data ผิด — ข้ามไป 3.3.4 (service layer + SQL) กับ 3.3.6 (entities + SourceDB)
  • Engineer ที่จะปรับปรุงระบบ — อ่าน 3.3.8 (security debt) กับ 3.3.9 (scorecard + ownership) ก่อน ทั้งสองบทระบุจุดที่ต้อง refactor
  • Security reviewer — อ่าน 3.3.5 (auth bypass) กับ 3.3.8 (security debt) ก่อน เพราะทั้งสองบทระบุช่องโหว่ที่ยังไม่ได้รับการแก้ไขในปัจจุบัน

ข้อควรระวังสำคัญ — ที่มาของ caller ใน production ยังไม่ยืนยัน

การสำรวจ code ใน vtrc-api/api/src/ พบว่า END_POINT_CENTRALIZE ชี้ไป GraphQL ของ cu-central-api (CLIENT_TARGET ใน .env.example เป็น http://localhost:8082/api/graphql) — ไม่ใช่ NestJS centralize-api ตัวนี้ที่รัน port 3000

vtrc-api/.env.example:36    END_POINT_CENTRALIZE="http://localhost:8082/api/graphql"
vtrc-api/api/src/centralize/controllers/graphql.js:21   new GraphQLClient(END_POINT_CENTRALIZE, ...)
vtrc-api/api/src/centralize/controllers/auth.js:28      ${END_POINT_CENTRALIZE_AUTH}/signin

ผลคือ ไม่พบ code ใน vtrc-api ที่ยิงไป /api/employee, /api/hrPaySlip, /api/hrTimeTemp ของ NestJS centralize-api โดยตรง ในระดับ source code ที่ตรวจสอบ อาจมีสาเหตุได้หลายแบบ

  1. NestJS centralize-api ตัวนี้เป็น service ที่ยังไม่เสียบใช้งานจริงใน flow ที่ตรวจสอบ
  2. มี caller อื่นที่ไม่ใช่ vtrc-api (อาจเป็น direct admin tool ที่ไม่ได้เก็บใน repo ที่ตรวจสอบ)
  3. env ใน .env.example เป็น CLIENT_TARGET ตัวอย่าง — ไม่ใช่ CODE_DEFAULT ของ cu-central และไม่ใช่ NestJS port 3000

Volume นี้จึงเขียนตามที่ code evidence บอก และ flag จุดนี้ไว้ใน 3.3.9 (Scorecard + ownership) พร้อมคำแนะนำในการ verify เพิ่มเติมก่อนตัดสินใจ refactor


สิ่งที่ Volume นี้ไม่ cover

  • HRMI MSSQL schema ทั้งหมด — cover ใน Volume 8 (Data Model & Persistence) โดยเฉพาะบท 8.1 (Persistence topology) และ 8.9 (Identity across databases) เพราะ schema ของ HRMI เป็นของระบบ CU/RC ไม่ใช่ของ VTRC
  • vtrc-api → cu-central-api bridge mechanics — cover ใน Volume 3.1.4 (The centralize bridge) เพราะ bridge logic อยู่ฝั่ง vtrc-api (โฟลเดอร์ชื่อ centralize/ แต่ปลายทางคือ cu-central-api ไม่ใช่ NestJS service นี้)
  • cu-central-api เปรียบเทียบเชิงลึก — cover ใน Volume 3.2 ทั้ง volume

โครงสร้างบท

บทชื่อจุดประสงค์
3.3.1Repository map + dead codeทำความรู้จักโครงสร้างไฟล์ พร้อมระบุ dead code
3.3.2Bootstrap + TypeORM 2 connectionsเข้าใจ boot sequence + การเชื่อม MSSQL 2 databases
3.3.3REST API surface + DTOเห็นภาพทั้ง 17 endpoints ในตารางเดียว
3.3.4Service layer + SQL patternsเข้าใจ raw SQL pattern + copy-paste CTE + NOLOCK
3.3.5Authentication ที่ไม่ทำงานเห็นว่า guard ลงทะเบียนแล้วแต่ทุก route @Public()
3.3.6Entities + SourceDB tenancyรวบรวม 7 entities พร้อม SourceDB multi-tenant column
3.3.7Cross-cutting + Swaggerlogger, validation, filter, CORS, docs
3.3.8บัญชีหนี้ทางเทคนิคด้านความปลอดภัย4 Critical + 7 High severity พร้อม remediation
3.3.9Scorecard + ownership + comparisonสรุป + เปรียบเทียบกับ vtrc-api/cu-central-api

สิ่งที่ควรทราบก่อนอ่าน

  • NestJS 9 — module/controller/service pattern, DI, decorator-based metadata (@Module, @Injectable, @Get, @Public)
  • TypeORM 0.3@Entity, @Column, EntityManager.query(), BaseEntity แบบ active record
  • MSSQL + tedious driver@0, @1 positional parameter binding, WITH (NOLOCK) hint, cross-database query syntax (dbHRMI_Center.dbo.tableName)
  • Swagger / OpenAPI@ApiTags, @ApiBearerAuth, @ApiOperation, DocumentBuilder

ถ้าไม่คุ้นกับ NestJS แนะนำอ่าน NestJS overview ส่วน dependency injection ก่อน เพราะ centralize-api ใช้ pattern นี้ทั้งระบบ


การเปลี่ยนแปลงจาก v1 และ code drift ที่พบ

ทุก citation (versions ใน centralize-api/package.json, logic ใน centralize-api/libs/jwt/jwt-auth.guard.ts, TypeORM config ใน centralize-api/src/app.module.ts, CORS config ใน centralize-api/src/main.ts, ขนาด .env) verify ใหม่กับโค้ดปัจจุบัน (commit a7aca60, 2026-07-13) — คงที่ตลอด ไม่พบ code drift ในจุดที่ v1 ยกเป็นหลักฐานด้าน security

พบจุดที่ v1 เอกสารเก่ากว่าโค้ดปัจจุบัน 4 จุด (รายละเอียดในบทที่เกี่ยวข้อง)

#ไฟล์v1 claimโค้ดปัจจุบัน
1centralize-api/docs/document.config.ts4 addServer(), setDescription(''), addTag() ×42 addServer() (env-driven), setDescription('Centralize API'), ไม่มี addTag
2centralize-api/Dockerfile3-stage (develop/build/production), yarn สลับ npm2-stage (develop/production), yarn ทั้งคู่, COPY --from=develop .../.env . ใน production stage
3centralize-api/src/dto/hrTimeTemp.dto.ts@IsDateString({}, { each: true }), no empCode field, ShiftTimeType/ResponseGetShiftTimeWithWork เป็น class, timeIn/timeOut เป็น Date@IsDateString(null, { each: true }), มี empCode: string required, สองตัวนั้นเป็น interface, timeIn/timeOut เป็น string
4centralize-api/libs/exception/http-exception.filter.ts46 LOC, return { statusCode, timestamp, path, method }26 LOC, return { statusCode, message } เท่านั้น

การเปลี่ยนแปลงเชิงนโยบายเอกสาร — v1 quote ค่าจริงของ DB_PASSWORD และ SECRET_KEY ตรงๆ ในตัวเอกสาร v2 อ้างอิงแค่ file:line และอธิบายลักษณะของปัญหา (plaintext password, SSH pubkey fragment) โดยไม่ paste ค่าจริง เป็นการยกระดับ security hygiene ของตัวเอกสาร ไม่ใช่การเปลี่ยนแปลงของโค้ด