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 .envcommitted มี plaintext DB password และ SECRET_KEY ที่เป็น SSH pubkey fragment ไม่ใช่ JWT secret — เป็น broken crypto design- ทุก HRMI entity มี
SourceDBcolumn เช่นเดียวกับ 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 ที่ตรวจสอบ อาจมีสาเหตุได้หลายแบบ
- NestJS centralize-api ตัวนี้เป็น service ที่ยังไม่เสียบใช้งานจริงใน flow ที่ตรวจสอบ
- มี caller อื่นที่ไม่ใช่ vtrc-api (อาจเป็น direct admin tool ที่ไม่ได้เก็บใน repo ที่ตรวจสอบ)
- 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.1 | Repository map + dead code | ทำความรู้จักโครงสร้างไฟล์ พร้อมระบุ dead code |
| 3.3.2 | Bootstrap + TypeORM 2 connections | เข้าใจ boot sequence + การเชื่อม MSSQL 2 databases |
| 3.3.3 | REST API surface + DTO | เห็นภาพทั้ง 17 endpoints ในตารางเดียว |
| 3.3.4 | Service layer + SQL patterns | เข้าใจ raw SQL pattern + copy-paste CTE + NOLOCK |
| 3.3.5 | Authentication ที่ไม่ทำงาน | เห็นว่า guard ลงทะเบียนแล้วแต่ทุก route @Public() |
| 3.3.6 | Entities + SourceDB tenancy | รวบรวม 7 entities พร้อม SourceDB multi-tenant column |
| 3.3.7 | Cross-cutting + Swagger | logger, validation, filter, CORS, docs |
| 3.3.8 | บัญชีหนี้ทางเทคนิคด้านความปลอดภัย | 4 Critical + 7 High severity พร้อม remediation |
| 3.3.9 | Scorecard + 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,@1positional 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 | โค้ดปัจจุบัน |
|---|---|---|---|
| 1 | centralize-api/docs/document.config.ts | 4 addServer(), setDescription(''), addTag() ×4 | 2 addServer() (env-driven), setDescription('Centralize API'), ไม่มี addTag |
| 2 | centralize-api/Dockerfile | 3-stage (develop/build/production), yarn สลับ npm | 2-stage (develop/production), yarn ทั้งคู่, COPY --from=develop .../.env . ใน production stage |
| 3 | centralize-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 |
| 4 | centralize-api/libs/exception/http-exception.filter.ts | 46 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 ของตัวเอกสาร ไม่ใช่การเปลี่ยนแปลงของโค้ด