Job-Scheduler-API · Repository map + Cron catalog (branch: prod)
job-scheduler-api เป็น NestJS 9 headless cron runner — มี business logic จริงแค่ 1 job: ทุกวัน 17:00 น. เรียก POST {INCIDENT_SERVER}incident-management-api/crontab/summary
ชื่อ service + .env (BASE_PATH=benefit-api/, DB_NAME=benefit) เป็น copy-paste จาก benefit-api — ไม่สะท้อนหน้าที่จริง
Identity
| Item | Value |
|---|---|
| Canonical branch | prod (31 tracked files) |
| Stack | NestJS 9 + @nestjs/schedule + axios + Passport JWT (unused) |
| Port | 9900 |
| Timezone | Asia/Bangkok (Docker volume) |
| HTTP surface | ว่าง — AppController ไม่มี route |
Module map
AppModule
├── ConfigModule.forRoot
├── ScheduleModule.forRoot() ← enables @Cron
├── IncidentsModule
│ └── IncidentsService ← sole business logic
├── AppController ← EMPTY
├── AppService ← EMPTY
└── JwtStrategy ← template residueIncidentsModule ไม่มี controller — cron-only
Cron catalog (authoritative)
| Job | Schedule | Meaning | File:line | Action |
|---|---|---|---|---|
incidentSummary | CronExpression.EVERY_DAY_AT_5PM → 0 17 * * * | Daily 17:00 Bangkok | incidents.service.ts:16-30 | POST {INCIDENT_SERVER}incident-management-api/crontab/summary — no body, no auth |
History: commit เคยใช้ @Cron('* * 17 * * *') (ทุกนาทีในชั่วโมง 17) + GET — แก้เป็น daily 5PM + POST
Unmerged — branch origin/add-cronjob-meeting only
| Job | Schedule | Action |
|---|---|---|
meeting() | 0 */15 * * * * (every 15 min) | GET {meetingServer}meeting/secure/corn-email |
ยังไม่ merge เข้า prod
Full trace — incidentSummary
17:00 Bangkok
→ IncidentsService.incidentSummary()
→ config.get('incidentServer') // INCIDENT_SERVER=https://vtrcapi.redcross.or.th/
→ axios.post(`${url}incident-management-api/crontab/summary`) // NO auth headers
→ vtrc-api nginx proxies /incident-management-api/ → localhost:7201
→ I3GatewayLogger.info(response.data) → ./logs/{DD-MM-YYYY}.log
→ on error: throw BadRequestException (unusual for background cron)Evidence — job-scheduler-api/src/modules/incidents/incidents.service.ts:16-30 (branch: prod)
Downstream: incident-management-api repo ไม่อยู่ใน workspace — ยืนยันได้แค่ nginx upstream ที่ vtrc-api/nginx/default.conf:116-123
Dead DB wiring
| Layer | Status |
|---|---|
.env DB_* (MSSQL, DB_NAME=benefit) | Configured แต่ never consumed |
configuration.ts db.* | Loaded into ConfigService แต่ไม่มี config.get('db.*') |
TypeOrmModule | Imported ใน app.module.ts:6 แต่ ไม่อยู่ใน imports array |
Commit 82cab98 "remove db" | เคยมี TypeOrmModule.forRootAsync (mysql + synchronize:true) — ลบแล้ว |
npm deps typeorm, mysql2 | Zero runtime usage |
| Dead imports | InjectEntityManager, EntityManager ใน incidents.service.ts:3-4 |
Also unused env: VTRC_SERVER, VTRC_COMMON_SERVER
Auth / HTTP surface
| Surface | Behavior |
|---|---|
| HTTP routes | None — empty AppController |
| Global JwtAuthGuard | Applied (main.ts:27) — irrelevant today |
@Public() | Defined แต่ไม่เคยใช้ |
| Outbound cron POST | No auth — bare axios |
| Swagger | {BASE_PATH}docs = benefit-api/docs (misleading) |
Security findings
SEC-JOBSCHED-01 · .env tracked in git with secrets
ที่: job-scheduler-api/.env (committed) — MSSQL sa password + SECRET_KEY
Impact: anyone with repo read access ได้ production DB credentials
Remediation: rotate + .gitignore + git filter-repo
SEC-JOBSCHED-02 · Unauthenticated outbound cron POST
ที่: incidents.service.ts:20-21
Impact: endpoint /crontab/summary ถูกเรียกโดยไม่มี auth — ถ้า network เปิด ใครก็ trigger ได้
Remediation: เพิ่ม shared secret header หรือ mTLS; ฝั่ง incident-management-api ต้อง verify
Other findings
| ID | Severity | Finding |
|---|---|---|
| SEC-JOBSCHED-03 | 🟡 | CORS origin: '*' (main.ts:29-33) |
| SEC-JOBSCHED-04 | 🟡 | JwtAuthGuard logs full user payload |
| QUAL-JOBSCHED-01 | 🟢 | Committed log file logs/30-01-2023.log |
| QUAL-JOBSCHED-02 | 🟢 | Broken e2e expects Hello World! |
| OBS-JOBSCHED-01 | 🟢 | Dockerfile EXPOSE 3000 vs port 9900 |
Health Scorecard
| Dimension | สถานะ | เหตุผล |
|---|---|---|
| Job correctness | 🟡 | 1 job ทำงาน; history ของ schedule bug |
| Secrets | 🔴 | .env committed |
| Outbound auth | 🔴 | Cron POST ไม่มี auth |
| Dead code | 🔴 | TypeORM/JWT/empty controller ทั้งหมด |
| Naming identity | 🔴 | benefit-api copy-paste |
| Observability | 🟡 | File log มี; ไม่มี health/metrics |
| Test coverage | 🔴 | Smoke only + broken e2e |
คะแนนรวม: 0 🟢 / 2 🟡 / 5 🔴
Modernization priority
- Rotate secrets + ลบ
.envจาก git - เพิ่ม auth ให้ outbound cron + ฝั่ง receiver
- ลบ TypeORM/mysql2/JWT dead deps
- แก้ identity (
BASE_PATH, project name) - เพิ่ม
/health@Public()endpoint - ตัดสินใจ merge meeting cron จาก
add-cronjob-meetingหรือปิด branch
กลับไป index →