3.3.1 · Repository map + รวบรวม dead code
บทนี้วาดแผนผังของ centralize-api/ ทั้ง repository พร้อมระบุ dead code และไฟล์ที่ตกค้างจาก NestJS starter template เพื่อให้อ่านและค้นหาโค้ดได้ง่ายขึ้น และทราบว่าไฟล์ไหนไม่ควรแก้เพราะเป็นขยะ
ภาพรวมขนาด
| หมวด | จำนวนไฟล์ | รวม LOC |
|---|---|---|
| Controllers | 4 (app, employee, hrPaySlip, hrTimeTemp) | 218 |
| Services | 7 | 1,019 |
| Entities | 7 (+ 1 index) | 200 |
| Modules | 6 | 99 |
| DTOs | 5 (+ 1 index) | 276 |
| Repositories | 3 | 80 |
| Root + libs + config + docs | 12 | 523 |
| รวม TypeScript | 44 | ~2,243 |
เปรียบเทียบขนาดกับ service อื่นในระบบ — vtrc-api ใหญ่ที่สุดที่ประมาณ 25,800 LOC, cu-central-api อยู่ในระดับกลาง, ส่วน centralize-api ตัวนี้เล็กที่สุด (ประมาณ 2,243 LOC) — เล็กพอที่จะอ่านได้ทั้ง repo ในรอบเดียว แต่มีรายละเอียดซ่อนอยู่มากตามที่บทถัดไปจะอธิบาย
แผนผังไดเรกทอรี (depth 3 ของ src/)
centralize-api/
├── .env ← committed มี prod/UAT creds (3.3.2 + 3.3.8)
├── .eslintrc.js
├── .gitignore ← มี `.env*` แต่ `.env` ถูก force-add อยู่แล้ว
├── .prettierrc
├── Dockerfile ← ใช้ yarn install ทั้งที่เป็น npm repo (bug — ดู §"Dockerfile")
├── docker-compose.yml
├── nest-cli.json ← Swagger plugin config
├── package.json ← NestJS 9, TypeORM 0.3, mssql 9
├── tsconfig.json ← strictNullChecks: false
├── tsconfig.build.json
├── README.md ← default NestJS starter (ไม่มีเนื้อหา-specific)
├── config/
│ └── configuration.ts ← env var loader 13 LOC
├── docs/
│ └── document.config.ts ← Swagger DocumentBuilder 10 LOC
├── libs/
│ ├── common/
│ │ └── validator-date.ts ← IsOnlyDate ไม่ได้ใช้ (dead)
│ ├── exception/
│ │ └── http-exception.filter.ts ← จับแค่ HttpException
│ ├── jwt/
│ │ ├── jwt-auth.guard.ts ← global guard ที่ถูก bypass ทุก route (3.3.5)
│ │ └── jwt.strategy.ts ← secretOrKey ผิดประเภท (3.3.5)
│ └── log/
│ └── logs.middleware.ts ← custom logger I3GatewayLogger
├── src/
│ ├── main.ts ← bootstrap + globals (3.3.2)
│ ├── app.module.ts ← root module + 2 TypeORM connections (3.3.2)
│ ├── app.controller.ts ← root controller 1 endpoint
│ ├── app.controller.spec.ts ← empty test (dead)
│ ├── app.service.ts ← org hierarchy CTE ~130 LOC
│ ├── dto/
│ │ ├── employee.dto.ts ← DTOs ครอบคลุม EmployeeController (14 GET)
│ │ ├── hrTimeTemp.dto.ts ← time attendance DTOs
│ │ ├── hrpayslip.dto.ts ← EmpCodeListDto 1 class
│ │ ├── test.dto.ts ← CreateCatDto (dead — โค้ดค้างจาก NestJS starter)
│ │ └── index.ts
│ ├── entities/
│ │ ├── employeeGroup.entity.ts ← center connection
│ │ ├── employeeGroup.nbc.entity.ts ← NBC connection (inherit)
│ │ ├── employeeLevel.entity.ts ← center
│ │ ├── employeeLevel.nbc.entity.ts ← NBC
│ │ ├── technicalPosition.entity.ts ← center
│ │ ├── technicalPosition.nbc.entity.ts← NBC
│ │ ├── user.entity.ts ← DEAD — not registered (3.3.6)
│ │ └── index.ts
│ └── modules/
│ ├── employee/ ← 1 controller 14 GET endpoints + 1 service 605 LOC
│ │ ├── employee.controller.ts
│ │ ├── employee.service.ts
│ │ └── employee.module.ts
│ ├── employeeGroup/ ← sub-module ไม่มี controller
│ ├── employeeLevel/ ← sub-module ไม่มี controller
│ ├── hrPaySlip/ ← 1 controller 1 POST endpoint
│ ├── hrTimeTemp/ ← 1 controller 1 POST endpoint
│ └── technicalPosition/ ← sub-module ไม่มี controller
└── test/
├── app.e2e-spec.ts ← broken — expects 'Hello World!' แต่จริงคือ SQL rows
└── jest-e2e.jsonจุดสังเกต
src/modules/employeeGroup/,employeeLevel/,technicalPosition/เป็น sub-module ที่มี service + module + repository แต่ ไม่มี controller ของตัวเอง — service ของทั้งสามถูก inject เข้าไปในEmployeeControllerแทน (บท 3.3.3) ทำให้EmployeeModuleimport ทั้งสาม sub-module เข้ามา (centralize-api/src/modules/employee/employee.module.ts:18-19)libs/ใช้สำหรับโค้ด cross-cutting (auth, logging, exception, validator) ไม่ใช่ module ของ domain- ไม่มี
migrations/directory — TypeORM ใช้แค่ connection ไม่ได้ทำ schema migration (บท 3.3.2) - ไม่มี
health/directory หรือHealthController— root/apiที่ดูเหมือน health check จริง ๆ รัน SQL 130 บรรทัด (บท 3.3.2)
package.json — version ที่ใช้
centralize-api/package.json:26-45
"dependencies": {
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.0.0",
"@nestjs/passport": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/swagger": "^6.1.3",
"@nestjs/typeorm": "^9.0.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"moment": "^2.29.4",
"mssql": "^9.1.1",
"passport": "^0.6.0",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"typeorm": "^0.3.10"
}สิ่งที่ควรทราบ
@nestjs/*9.x — NestJS 9 release เดือน พฤษภาคม 2022 (ปัจจุบันเวอร์ชั่น 11.x ออกแล้ว ตามหลัง 2 major)typeorm0.3.10 — เวอร์ชั่น 0.3 ล่าสุดรองรับทะเบียนวันที่ 2026 อยู่ที่ 0.3.20+mssql9.1.1 — Microsoft driver ตามหลัง 2 major (ปัจจุบันเวอร์ชั่น 11.x) มี known issue เรื่อง tedious drivermoment2.29.4 — deprecated อย่างเป็นทางการ เปลี่ยนเป็นdate-fnsหรือdayjspassport-localอยู่ใน deps แต่ไม่ถูก import ที่ไหน — dead dependency (บท 3.3.5)- ไม่มี
axios,got,node-fetch→ service ไม่ได้เรียก HTTP external ใด ๆ - ไม่มี
@nestjs/schedule→ ไม่มี cron - ไม่มี
helmet,compression,cookie-parser→ ขาดการเตรียมพร้อมสำหรับ production
centralize-api/package.json:8-25
"scripts": {
"nest": "nest",
"dev:docker": "docker-compose up dev",
"prod:docker": "docker-compose up prod",
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start --watch",
"start:dev": "rm -rf ./dist && nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/src/main",
"lint": "eslint \"{src,docs,config,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
}จุดสำคัญ
start:prod=node dist/src/mainไม่มีNODE_ENVinjection ใน script → env var ต้องตั้งภายนอก (docker-compose หรือ systemd)lintใช้--fixเป็น default → อันตรายถ้ารันใน CI เพราะจะ modify file แทนที่จะแค่ตรวจ- มี
test,test:watch,test:cov,test:debug,test:e2eครบ แต่ไฟล์ test ที่มีคือ empty spec + broken e2e (ดู §"Dead code")
tsconfig.json — type safety ปิดเกือบหมด
centralize-api/tsconfig.json:1-21
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}การตั้งค่าที่ทำให้ type safety อ่อนลง
| ตัวเลือก | ค่า | ผลกระทบ |
|---|---|---|
strictNullChecks | false | TypeScript ไม่ตรวจ null/undefined safety — obj.field ที่อาจเป็น null จะ compile ผ่าน |
noImplicitAny | false | อนุญาต implicit any — function parameter ที่ไม่มี type จะเป็น any โดยอัตโนมัติ |
strictBindCallApply | false | ไม่ตรวจ bind/call/apply argument type |
forceConsistentCasingInFileNames | false | import path ตัวเล็ก-ใหญ่ผิดได้ (เสี่ยงกับ filesystem case-sensitive เช่น Linux) |
ไม่มี strict: true | — | umbrella strict mode ไม่เปิด |
experimentalDecorators + emitDecoratorMetadata จำเป็นสำหรับ NestJS เพราะ decorator ต้องการ metadata ในขณะรัน (runtime)
.eslintrc.js ก็ปิด @typescript-eslint/no-explicit-any: 'off' เสริม → any ใช้ได้อย่างเสรี พบจริงใน centralize-api/libs/jwt/jwt.strategy.ts:9,18, repositories ทั้ง 3 (centralize-api/src/modules/employeeGroup/employeeGroup.repository.ts:9, centralize-api/src/modules/employeeLevel/employeeLevel.repository.ts:7, centralize-api/src/modules/technicalPosition/technicalPosition.repository.ts:9)
nest-cli.json — Swagger plugin
centralize-api/nest-cli.json:1-17
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": [
{
"name": "@nestjs/swagger",
"options": {
"dtoFileNameSuffix": [".dto.ts"],
"classValidatorShim": true,
"introspectComments": true
}
}
]
}
}plugin นี้อ่าน DTO class ที่ลงท้ายด้วย .dto.ts แล้วสร้าง @ApiProperty() ให้อัตโนมัติจาก TypeScript type — เป็นเหตุผลที่ DTOs ใน src/dto/ ไม่ต้องเขียน decorator เองทุกบรรทัด (บท 3.3.3)
introspectComments: true → JSDoc comment เหนือ class/method จะถูกดึงไปเป็น Swagger description ด้วย แต่ในทางปฏิบัติ DTOs ส่วนใหญ่ใน repo ไม่มี JSDoc ทำให้ description ว่าง
Dockerfile — yarn ใน npm repo + ขน .env เข้า production image
centralize-api/Dockerfile:1-27
FROM node:16 AS develop
WORKDIR /usr/src/app
ENV TZ="Asia/Bangkok"
COPY package.json ./
COPY . .
RUN yarn install --ignore-engines
RUN yarn build
FROM node:16 AS production
WORKDIR /usr/src/app
COPY --from=develop /usr/src/app/.env .
COPY --from=develop /usr/src/app/dist ./dist
COPY --from=develop /usr/src/app/package.json ./package.json
RUN yarn install --ignore-engines
EXPOSE 3000
CMD [ "yarn", "start:prod"]ปัญหา
- ทั้งสอง stage ใช้
yarn install --ignore-enginesทั้งที่ repo เป็น npm (มีแค่package.jsonไม่มีyarn.lock) → yarn จะ resolve dependency tree เองใหม่ตาม semver range ผลลัพธ์ที่ได้ใน container อาจต่างจากที่ dev ติดตั้ง local ผ่าน npm เพราะไม่มี lockfile เป็นหลักฐาน → dependency drift อย่างเงียบ COPY --from=develop /usr/src/app/.env .— บรรทัดนี้เป็นหลักฐานชัดเจนว่า build pipeline พึ่งพาไฟล์.envที่ committed โดยตรง ไม่ได้รับ env var จาก orchestration (Kubernetes Secret, docker-compose env_file) → แม้จะแก้ CRIT-01 (ลบ.envออกจาก git) แต่ถ้าไม่แก้ Dockerfile ไปด้วย จะ build ไม่ผ่าน--ignore-enginesใน yarn stage ปิดเสียงเตือน engine check — อาจซ่อนปัญหาความเข้ากันได้ของ dependency กับ Node เวอร์ชั่นปัจจุบัน (Node 16 เป็น EOL ตั้งแต่ กันยายน 2023)- ไม่มี multi-stage แยก build deps ออก —
developstage เอาทั้ง devDependencies ไปด้วย ทำให้ image โตเกินจำเป็น - ไม่มี
USERdirective — container รันเป็น root โดย default
ผลกระทบ — build ระหว่าง stage ไม่สามารถทำซ้ำได้ผลลัพธ์เดิม (reproducible) อย่างสมบูรณ์ และถ้ามี dependency ที่ติดตั้งไม่เหมือนกันระหว่าง dev กับ production จะเจอ bug ที่หายาก
การแก้ — เปลี่ยนทุก stage เป็น npm ci (clean install จาก package-lock.json) ลบ --ignore-engines ใช้ --omit=dev แยก stage build ออกจาก runtime, เพิ่ม USER node, ดึง .env ออกแล้วใช้ env_file ใน compose หรือ Secret ใน Kubernetes แทน
รวบรวม dead code
ไฟล์และโค้ดต่อไปนี้อยู่ใน repo แต่ ไม่ได้ใช้งานจริง — ลบได้โดยไม่กระทบ runtime แต่ทีมยังไม่ได้ลบเพราะอาจมีการคืนมาใช้ในอนาคต
| รายการ | ไฟล์ | หลักฐาน |
|---|---|---|
CreateCatDto (class cat/breed/age) | centralize-api/src/dto/test.dto.ts:1-17 | ค้างจาก NestJS starter template — ไม่ถูก import |
IsOnlyDate validator | centralize-api/libs/common/validator-date.ts:8-22 | grep ทั้ง repo ไม่พบการใช้งาน |
UserEntity (table auth_user) | centralize-api/src/entities/user.entity.ts:1-47 | ไม่ได้ export จาก entities/index.ts, ไม่ได้ register ใน module, table ไม่มีใน HRMI |
getEmployeesTempPOakByEmployeeCode (commented) | centralize-api/src/modules/employee/employee.service.ts:588-604 | โค้ดทั้ง method อยู่ใน /* */ |
EmployeeResponseDto | centralize-api/src/dto/employee.dto.ts:72-102 | define แต่ไม่ได้เป็น return type ของ controller |
passport-local dependency | centralize-api/package.json:40 | import ไม่ได้เกิดขึ้นที่ไหน |
app.controller.spec.ts empty test | centralize-api/src/app.controller.spec.ts:18-20 | expect() ทั้งหมด comment ไว้ |
test/app.e2e-spec.ts broken e2e | centralize-api/test/app.e2e-spec.ts | ทดสอบ return string 'Hello World!' แต่ service จริงคืน SQL rows → fail ทุกครั้ง |
RootObject interface (unused) | centralize-api/src/app.controller.ts:7-16 | define shape 9 ฟิลด์แต่ไม่ได้ใช้เป็น type ของอะไร |
UserEntity — ตัวอย่าง dead code ที่น่าสนใจ
centralize-api/src/entities/user.entity.ts:1-48
import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm';
@Entity('auth_user')
@Unique('my_unique_auth_user', ['userName', 'email'])
export class UserEntity {
@PrimaryGeneratedColumn('uuid')
userId: string;
// ... userName, password, firstName, lastName, email, userLevel, ...
}จุดสังเกตที่บ่งบอกว่าเป็นโค้ดที่ค้างจาก starter template
- ใช้
PrimaryGeneratedColumn('uuid')แบบ auto-generate — HRMI tables ใช้ string PK (เช่นEmplGrupIDlength 36) ไม่ใช่ auto-gen UUID - table name
auth_userไม่มีใน HRMI MSSQL เลย - decorator
@Unique('my_unique_auth_user', ['userName', 'email'])มี prefixmy_บ่งบอก tutorial code - ไม่มี
@ManyToOne/@OneToManyใด ๆ ขาด FK ทั้งหมด - ไม่ได้ export จาก
centralize-api/src/entities/index.ts:1-6→ ไม่ถูก TypeORM load เข้า connection - comment
// type: "timestamp with time zone"ที่centralize-api/src/entities/user.entity.ts:28,37เป็น Postgres type ไม่ใช่ MSSQL → เดาได้ว่าคนเขียน copy มาจาก Postgres starter
สิ่งที่ขาด — ไฟล์ที่คาดว่าจะมีแต่ไม่มี
เมื่อเทียบกับ production NestJS service มาตรฐาน ไฟล์ต่อไปนี้ขาดหายไป
| ไฟล์ / directory | ปกติมี | centralize-api |
|---|---|---|
src/migrations/ | TypeORM migration files | ไม่มี — schema sync อาศัย HRMI เป็น source of truth |
src/health/ หรือ HealthController | /health endpoint | ไม่มี — root /api รัน SQL หนักไม่เหมาะเป็น probe |
.env.example | template สำหรับ dev | ไม่มี — มีแค่ .env จริงที่ committed (บท 3.3.8) |
helm/ หรือ k8s/ | Kubernetes manifest | ไม่มี — deploy ผ่าน docker-compose |
package-lock.json (committed) | การติดตั้งที่ทำซ้ำได้ผลลัพธ์เดิม (reproducible) | ไม่มีใน repo — Dockerfile ใช้ yarn install แทน |
tests/ ที่มี unit จริง | coverage | มีแค่ empty spec + broken e2e |
LICENSE | open source license | ไม่มี |
สรุปประเด็นสำคัญ
- centralize-api เป็น TypeScript service ตัวเดียวในระบบ แต่ type safety ปิดเกือบหมด (
strictNullChecks: false,noImplicitAny: false) - Dockerfile มี bug ร้าย —
yarn installใน npm repo ทำให้ build ไม่สามารถทำซ้ำได้ผลลัพธ์เดิม (reproducible) และมีการCOPY --from=develop /usr/src/app/.env .ที่ bind กับ.envcommitted โดยตรง - Dead code 9 รายการค้างอยู่ รวมถึง
UserEntityทั้งไฟล์และCreateCatDtoที่เป็นโค้ดค้างจาก NestJS starter template - README.md เป็น default NestJS starter ไม่มีเนื้อหาเกี่ยวกับ project จริง — ถ้าเจอช่องว่างเอกสาร ให้เริ่มเขียนใหม่ทั้งหมด อย่าแก้ไขจากของเดิม