1 · Repository map
แผนที่โครงสร้าง ai-recruitment-api บน branch prod — ไม่มีโฟลเดอร์ src/ (ต่างจาก NestJS services ใน volume นี้) โค้ดอยู่ที่ root ของ repo
สารบัญ volume: index
แผนที่โฟลเดอร์
ai-recruitment-api/
├── server.js entry — dotenv, CORS, verifyJWT, static, bodyParser, listen
├── logger.js JSON file logger → logs/{YYYY-MM-DD}/{level}.log
├── package.json / package-lock.json Express + openai + mssql + puppeteer + ...
├── Dockerfile node:18-alpine + chromium + canvas native deps
├── .env tracked despite .gitignore — secrets (ดู 04/06)
├── .gitignore ละเว้น node_modules, .env, logs/, storage/
├── text.json ไฟล์ว่าง (0 bytes) ณ เวลาสำรวจ
├── middleware/
│ └── verifyJWT.js jwt.verify + decode สำหรับ log uid
├── config/
│ ├── db.js mssql config object จาก DB_* env
│ ├── openai.js callOpenAI() → gpt-4o via axios
│ └── dalle.js callDalle() → openai.images.generate
├── routes/
│ ├── index.js **mounted** จาก server.js — 9 POST endpoints
│ └── get-post-upload_image.js **orphan** — ไม่ถูก require จาก server.js
├── controllers/ 12 ไฟล์ handler
│ ├── generateJobDescriptionController.js 389 บรรทัด — MSSQL + OpenAI ×4
│ ├── generateInterviewQuestionsController.js 570 บรรทัด — MSSQL หลายตาราง + OpenAI ×6+
│ ├── summarizeJobDescriptionController.js 88 — OpenAI gpt-3.5-turbo
│ ├── summarizeJobQualificationController.js 91 — OpenAI gpt-3.5-turbo
│ ├── module2_1_get_background.js 101 — สุ่มพื้นหลัง b1–b29 (mounted)
│ ├── module2_1_get_background_v2.js 102 — เวอร์ชัน tagged seriousness (orphan)
│ ├── module2_2_controller.js 216 — career classify + DALL-E + remove.bg
│ ├── module_2_3_combine_image.js 443 — Puppeteer combine (orphan)
│ ├── combineJobPostingController.js 451 — Puppeteer combine (mounted)
│ ├── removeBgUploadController.js 183 — upload → remove.bg (mounted)
│ ├── get-post-upload_image.js 176 — เกือบซ้ำ removeBgUpload; export คนละชื่อ
│ └── uploadController.js 30 — saveFileToAPI → writeFileAsync
├── services/
│ └── removeBgService.js removeBgAndSave() สำหรับ module2_2
├── utils/
│ └── file-util.js makeDir / writeFileAsync ใต้ storage/
├── data/
│ └── allImages.js array แผนที่ตำแหน่ง/เพศ → path ภาพใน storage-uploads
└── storage-uploads/
├── Background/ b1.png … b29.png
└── 2025-02-06/uploads/ คลังตัวละคร (c*.png และอื่น ๆ)จำนวนไฟล์ .js ที่ไม่ใช่ node_modules: 23 ไฟล์ (นับจาก tree ด้านบน + root)
Boot sequence
1. require('dotenv').config() server.js:2
2. express() + mkdir storage-uploads ถ้าไม่มี server.js:4-15
3. express.static ×2 ภายใต้ /ai-recruitment-api/api
→ ./storage และ ./storage-uploads server.js:18-19
4. app.disable("x-powered-by") server.js:21
5. cors(_cors) whitelist 3 origins server.js:23-34
6. app.use(verifyJWT) ← global, ไม่มี bypass server.js:37
7. require('./routes') → routes/index.js server.js:40
8. request logging middleware (logger.info) server.js:44-52
9. bodyParser.json / urlencoded limit 50mb server.js:56-57
10. app.use('/ai-recruitment-api/api', router) server.js:60
11. GET /ai-recruitment-api/api welcome string server.js:62-64
12. listen(PORT || 3000) server.js:67-72ลำดับสำคัญ: JWT ทำงานก่อน bodyParser และก่อน router — request ที่ไม่มี token ถูกตัดที่ middleware ก่อนเข้า controller
Static files ก็อยู่หลัง verifyJWT ใน stack เดียวกัน (server.js:18-19 ก่อน JWT แต่ Express จับคู่ path ตามลำดับ registration: static ลงทะเบียนก่อน JWT) — ถ้า request ตรงกับไฟล์ static จะถูกส่งก่อนถึง JWT; ถ้าไม่ตรงไฟล์ จะไปต่อ JWT แล้ว router
Endpoint catalog (mounted)
Prefix จริง: /ai-recruitment-api/api + path ด้านล่าง
แหล่ง mount: routes/index.js ผ่าน server.js:60
| # | Method | Path | Handler | ไฟล์ |
|---|---|---|---|---|
| 1 | POST | /generate-job-description | generateJobDescription | generateJobDescriptionController.js |
| 2 | POST | /get-post-background | getBackground | module2_1_get_background.js |
| 3 | POST | /get-post-character | module2_2Controller (หรือ 500 guard) | module2_2_controller.js |
| 4 | OPTIONS/POST | /get-post-upload_image | removeBgUploadController + multer.any | removeBgUploadController.js |
| 5 | POST | /get-post-summarize-job-description | summarizeJobDescription | summarizeJobDescriptionController.js |
| 6 | POST | /get-post-summarize-job-qualification | summarizeJobQualification | summarizeJobQualificationController.js |
| 7 | POST | /get-post-combine | combineJobPosting | combineJobPostingController.js |
| 8 | POST | /generate-interview-questions | generateInterviewQuestions | generateInterviewQuestionsController.js |
| 9 | POST | /save-file-to-api | saveFileToAPI | uploadController.js |
| — | GET | /ai-recruitment-api/api | welcome text | server.js:62-64 |
หลักฐานประกาศ route: routes/index.js:53-144
Guard ของ /get-post-character
if (typeof module2_2Controller === 'function') {
router.post('/get-post-character', module2_2Controller);
} else {
router.post('/get-post-character', (req, res) => {
res.status(500).json({ status: 'error', message: 'module2_2Controller is not loaded. ...' });
});
}แหล่ง: routes/index.js:63-78 — ออกแบบมาให้ server ไม่ crash ถ้า require controller ล้ม
Orphan / dead code map
| ไฟล์ | สถานะ | หลักฐาน |
|---|---|---|
routes/get-post-upload_image.js | ไม่ถูก mount | server.js require เฉพาะ ./routes (= routes/index.js) |
controllers/module2_1_get_background_v2.js | ไม่ถูก require | routes/index.js:21 ชี้แค่ module2_1_get_background |
controllers/module_2_3_combine_image.js | ไม่ถูก require | mount ใช้ combineJobPostingController ที่ routes/index.js:36,134 |
controllers/get-post-upload_image.js | require แล้วแต่ export ผิดชื่อ | ไฟล์ export { removeBgUploadController } (get-post-upload_image.js:176) แต่ routes/index.js:33 destructure { uploadImageController } → ค่าเป็น undefined (log ที่บรรทัด 44) — route จริงใช้ removeBgUploadController จากไฟล์อีกตัว (routes/index.js:86-117) |
สรุป: upload ที่ทำงานจริงคือ removeBgUploadController.js ไม่ใช่ orphan route file และไม่ใช่ uploadImageController
Config modules
config/db.js
const dbConfig = {
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
server: process.env.DB_SERVER,
database: process.env.DB_DATABASE,
options: {
encrypt: false,
trustServerCertificate: true
}
};แหล่ง: config/db.js:5-14 — export { sql, dbConfig }
หมายเหตุ: generateJobDescriptionController เรียก sql.connect แล้ว override encrypt: true ใน options ของ connection นั้น (generateJobDescriptionController.js:128-135) ในขณะที่ generateInterviewQuestionsController สร้าง config เองและตั้ง encrypt: false (generateInterviewQuestionsController.js:29-36) — ค่า encrypt ไม่สม่ำเสมอข้าม controller
config/openai.js
- URL คงที่:
https://api.openai.com/v1/chat/completions(openai.js:5) - Model:
gpt-4o(openai.js:12) - Fail-soft: catch →
console.errorแล้ว return''(openai.js:24-27) - ไม่ตั้ง timeout / retry ของ axios
config/dalle.js
- Client:
new OpenAI({ apiKey: process.env.OPENAI_API_KEY })(dalle.js:15-17) - Default model
gpt-image-1, size1024x1024(dalle.js:41) - ถ้าได้
b64_jsonจะเขียนไฟล์ใต้PUBLIC_DIR/IMAGE_OUT_DIRแล้วประกอบ URL จากBASE_URL(dalle.js:57-78) - Default
BASE_URL=http://localhost:3000,PUBLIC_DIR=../public— โฟลเดอร์public/ไม่ปรากฏใน tree ของ repo (อาจสร้างตอน runtime)
Logger
logger.js เขียน JSON line ต่อไฟล์ logs/{date}/{level}.log (logger.js:26-38)
ระดับ: info, error, warn, debug (logger.js:42-47)
server.js ใช้ logger.info สำหรับ incoming request (server.js:46-50)uploadController ใช้ logger.error (uploadController.js:19-24)
จุดอื่นส่วนใหญ่ใช้ console.log / console.error — ดู OBS ใน scorecard
Data asset: data/allImages.js
Array ของ object { position, gender, image } ส่งออกเป็น module.exports = allImages (allImages.js:342)
ใช้เมื่อ module2_2 อยู่ในโหมด "first time" — สุ่มภาพจากคลังแทนการเรียก DALL-E (module2_2_controller.js:124-157)
ตำแหน่งที่พบในไฟล์ (ตัวอย่าง): Office worker, Doctor, Nurse, Lawyer, Dentist, pharmacist, Nurse Assistant, IT admin, Janitor, Driver, Gardener, Manager, Engineer, Chef — ตรงกับรายการ keyword ใน prompt career (module2_2_controller.js:105-110)
Path ภาพชี้ไป /2025-02-06/uploads/c*.png ซึ่งถูกเสิร์ฟผ่าน static storage-uploads (server.js:19)
Env key inventory (ชื่อเท่านั้น — ไม่ใส่ค่า)
จาก .env บน working tree (branch prod) บรรทัด 1–12:
| บรรทัด | Key | ใช้ในโค้ด? |
|---|---|---|
| 1 | DB_USER | ใช่ — config/db.js, interview controller |
| 2 | DB_PASSWORD | ใช่ |
| 3 | DB_SERVER | ใช่ |
| 4 | DB_DATABASE | ใช่ |
| 5 | OPENAI_API_KEY | ใช่ — openai.js, dalle.js, summarize*, interview, removeBg ไม่ใช้ |
| 6 | PORT | ใช่ — server.js:67 |
| 7 | REMOVE_BG_API_KEY | ใช่ — removeBgService, removeBgUploadController |
| 8 | DALLE_ENDPOINT | ไม่พบ caller |
| 9–10 | HCTI_USER_ID, HCTI_API_KEY | ไม่พบ caller |
| 11 | JWT_SECRET | ใช่ — verifyJWT.js:4 |
| 12 | GEN_BASE_URL | ไม่พบ caller (มี BASE_URL ใน dalle/removeBgService แทน) |
Dependency notes ที่ควรรู้ตอนอ่านโค้ด
Package ใน package.json | การใช้จริงใน .js |
|---|---|
puppeteer | combineJobPostingController.js, module_2_3_combine_image.js (orphan) |
openai | config/dalle.js เท่านั้น — text path ส่วนใหญ่ใช้ axios ตรง |
axios | openai wrapper, summarize, interview, QR, remove.bg |
mssql | job description + interview controllers |
multer | routes/index.js |
form-data | removeBgService / removeBgUpload |
jsonwebtoken | verifyJWT |
cors, dotenv, qs | server / dotenv; qs ไม่พบ require ตรง |
canvas, sharp | ไม่พบ require — ยังดึง native build ใน Dockerfile |
uuid | require('uuid') ใน background controllers แต่ ไม่อยู่ใน dependencies ของ package.json — พึ่ง transitive หรือ runtime ล้มถ้าไม่มี |
ตัวเลขที่ควรจำ
| ตัวเลข | ค่า |
|---|---|
| Controllers | 12 ไฟล์ |
| Endpoints ที่ mount จริง | 9 POST + 1 GET welcome + OPTIONS upload |
| Services | 1 (removeBgService.js) |
| DB connections ใน config กลาง | 1 (dbConfig) |
| Controller ที่ใหญ่สุด | generateInterviewQuestionsController.js (570 บรรทัด) |
| พื้นหลังสำเร็จรูป | 29 (b1–b29) |
ไฟล์ใต้ storage-uploads/ | 361 (นับ ณ เวลาสำรวจ) |
Test files (.test.js / .spec.js) | 0 |
เคล็ดลับตอนทำงานจริง
- เพิ่ม endpoint ใหม่ — แก้เฉพาะ
routes/index.jsแล้ว export จาก controller ที่ชัดเจน; อย่าใช้routes/get-post-upload_image.js - อย่าสับสน
combineJobPostingController(live) กับmodule_2_3_combine_image(dead) - ก่อนแก้ prompt ของ character — อ่าน
PROMPT_RULES+isFirstTimeในmodule2_2_controller.jsให้ครบ เพราะมีสอง path (local gallery vs DALL-E) - ตรวจ
encryptของ MSSQL ให้ตรงกับ environment จริง — โค้ดมีทั้งtrueและfalse
ไป บท 2 Core pipeline →