1 · Repository map — benefit-api
บทนี้เป็นแผนที่ระดับสูงของ repository benefit-api/ ครอบคลุม tech stack โครงสร้างโฟลเดอร์ ตัวเลขสถิติของไฟล์ entry-point trace และ auth strategy ที่ใช้ทั่ว repo เพื่อให้บทถัดไป (core pipeline, persistence, integrations) สามารถอ้างชื่อ module/entity/ guard ได้โดยไม่ต้องอธิบายซ้ำ
Tech stack
benefit-api เป็น NestJS 9 service ที่ใช้ TypeORM เป็น ORM ตัวเดียว (ไม่มี Mongoose ในกระบวนการผลิตจริง — ระบบทุก connection ที่พบใน benefit-api/src/app.module.ts (branch: prod):40-57 เป็น TypeORM connection ทั้งหมด) ภาษาคือ TypeScript 4.7 ทำงานบน Node.js 20 (ดู benefit-api/Dockerfile (branch: prod):1) และเปิด 3 database connection พร้อมกัน ได้แก่ main (schema benefit บน SQL Server), vtrc (schema ของ legacy core ซึ่งชี้ไปยัง MariaDB หรือ MSSQL ตาม env), และ hrmi (SQL Server เก็บ HR master data)
package ที่เกี่ยวข้องกับการทำงานหลักใน benefit-api/package.json (branch: prod):26-61 คือกลุ่ม NestJS core (@nestjs/common, @nestjs/core, @nestjs/platform-express ที่ v9), กลุ่ม config (@nestjs/config, @nestjs/swagger 6.1, @nestjs/terminus 9.2), กลุ่ม ORM/database (@nestjs/typeorm 9, typeorm 0.3, mssql 9, mysql 2), กลุ่ม auth (@nestjs/passport 9, passport 0.6, passport-jwt 4, passport-local 1), กลุ่ม scheduler (@nestjs/schedule 2.2), กลุ่ม HTTP outbound (@nestjs/axios 2, axios 1.4), และกลุ่ม utility เฉพาะงาน (puppeteer 19 สำหรับ render PDF, exceljs 4 สำหรับ export Excel, ejs 3 สำหรับ template, ssh2-sftp-client 10 และ sftp-upload 1 สำหรับส่งไฟล์ไป FMIS, adm-zip, iconv-lite, moment, uuid)
ชื่อ package จริงใน benefit-api/package.json (branch: prod):2 คือ vtrc-restfull-v2 ไม่ใช่ benefit-api — ถ้า grep หา package name ใน CI/CD config จะไม่เจอคำว่า benefit-api เลย
Branch และสถิติไฟล์
repo อยู่บน canonical branch prod ตามที่ระบุใน workspace rule โดยมีไฟล์ tracked ทั้งหมด 172 ไฟล์ เมื่อนับเฉพาะ .ts ภายใต้ src/, libs/, config/, docs/, test/ จะได้ 127 ไฟล์ ส่วนที่เหลือคือ yarn.lock, Dockerfile, docker-compose.yml, nest-cli.json, tsconfig.json, package.json, .env, .eslintrc.js, bitbucket-pipelines.yml, sql/ (DDL scripts), uploads/ (sample files), และ docs เมื่อแบ่งตามโฟลเดอร์ภายใต้ src/ จะได้สัดส่วนประมาณนี้
| โฟลเดอร์ | จำนวนไฟล์ | บทบาท |
|---|---|---|
src/modules/ | 14 module × ~3 ไฟล์ (controller + service + module) = ~42 ไฟล์ | feature module ตาม Nest convention |
src/entities/ | 28 entity | TypeORM entity ต่อตารางใน schema benefit |
src/dto/ | 18 DTO | class-validator DTO ที่ใช้กับ controller |
src/crontab/ | 7 ไฟล์ | node-cron job (ผ่าน @nestjs/schedule) |
libs/ | 19 ไฟล์ | shared utility, guard, filter, logger, PDF template |
config/ | 1 ไฟล์ | env var → TypeORM options |
docs/ | 1 ไฟล์ | Swagger setup |
โครงสร้างโฟลเดอร์
benefit-api/
├── src/
│ ├── main.ts bootstrap ของ Nest app, global prefix, static uploads, global guards/filters/pipes
│ ├── app.module.ts root module — ประกาศ 3 TypeORM connection + import ทุก feature module
│ ├── app.controller.ts health/root controller
│ ├── app.service.ts
│ ├── dto/ 18 DTO ไฟล์ — class-validator decorator ต่อ module
│ ├── entities/ 28 TypeORM entity — ต่อตารางใน schema `benefit`
│ ├── crontab/ node-cron ผ่าน @nestjs/schedule
│ │ ├── cronTab.module.ts
│ │ ├── cronTab.service.ts ประกาศ @Cron ทั้งหมดของ repo (2 jobs)
│ │ ├── funeral/ funeral.task.service.ts — import/update ข้อมูลฌาปนกิจจาก HRMI
│ │ └── inheritance/ inheritance.task.service.ts — import/update มรดกตกทอด
│ └── modules/ 14 feature module (Nest convention: controller + service + module)
│ ├── wdhospitals/ เบิกค่ารักษาพยาบาล (WdHospitals) — endpoint หลักเป็น @Public()
│ ├── disaster/ เบิกเงินช่วยเหลือภัยพิบัติ (WfDisasterHD)
│ ├── approveDisaster/ อนุมัติเงินภัยพิบัติ + สร้าง payment transaction + เรียก centralize
│ ├── payments/ จัดการ paymentTransaction ทุกประเภท (docType 1-5) — ใหญ่สุดเป็นอันดับ 2
│ ├── disbursement/ เบิกสวัสดิการอื่น (other welfare) + cash payment
│ ├── exportPaymentReport/ export ไฟล์จ่ายเงินไป FMIS ผ่าน SFTP (ไฟล์ใหญ่สุดใน repo, ~7,740 บรรทัด)
│ ├── studyLeave/ ค่าธรรมเนียมการศึกษา (docType 3)
│ ├── setupPayor/ master data ผู้จ่ายเงิน
│ ├── chartOfAccount/ Chart of Account master data
│ ├── dropdown/ dropdown/master lookup กลาง (mDropdown table)
│ ├── uploadFiles/ เก็บไฟล์ base64 ลง local disk (ไม่ผ่าน vtrc-common)
│ ├── export-payments/ export payment list (เล็ก, มี spec)
│ └── batchJob/ manual trigger สำหรับ cron jobs (ops เรียกย้อนหลัง)
├── libs/
│ ├── jwt/ jwt.strategy.ts + jwt-auth.guard.ts (global guard + @Public() override)
│ ├── log/ logs.middleware.ts — I3GatewayLogger (custom logger wrapper)
│ ├── exception/ http-exception.filter.ts — global exception filter
│ ├── utils/ welfare.util.ts, lists.util.ts, running.util.ts, dropdown.util.ts, check-emp-wd.util.ts
│ ├── common/ base64-to-file.ts, remove-file.ts, paginate.ts, validator-date.ts
│ └── pdfTemplate/ ejs templates สำหรับ FMIS report + payment slip (puppeteer render เป็น PDF)
├── config/configuration.ts env var → TypeORM options (3 connections) + business constant (status labels)
├── docs/document.config.ts Swagger document config
├── sql/ DDL scripts สำหรับ schema `benefit`
├── uploads/ ไฟล์ที่ผู้ใช้ upload จริง — มี PNG ตัวอย่าง commit ติดมาด้วย
└── Dockerfile node:20-alpine + chromium (สำหรับ puppeteer)Dependencies ที่สำคัญ
ส่วนนี้สรุป dependency ที่โหลดใน app.module.ts และใช้จริงใน service layer อ่านแบบเต็มได้ใน benefit-api/package.json (branch: prod):26-61
NestJS modules ที่ลงทะเบียนใน root module (benefit-api/src/app.module.ts (branch: prod):33-75) คือ ConfigModule.forRoot (global, load factory configuration), TypeOrmModule.forRootAsync × 3 (ชื่อ connection main, vtrc, hrmi), ScheduleModule.forRoot, HttpModule ของ @nestjs/axios, TerminusModule สำหรับ health check, และ feature module 14 ตัว
ORM library ที่ใช้คือ typeorm 0.3 ผ่าน @nestjs/typeorm 9 — service ทุกตัว inject EntityManager ผ่าน decorator @InjectEntityManager('main' | 'vtrc' | 'hrmi') และเรียก method find, findOne, save, update, transaction, createQueryBuilder, และ query (raw SQL string) ตามต้องการ ไม่มีการสร้าง @InjectRepository pattern แยกต่างหากเพราะทุก module ใช้ shared entity manager ที่ inject ใน service constructor
auth library คือ @nestjs/passport + passport-jwt โดยมี JwtStrategy (benefit-api/libs/jwt/jwt.strategy.ts (branch: prod):7-23) เป็น strategy ที่ verify JWT ด้วย secretOrKey จาก env SECRET_KEY และมี JwtAuthGuard (benefit-api/libs/jwt/jwt-auth.guard.ts (branch: prod):6-37) เป็น global guard ที่ถูกเปิดใช้ใน main.ts (ดู benefit-api/src/main.ts (branch: prod):32) — ทุก route จะถูก guard อัตโนมัติยกเว้นมี decorator @Public() ติดอยู่
integration library ที่ใช้ติดต่อ service อื่นมี 3 ตัวคือ axios (สำหรับ HTTP call ไป centralize-api, vtrc-api, vtrc-common), ssh2-sftp-client และ sftp-upload (สำหรับส่งไฟล์ export ไป FMIS) และ puppeteer (สำหรับ render PDF slip/report)
สถิติที่ควรจำ
| ตัวเลข | ค่า | ที่มา |
|---|---|---|
| Tracked files (branch prod) | 172 | git ls-files | wc -l |
| TypeScript files (src + libs + config + docs + test) | 127 | find src libs config docs test -type f -name "*.ts" |
| Feature module | 14 | benefit-api/src/app.module.ts (branch: prod):58-74 |
| TypeORM entity | 28 | benefit-api/src/entities/ |
| DTO ไฟล์ | 18 | benefit-api/src/dto/ |
| TypeORM connection | 3 (main, vtrc, hrmi) | benefit-api/src/app.module.ts (branch: prod):40-57 |
| Cron job ที่ประกาศจริง | 2 (importDataFromHRMI, updateDataToHRMI) | benefit-api/src/crontab/cronTab.service.ts (branch: prod):22,37 |
| ไฟล์ใหญ่สุดเป็นบรรทัด | exportpaymentreport.service.ts (~7,740 บรรทัด) | benefit-api/src/modules/exportPaymentReport/exportpaymentreport.service.ts (branch: prod) |
| ไฟล์ใหญ่อันดับ 2 | payments.service.ts (1,450 บรรทัด) | benefit-api/src/modules/payments/payments.service.ts (branch: prod) |
| Default listen port | 9320 (override ได้ด้วย env PORT) | benefit-api/src/main.ts (branch: prod):21 และ benefit-api/Dockerfile (branch: prod):28 |
Entry point trace
main.ts เป็น bootstrap หลักของ Nest และกำหนด global middleware ทั้งหมด ลำดับการทำงานมีดังนี้
NestFactory.create<NestExpressApplication>(AppModule, { logger: new I3GatewayLogger('System') })สร้าง app พร้อม custom logger (benefit-api/src/main.ts (branch: prod):15-17) — ใช้NestExpressApplicationเพื่อเข้าถึงuseStaticAssetsสำหรับโฟลเดอร์uploads/ConfigModule.forRoot({ load: [configuration], isGlobal: true })ที่ประกาศในAppModule(benefit-api/src/app.module.ts (branch: prod):36-39) โหลดconfiguration()factory จากconfig/configuration.tsซึ่งแปลง env var ทั้งหมดเป็น TypeORM options object ภายใต้ keydb,dbHRMI,vtrcDBและเก็บค่า scalar เช่นport,jwtSecretKey,centralizeServer,vtrcServerไว้ใน ConfigService (benefit-api/config/configuration.ts (branch: prod):3-80)TypeOrmModule.forRootAsync× 3 ในAppModule(benefit-api/src/app.module.ts (branch: prod):40-57) เปิด connectionmain,vtrc,hrmiโดยใช้useFactoryที่อ่านconfigService.get('db' | 'vtrcDB' | 'dbHRMI')— ทุก connection ตั้งlogging: ['error', 'query']ซึ่งหมายความว่าทุก SQL query จะถูก log ออก stdout ในทุก environmentScheduleModule.forRoot()(benefit-api/src/app.module.ts (branch: prod):67) เปิดใช้@Crondecorator เพื่อให้CronTabService.importDataFromHRMIและupdateDataToHRMIทำงานตาม schedule (benefit-api/src/crontab/cronTab.service.ts (branch: prod):22,37)app.setGlobalPrefix(\${process.env.BASE_PATH}api/v1`)(benefit-api/src/main.ts (branch: prod):25) กำหนด prefix ของทุก route — ค่าจริงใน prod คือ/benefit-api/api/v1/...`app.useStaticAssets(join(process.cwd(), '/uploads'), { prefix: '/benefit-api/' })(benefit-api/src/main.ts (branch: prod):26-28) เปิด static file serving สำหรับไฟล์ที่UploadServiceเขียนลง diskapp.useGlobalFilters(new HttpExceptionFilter())(benefit-api/src/main.ts (branch: prod):31) ติดตั้ง global exception filter ที่ catch เฉพาะHttpExceptionแล้ว reformat response เป็น{ statusCode, message }(benefit-api/libs/exception/http-exception.filter.ts (branch: prod):8-21)app.useGlobalGuards(new JwtAuthGuard(reflector))(benefit-api/src/main.ts (branch: prod):32) บังคับให้ทุก route ผ่าน JWT validation ก่อน เว้นแต่มี@Public()บน method/classapp.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }))(benefit-api/src/main.ts (branch: prod):33) strip field ที่ไม่อยู่ใน DTO class ออก และ reject request ที่ส่ง field เกินมาapp.enableCors({ origin: '*', allowedHeaders: '*', methods: 'GET,PUT,POST,DELETE,PATCH,OPTIONS' })(benefit-api/src/main.ts (branch: prod):34-38) เปิด CORS สำหรับทุก origin (permissive)SwaggerModule.setup(benefit-api/src/main.ts (branch: prod):40-41) เปิด Swagger UI ที่${BASE_PATH}docsapp.listen(port, '0.0.0.0')(benefit-api/src/main.ts (branch: prod):42) start HTTP server บน port จาก envPORT(default 3000 ใน code, แต่ DockerfileEXPOSE 9320จึงเท่ากับ 9320 จริง)
Auth strategy ที่ใช้ทั่ว repo
รูปแบบ auth ของ benefit-api คือ global JWT guard + @Public() opt-out ซึ่งเป็น pattern เดียวกับ centralize-api และ NestJS service อื่น ๆ ในวงการ vtrc ไม่ใช่ pattern แบบ vtrc-api ที่ใช้ @auth(accessRole, accessMenu) directive ฝั่ง GraphQL
การไหลของ auth มีดังนี้
- request มาถึง route ใด ๆ —
JwtAuthGuard.canActivate(benefit-api/libs/jwt/jwt-auth.guard.ts (branch: prod):15-24) เช็คก่อนว่า method/class มี metadataisPublic: true(ที่ตั้งด้วย@Public()) หรือไม่ ถ้ามีให้ returntrueทันที ข้าม JWT - ถ้าไม่ใช่
@Public()จะเรียกsuper.canActivate(context)ซึ่งเข้าสู่passport-jwtflow —ExtractJwt.fromAuthHeaderAsBearerToken()(benefit-api/libs/jwt/jwt.strategy.ts (branch: prod):12) ดึง JWT จาก headerAuthorization: Bearer <token>แล้ว verify signature ด้วยsecretOrKeyจาก envSECRET_KEY JwtStrategy.validate(payload)(benefit-api/libs/jwt/jwt.strategy.ts (branch: prod):18-22) รับ decoded payload แล้ว return กลับเป็นuserobject ซึ่ง Nest จะ inject ลง@Request() { user }ใน controller ทุกตัว —userจะมี fielduid,profile.empCode,profile.fullName,profile.sourceDBJwtAuthGuard.handleRequest(benefit-api/libs/jwt/jwt-auth.guard.ts (branch: prod):26-36) ถ้า verify ล้มเหลวจะ throwUnauthorizedException({ statusCode: 401, error: info.message })กลับไปที่ client
ที่สำคัญคือ guard ตัวนี้ ไม่ได้เช็ค role/menu/permission เลย — payload ที่ decode ได้จาก JWT จะถูกส่งผ่านไป controller ตรง ๆ การ authorize ว่า user คนนี้มีสิทธิ์เข้าถึงเอกสารหรือไม่จึงตกเป็นความรับผิดชอบของแต่ละ service ซึ่งในกรณีส่วนใหญ่ service ก็ไม่ได้เช็คเพิ่ม (ดู 02 Core pipeline สำหรับการวิเคราะห์เชิงลึก)
route ที่ใช้ @Public() อย่างชัดเจนคือ WdhospitalsController.add, update/:status, detail/:id (benefit-api/src/modules/wdhospitals/wdhospitals.controller.ts (branch: prod):24,30,36) — ทั้ง 3 endpoint นี้ถูกเรียกโดย vtrc-api ผ่าน HTTP ไม่ใช่ user จึงไม่มี JWT ติดมาด้วย module อื่น ๆ เช่น disaster, approveDisaster, payments ไม่มี @Public() ที่ endpoint ใด ๆ จึงต้องผ่าน JWT เสมอ
Naming ที่ต้องระวัง
ชื่อ package ใน package.json คือ vtrc-restfull-v2 (benefit-api/package.json (branch: prod):2) — ถ้า grep benefit-api ใน CI/CD script หรือ log pipeline จะไม่เจอชื่อนี้ ต้องค้นหาด้วย vtrc-restfull-v2 แทน
UploadService (benefit-api/src/modules/uploadFiles/upload.service.ts (branch: prod):7) เก็บไฟล์ที่ local disk ภายใต้ ./uploads/${prefixPath}/${dirDate}/ ตรง ๆ ผ่าน base64ToFile ใน libs/common/base64-to-file.ts — คนละ mechanism กับ vtrc-common file service ที่ module disbursement และ payments (ตอนอ่าน welfareAttachment) เรียกผ่าน HTTP ไป ${VTRC_COMMON_SERVER}vtrc-common/api/v1/file/getFileArray (benefit-api/src/modules/payments/payments.service.ts (branch: prod):901-912) — repo เดียวมี 2 ระบบเก็บไฟล์คู่ขนาน ถ้าแก้ไฟล์ attachment flow ต้องเช็คก่อนวย module ไหนใช้ mechanism ไหน
Entity base class
entity ทุกตัวใน src/entities/ สืบทอดจากหนึ่งในสอง base class คือ BaseEntity (benefit-api/src/entities/base.ts (branch: prod):3-21) สำหรับ master/lookup table (id, createdAt, createdBy, updatedAt, updatedBy, isDeleted) และ BaseBenefitEntity (benefit-api/src/entities/baseBenefit.entity.ts (branch: prod):3-24) สำหรับ transaction table (id, createdAt, createdByEmpcode, createdByFullname, updatedAt, updatedByEmpcode, updatedByFullname) — สังเกตว่า base class ทั้งสองมี field audit ต่างกันเล็กน้อย (string createdBy vs. split createdByEmpcode + createdByFullname) ซึ่งสะท้อนว่า schema benefit ถูกออกแบบโดยคนละช่วงเวลาหรือคนละทีม ตอนเขียน entity ใหม่ต้องเลือก base class ให้ตรงกับ migration script ใน sql/
PaymentTransactionEntity (benefit-api/src/entities/paymentTransaction.entity.ts (branch: prod):7-210) เป็น entity ที่กลายเป็นจุดศูนย์กลางของระบบเพราะเก็บ payment transaction ของทุก docType (1 = WdHospitals, 2 = Disaster, 3 = StudyLeave, 4 = other, 5 = inheritance) — service หลายตัวใน payments, disaster, approveDisaster, disbursement, studyLeave อ่าน/เขียนตารางนี้ร่วมกัน ทำให้การแก้ field หรือ index ของ entity นี้มี blast radius กว้าง
WelfareHeader (benefit-api/src/entities/welfareHeader.entity.ts) เป็นอีก entity สำคัญที่เก็บ header ของเอกสารเบิก "สวัสดิการอื่น" (docType 4) ครอบคลุม relation welfareDetail, welfareBankDetail, welfareAttachment, welfareHistory — module disbursement และ payments (listOtherWelfare, getOtherWelfareDetailById) เรียกใช้ผ่าน leftJoinAndSelect chain
entity ของ disaster family ประกอบด้วย WfDisasterHD (header), WfDisasterDocument (รายการเอกสารประกอบ), WfDisasterHistories (audit trail), MAttachfile (ไฟล์แนบรวม), MDisasterCondition (master ประเภทภัยพิบัติ) — WfDisasterHD มี relation cascade ไปยังทั้ง 4 ทำให้การ save header ทีเดียวจะกระทบลูกทั้งหมด (ดู 02 Core pipeline)
entity ของ WdHospitals family ใน schema benefit ประกอบด้วย WdHospitalsEntity, WdHospitalsHistoryEntity, WdHospitalSicksEntity, WdDocumentsEntity, WdExportPaymentEntity/WdExportPaymentDetailEntity/WdExportPaymentHistoryEntity — เป็นการ mirror ข้อมูลจาก legacy WDHospitals table ใน schema vtrc เพื่อให้ benefit-api อ่านได้โดยไม่ต้อง cross-database query ในทุกกรณี
Env var และ configuration factory
config/configuration.ts (benefit-api/config/configuration.ts (branch: prod):3-80) เป็น factory function ที่แปลง env var ทั้งหมดเป็น config object ที่ NestJS ConfigService เข้าถึงได้ โครงสร้าง key สำคัญมีดังนี้
| key ใน config | env var ที่อ่าน | การใช้งาน |
|---|---|---|
port | PORT | listen port — Dockerfile expose 9320 |
jwtSecretKey | SECRET_KEY | JWT signing/verification ใน JwtStrategy |
centralizeServer | CENTRALIZE_SERVER | base URL ของ centralize-api |
vtrcServer | VTRC_SERVER | base URL ของ vtrc-api |
db | DB_TYPE, DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME | TypeORM options สำหรับ connection main (schema benefit) |
dbHRMI | DB_HRMI_* | TypeORM options สำหรับ connection hrmi (SQL Server HR master) |
vtrcDB | VTRC_DB_* | TypeORM options สำหรับ connection vtrc (legacy core schema) |
disbursement.status | (hardcode) | label ภาษาไทยของสถานะ disbursement |
exportPaymentTransactionDT.status | (hardcode) | label ภาษาไทยของสถานะ payment |
สังเกตว่า VTRC_COMMON_SERVER (ที่ใช้ใน payments.service.ts:901) ไม่ได้ถูก declare ใน configuration.ts — service อ่านตรงจาก process.env ผ่าน ConfigService.get('VTRC_COMMON_SERVER') ซึ่งก็ได้ค่า เพราะ ConfigModule load env โดย default แต่ไม่มี type-safe key ใน factory — เป็น inconsistency เล็กน้อยที่อาจทำให้ miss config ตอน deploy ได้
ทุก connection ตั้ง logging: ['error', 'query'] (benefit-api/config/configuration.ts (branch: prod):17,35,53) ซึ่งหมายความว่าทุก SQL query จะถูก log ออก stdout ในทุก environment รวมถึง prod — เป็นค่าเริ่มต้นที่ค่อนข้าง verbose และอาจรั่วข้อมูล sensitive ลง log file
ทุก connection ตั้ง extra: { trustServerCertificate: true, encrypt: false, connectionTimeout: 15000, requestTimeout: 15000 } (benefit-api/config/configuration.ts (branch: prod):19-24,37-42,55-60) — encrypt: false เป็นการลดความปลอดภัยของ database connection เพื่อให้ใช้งานกับ SQL Server เก่าได้
autoLoadEntities: process.env.NODE_ENV === 'dev' และ synchronize: process.env.NODE_ENV === 'dev' (benefit-api/config/configuration.ts (branch: prod):15-16) — เฉพาะ connection main เท่านั้นที่เปิด synchronize ใน dev ส่วน vtrc และ hrmi ปิดไว้เสมอเพื่อกัน schema drift
Build, deploy, runtime
Dockerfile (benefit-api/Dockerfile (branch: prod):1-30) ใช้ base image node:20-alpine ติดตั้ง chromium package (benefit-api/Dockerfile (branch: prod):8-14) เพื่อให้ puppeteer ทำงานได้โดยไม่โหลด Chromium เอง (ตั้ง env PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true ที่บรรทัด 18) — ขนาด image จึงใหญ่กว่า Node.js service ทั่วไปมาก จาก chromium + library dependencies (harfbuzz, freetype, ttf-freefont, nss)
build script ใน package.json (benefit-api/package.json (branch: prod):13) คือ nest build (compile TS → JS ด้วย @nestjs/cli) ส่งผลลัพธ์ไป dist/src/main.js — start:prod script (benefit-api/package.json (branch: prod):18) รัน node dist/src/main โดยตรง ไม่ใช้ nest start
CI/CD pipeline อยู่ใน bitbucket-pipelines.yml ใช้ Bitbucket Pipelines (ไม่ใช่ GitHub Actions) — deploy ผ่าน docker-compose ไปยัง server ภายในของ Thai Red Cross รายละเอียดอยู่ใน volume 13 (deploy)
runtime directory layout บน server ภายหลัง deploy จะมีโฟลเดอร์ uploads/ ที่ runtime เขียนไฟล์แนบของ user ลงไปจริง — ใน container นี้ไม่ใช่ volume แยก แต่อยู่ใน working directory ของ container ดังนั้นถ้า container restart ไฟล์แนบที่ user upload หลังจาก deploy ครั้งล่าสุดจะ หายไป ถ้าไม่ได้ mount volume แยก ตรวจสอบกับ ops ว่ามีการ mount จริงหรือไม่ (UNVERIFIED — ต้องถามทีม ops)
Feature module reference
ตารางนี้สรุป endpoint หลักของแต่ละ module เพื่อให้กระโดดไป source code ได้เร็ว
| Module | Controller path | endpoint ที่สำคัญ | ใช้ @Public() ไหม |
|---|---|---|---|
disaster | /disaster | POST /create_disaster, PUT /update_disaster, GET /get_list, GET /get_disaster_condition | ไม่ |
approveDisaster | /approveDisaster | POST /saveApprove, GET /listBenefitForApprover | ไม่ |
payments | /payments | POST /add, POST /update/:status, POST /cashPayment, POST /cancelWelfare, POST /checkEmpDetail | ไม่ |
wdhospitals | /wdHospitals | POST /add, POST /update/:status, GET /detail/:id, GET /employee, GET /maker, GET /approver | ใช่ (3 endpoint แรก) |
disbursement | /disbursement | create/update/list other welfare | ไม่ |
exportPaymentReport | /exportPaymentReport | export FMIS file, render PDF slip | ไม่ |
studyLeave | /studyLeave | create/update study leave | ไม่ |
setupPayor | /setupPayor | CRUD payor master | ไม่ |
chartOfAccount | /chartOfAccount | CRUD COA master | ไม่ |
dropdown | /dropdown | lookup master | ไม่ |
uploadFiles | /upload | เก็บไฟล์ base64 → local disk | ผสม |
export-payments | /exportPayments | export payment list | ไม่ |
batchJob | /batchJob | manual trigger cron | ไม่ |
endpoint ทั้งหมดขึ้นต้นด้วย prefix /benefit-api/api/v1 (จาก app.setGlobalPrefix ใน main.ts:25) — full URL จึงเป็น /benefit-api/api/v1/disaster/create_disaster เป็นต้น
Swagger UI เปิดที่ /benefit-api/docs (benefit-api/src/main.ts (branch: prod):40-41) — ใช้ตรวจสอบ DTO shape และลองยิง request ได้ในแบบ interactive
Cron jobs
CronTabService (benefit-api/src/crontab/cronTab.service.ts (branch: prod)) ประกาศ cron job เพียง 2 ตัวผ่าน decorator @Cron ของ @nestjs/schedule
@Cron('10 22 * * *')
private async importDataFromHRMI() {
await this.funeralTaskService.doImportFuneral(null);
await this.inheritanceTaskService.doImportInheritance(null);
}
@Cron(CronExpression.EVERY_DAY_AT_9PM)
private async updateDataToHRMI() {
await this.funeralTaskService.doUpdateFuneralForHRMI(null);
await this.inheritanceTaskService.doUpdateInheritanceForHRMI(null);
}importDataFromHRMI รันทุกวันเวลา 22:10 (TZ Asia/Bangkok จาก Dockerfile:5) — ดึงข้อมูลฌาปนกิจและมรดกตกทอดจาก HRMI เข้า benefit
updateDataToHRMI รันทุกวันเวลา 21:00 — ส่งข้อมูลสถานะการจ่ายเงินกลับไป HRMI
ทั้งสอง job ทำงาน sequential ภายใน method เดียว ไม่มี retry ไม่มี backoff — ถ้า HRMI down จะ fail ทั้งก้อนแล้วรอรอบถัดไปวันต่อมา ดูรายละเอียดใน 05 Cron jobs
batchJob module (benefit-api/src/modules/batchJob/manualBatch.controller.ts) เป็น HTTP endpoint สำหรับ manual trigger งานเดียวกัน — มีไว้ให้ ops รันย้อนหลังได้เมื่อ cron fail
Logging และ observability
repo ใช้ custom logger I3GatewayLogger (benefit-api/libs/log/logs.middleware.ts) ที่ถูกใช้ทั้งใน main.ts (benefit-api/src/main.ts (branch: prod):16,19) และใน service ทุกตัวผ่าน pattern private logger = new I3GatewayLogger(ClassName.name) — เป็น wrapper เล็กน้อยบน Logger ของ NestJS เพื่อรวม log format ไม่ได้เพิ่ม feature อะไรมาก ไม่มี correlation ID ไม่มี structured logging (ออกมาเป็น string) — การ trace request ข้าม service จึงยาก
console.log ปรากฏหลายสิบครั้งใน service (เช่น benefit-api/src/modules/approveDisaster/approvedisaster.service.ts (branch: prod):145,265,403,418,421) — เป็น debug leftover ที่ไม่ควรอยู่ใน production code แต่ก็ไม่ได้ถูก lint ออก
TypeORM log level ['error', 'query'] ที่ตั้งใน configuration.ts ทำให้ทุก SQL query ถูก log ออก stdout — ในกรณี raw SQL string interpolation ที่มีค่าจาก user (เช่นใน check-emp-wd.util.ts:204, payments.service.ts:494) ค่าเหล่านั้นจะปรากฏใน log ทำให้ risk เรื่อง sensitive data exposure ใน log file สูง
ไม่มีการเชื่อมต่อกับ APM หรือ metrics collector (Prometheus, Datadog, Sentry) ใด ๆ ใน codebase — observability ของ benefit-api จึงอาศัย log ของ Bitbucket Pipelines และ log ของ container เท่านั้น
Testing
repo มี spec file เพียง 5 ไฟล์เท่านั้นคือ export-payments.service.spec.ts, export-payments.controller.spec.ts, dropdown.service.spec.ts, dropdown.controller.spec.ts, upload.controller.spec.ts, upload.service.spec.ts, disaster.controller.spec.ts, disaster.service.spec.ts — ครอบเพียง 4 ใน 14 module (export-payments, dropdown, uploadFiles, disaster) และ module ใหญ่ที่สุดอย่าง payments และ exportPaymentReport ไม่มี test เลย
config Jest ใน benefit-api/package.json (branch: prod):87-106 ตั้ง testRegex: .*.spec\\.ts$, transform ใช้ ts-jest, testEnvironment: node — script test (benefit-api/package.json (branch: prod):20) รัน jest ปกติ ส่วน test:cov (benefit-api/package.json (branch: prod):22) ใช้ --coverage แต่ไม่ได้ตั้ง threshold ไว้ใน config จึงไม่บังคับ
ไม่มี e2e test ที่ใช้งานได้จริง — test:debug script อ้าง ./test/jest-e2e.json แต่ไม่มี e2e spec ใน test/ นอกจาก config ว่าง ๆ
ทำไมต้องรู้เรื่องนี้ก่อน
บทถัดไป (core pipeline, persistence, integrations, scorecard) จะอ้างชื่อ module/entity/guard จากแผนที่นี้ตลอด ถ้าเจอ PaymentTransactionEntity แล้วงง ให้กลับมาดูว่ามันอยู่ใน src/entities/paymentTransaction.entity.ts และถูกใช้ร่วมกันโดยทุก module ที่สร้างรายการเบิกจ่าย ถ้าเจอ @Public() บน endpoint ให้นึกถึง global JwtAuthGuard ที่ติดตั้งใน main.ts:32 และถ้าเจอ query ไป vtrc entity manager ให้นึกถึง connection vtrc ใน app.module.ts:46-51 ที่ชี้ไป schema ของ legacy core — ไม่ใช่ schema benefit ของตัวเอง
ไป บท 2 Core pipeline →