Skip to content

2way-api — Repository Map

บทนี้เป็นแผนที่ไฟล์ของ 2way-api (branch: prod, 178 tracked files) — NestJS 9 + TypeORM + MSSQL สำหรับฟีเจอร์สื่อสารสองทาง (ประกาศ / แบบสำรวจ / เรื่องร้องเรียน) กับพนักงานผ่าน LINE และ backoffice

ชื่อ package ใน package.json คือ vtrc-restfull-v2 (2way-api/package.json:2) — ไม่ตรงกับชื่อ repo; เป็นร่องรอย scaffold ร่วมกับ 2way-batch / 2way-line-service


แผนที่โฟลเดอร์ระดับ repo

2way-api/                          (branch: prod)
├── package.json                   # name=vtrc-restfull-v2, NestJS ^9, TypeORM ^0.3.11
├── yarn.lock                      # package manager จริงใน CI คือ yarn
├── nest-cli.json
├── tsconfig.json / tsconfig.build.json
├── .env.example / .env.uat / .env.prod
├── Dockerfile / Dockerfile.prod / Dockerfile.uat
├── docker-compose.yml / docker-compose.cloud.yml
├── bitbucket-pipelines.yml        # CI/CD (ไม่ใช่ GitHub Actions)
├── endpoint.txt                   # รายการ endpoint มือเขียน (อ้างอิงเท่านั้น)
├── config/
│   └── configuration.ts           # ConfigModule factory (port, db, line tokens, status codes)
├── docs/
│   └── document.config.ts         # Swagger DocumentBuilder
├── libs/
│   ├── jwt/
│   │   ├── jwt-auth.guard.ts      # global JwtAuthGuard + @Public()
│   │   └── jwt.strategy.ts        # passport-jwt Strategy name 'jwt'
│   ├── exception/
│   │   └── http-exception.filter.ts
│   ├── log/
│   │   └── logs.middleware.ts     # I3GatewayLogger
│   └── common/
│       ├── base64-to-file.ts
│       ├── ganarate-random-string.ts
│       └── validator-date.ts
├── nginx/                         # conf / conf.d (reverse proxy snippets)
├── sql/
│   ├── 20230412_create_structure_table_prod.sql
│   └── backup/
├── uploads/                       # static files served ที่ prefix /2way-api/
├── test/
└── src/
    ├── main.ts                    # bootstrap
    ├── app.module.ts              # root wiring
    ├── app.controller.ts          # empty @Controller()
    ├── app.service.ts
    ├── constants/
    ├── dto/
    ├── entities/                  # 41 entity files (+ base.ts)
    ├── modules/
    ├── crontab/
    └── utils/

Boot sequence — จาก yarn start:prod ถึง listen

Production entry คือ node dist/src/main (2way-api/package.json:18). ลำดับใน main.ts:

1. NestFactory.create(AppModule, { logger: I3GatewayLogger('System') })
       │  `2way-api/src/main.ts:16-18`

2. อ่าน ConfigService → port = config.get('port') || 3000
       │  `2way-api/src/main.ts:21-22`
       │  (.env.example ตั้ง PORT=8200 ที่ `2way-api/.env.example:5`)

3. setGlobalPrefix(`${BASE_PATH}api/v1`)
       │  `2way-api/src/main.ts:24-26`
       │  BASE_PATH=2way-api/ → prefix = 2way-api/api/v1

4. useStaticAssets(uploads) prefix '/2way-api/'
       │  `2way-api/src/main.ts:27-29`

5. bodyParser json/urlencoded limit 50mb
       │  `2way-api/src/main.ts:30-31`

6. useGlobalFilters(HttpExceptionFilter)
   useGlobalGuards(JwtAuthGuard)
   useGlobalPipes(ValidationPipe whitelist+forbidNonWhitelisted)
       │  `2way-api/src/main.ts:32-34`

7. enableCors({ origin: '*', allowedHeaders: '*', ... })
       │  `2way-api/src/main.ts:35-39`

8. SwaggerModule.setup(`${BASE_PATH}docs`, ...)
       │  `2way-api/src/main.ts:41-42`

9. app.listen(port, '0.0.0.0')
       `2way-api/src/main.ts:43-46`

ข้อควรจำเรื่องพอร์ต: fallback ในโค้ดคือ 3000 แต่ค่าที่ทีมตั้งใน .env.example คือ 8200 — ห้ามสรุปว่า "ฟังที่ 3000" โดยไม่มีเงื่อนไข


AppModule — สิ่งที่ถูกโหลดจริง

AppModule (2way-api/src/app.module.ts:20-64) import ตามลำดับ:

ลำดับImportบทบาท
1ConfigModule.forRoot({ load: [configuration], isGlobal: true })env + status-code maps
2TypeOrmModule.forRootAsync name 'main'MSSQL connection
3AppModule (ตัวเอง)self-import ผิดปกติ — ดูด้านล่าง
4AnnouncesModuleประกาศ
5SurveysModuleแบบสำรวจ
6ComplaintsModuleเรื่องร้องเรียน
7EmployeesModuleproxy พนักงาน → centralize-api
8LineModuleLINE push/multicast
9UploadModuleอัปโหลดไฟล์ base64
10AuthModulelogin / bypass / verifyToken
11ComplaintTaskModulecron ร้องเรียน 07:00
12SurveyTaskModulecron แบบสำรวจ 07:00
13AnnounceTaskModulecron ประกาศ 07:00
14NotificationPmsTaskModuleโมดูล PMS (cron handler ถูก comment)

providers ระดับ root: AppService, JwtStrategy (2way-api/src/app.module.ts:61-62)

controllers ระดับ root: AppController ว่าง (2way-api/src/app.controller.ts:3)

Self-import ของ AppModule

typescript
imports: [
  // ...
  AppModule,   // บรรทัด 47
  AnnouncesModule,
  // ...
]

AppModule import ตัวเองเข้า imports (2way-api/src/app.module.ts:47) — pattern ผิดปกติ น่าจะเป็น copy-paste; ไม่มีหลักฐานว่าตั้งใจทำ circular provider


Dead code — โมดูลที่มีไฟล์ครบแต่ไม่ถูก wire

ยืนยันจาก app.module.ts ว่า ไม่มี import ของโฟลเดอร์เหล่านี้:

โฟลเดอร์มี controller/service/moduleถูก import ใน AppModule?
src/modules/announces-new/ใช่ (5 ไฟล์ รวม spec)ไม่
src/modules/employees-new/ใช่ (5 ไฟล์ รวม spec)ไม่
src/modules/surveys/old_surveys/ใช่ (module + repository + controller)ไม่
src/modules/complaints/old_complaints/มี complaints.repository.ts (ส่วนใหญ่ comment)ไม่
src/crontab/complaint/old_complaint/มี task module/serviceไม่
src/crontab/survey/old_survey/มี task module/serviceไม่

announces-new และ employees-new มี @Public() ครบชุดบน controller แต่ ไม่ถูกโหลด จึงไม่มี HTTP surface จากโฟลเดอร์เหล่านี้ใน runtime


Module breakdown — โมดูลที่ทำงานจริง

auth (src/modules/auth/)

ไฟล์บรรทัดโดยประมาณหน้าที่
auth.controller.ts303 routes ทั้งหมด @Public()
auth.service.ts358SSO login, backdoor i3admin, redirect bypass, verifyToken
auth.module.ts9controllers + providers เท่านั้น ไม่มี TypeOrmFeature

Routes (2way-api/src/modules/auth/auth.controller.ts:13-28):

MethodPath (ภายใต้ prefix)Handler
POSTauth/signInNonEmp
POSTauth/redirectbypasssignInRedirectByPass
POSTauth/verifyTokenverifyToken

announces (src/modules/announces/)

ไฟล์บรรทัดโดยประมาณหน้าที่
announces.controller.ts103list/detail/CRUD/read/know + CU variants
announces.service.ts1489raw SQL list + TypeORM write + LINE side-effect
announces.module.ts18HttpModule + UploadService + LineService + ProfileUtil

Routes สำคัญ (announces.controller.ts):

MethodPath@Public()?
GETannounces/cu/listใช่
GETannounces/cu/detail/:contentIdใช่
GETannounces/typesไม่ (ต้อง JWT)
GETannouncesใช่
GETannounces/:contentIdใช่
POSTannouncesไม่
PUTannounces/:contentIdไม่
POSTannounces/readContentใช่
POSTannounces/knowContentใช่
DELETEannounces/:contentIdไม่
POSTannounces/test/noti/:idไม่

surveys (src/modules/surveys/)

ไฟล์บรรทัดโดยประมาณหน้าที่
surveys.controller.ts136types/create/summary/list/detail/approve/reject/read
surveys.service.ts1199workflow แบบสำรวจ + raw SQL บางจุด
surveys.module.tswire ปกติ

@Public() ที่เปิดอยู่จริงมีแค่ GET /surveys และ GET /surveys/:id/:empCode (surveys.controller.ts:31-50); create/approve/reject ต้อง JWT

complaints (src/modules/complaints/)

ไฟล์บรรทัดโดยประมาณหน้าที่
complaints.controller.ts213เกือบทุก route เป็น @Public() รวม workflow states
complaints.service.ts2012ใหญ่สุดใน repo — state machine ร้องเรียน

Routes ที่ @Public() รวมถึง POST complaints, adminAssign, adminReject, assigneeManage, secretaryApprove, secretaryDecline, superAdminApproval, superAdminReject (complaints.controller.ts:13-114)

employees (src/modules/employees/)

ไฟล์บรรทัดโดยประมาณหน้าที่
employees.controller.ts168proxy REST ไป centralize-api + stub CRUD manager
employees.service.ts273axios → ${CENTRALIZE_SERVER}api/employee/...
EmpCodeMiddlewareใน controller fileถูก comment ทั้งก้อนnext() ตรงๆ

Controller path คือ @Controller('employee') ไม่มี s (employees.controller.ts:55)

line (src/modules/line/)

ไฟล์บรรทัดโดยประมาณหน้าที่
line.controller.ts36@Controller('Line') — L ใหญ่
line.service.ts93push / multicast / nextPage / previousPage
common/index.tsfetchAlways retry helper

ทุก route เป็น @Public() (line.controller.ts:13-34)

uploadFiles (src/modules/uploadFiles/)

ไฟล์บรรทัดโดยประมาณหน้าที่
upload.controller.ts42GET :id, POST create, POST complaints (ซ้ำ path @Post())
upload.service.ts233base64 → /uploads/{prefix}/{YYYY-MM-DD}

MIME ที่อนุญาต: jpeg/jpg/png/pdf/mp4 (upload.service.ts:44-45)


Crontab modules

ทุก task module เรียก ScheduleModule.forRoot() ภายในโมดูลตัวเอง (ไม่ใช่ครั้งเดียวที่ root):

ModuleCron expressionHandlerสถานะ
ComplaintTaskModuleEVERY_DAY_AT_7AMhandleCron → 4 งานแจ้งเตือนเปิด
SurveyTaskModuleEVERY_DAY_AT_7AMin-scope + out-scope notifyเปิด
AnnounceTaskModuleEVERY_DAY_AT_7AMnotificationOfferDateเปิด
NotificationPmsTaskModulehandleCron ถูก comment ทั้งก้อนไม่รัน

หลักฐาน PMS cron ปิด: notification-pms.task.service.ts:19-23 — method notificationLinePms() ยังมีโค้ด SQL ข้าม schema แต่ไม่มี @Cron ที่ active

Complaint cron ลำดับงาน (complaint.task.service.ts:21-38):

  1. noActionAssignment — แอดมินไม่มอบหมายใน 3 วัน
  2. noActionIsKnow3Days — ผู้รับมอบหมายไม่กดรับทราบ 3 วัน
  3. noActionIsKnow7Days — ไม่กดรับทราบ 7 วัน
  4. noActionAskApproval — กดรับทราบแล้วไม่ส่งกลับเกิน 14 วัน

Entities — 41 ไฟล์ + base

BaseEntity (src/entities/base.ts) ให้คอลัมน์ร่วม: id, create_date, create_by, update_date, update_by

TypeORM โหลดด้วย glob __dirname + '/**/*.entity{.ts,.js}' (app.module.ts:38) — ครอบคลุมทุกไฟล์ใต้ src/entities/

Master (m_*)

Entity fileTableใช้โดย
m_config.entity.tsm_configconfig key/value
m_content_flow.entity.tsm_content_flowflow stage ของ content
m_content_type.entity.tsm_content_typeประเภท content (Announce/...)
m_error_message.entity.tsm_error_messageข้อความ error
m_reuse_survey.entity.tsm_reuse_surveyประเภท reuse survey
m_role.entity.tsm_rolerole + action_view
m_status.entity.tsm_statusstatus กลาง
m_status_complaint.entity.tsm_status_complaintstatus ร้องเรียน
m_status_survey.entity.tsm_status_surveystatus แบบสำรวจ
m_subject_complaint.entity.tsm_subject_complaintหัวข้อร้องเรียน
m_topic.entity.tsm_topicหัวข้อประกาศ
m_type_complaint.entity.tsm_type_complaintประเภทร้องเรียน
m_type_survey.entity.tsm_type_surveyประเภทแบบสำรวจ
m_type_survey_ignore.entity.tsm_type_survey_ignoreignore type
m_user_manager.entity.tsm_user_managerรายชื่อ manager กรองออกจาก employee list

Transaction (t_*)

Entity fileTableDomain
t_content.entity.tst_contentประกาศ (content กลาง)
t_content_assign.entity.tst_content_assignมอบหมายรายคน
t_content_assign_all.entity.tst_content_assign_allมอบหมายแบบ ALL
t_content_assign_survey.entity.tst_content_survey_allsurvey assign-all
t_content_complaint.entity.tst_content_complaintร้องเรียน
t_content_flow_history.entity.tst_content_flow_historyประวัติ flow
t_content_manager.entity.tst_content_managermanager ของ content
t_content_status.entity.tst_content_statusสถานะ content
t_content_survey.entity.tst_content_surveyแบบสำรวจ
t_content_survey_ignore.entity.tst_content_survey_ignoreignore list
t_assign_complaint.entity.tst_assign_complaintมอบหมายร้องเรียน
t_assign_survey.entity.tst_assign_surveyมอบหมายแบบสำรวจ
t_attachment_announce.entity.tst_attachment_announceไฟล์แนบประกาศ
t_attachment_complaint.entity.tst_attachment_complaintไฟล์แนบร้องเรียน
t_thumbnail_announce.entity.tst_thumbnail_announcethumbnail
t_history_complaint.entity.tst_history_complaintประวัติร้องเรียน
t_history_survey.entity.tst_history_surveyประวัติแบบสำรวจ
t_reuse_survey.entity.tst_reuse_surveyรอบ reuse
t_send_notify.entity.tst_send_notifyคิวแจ้งเตือน
t_logs_notification.entity.tst_logs_notificationaudit log การส่ง LINE
t_change_divicode_complaints.entity.tst_change_divicode_complaintsเปลี่ยนฝ่าย
temp_complaint_role.entity.tstemp_complaint_roletemp role
temp_survey.entity.tstemp_surveytemp survey

User / LINE

Entity fileTableคอลัมน์สำคัญ
userLine.entity.tsuserLineuserID, identityCard, lineID, richMenuID, isActive
userLineProfile.entity.ts(profile table)โปรไฟล์ LINE ขยาย — ไฟล์ใหญ่ (~6 KB)

โมเดลข้อมูลหลักของ announces ใช้ t_content + m_content_type แยกโดเมนด้วย name_en (เช่น 'Announce') — surveys/complaints มีตาราง t_content_survey / t_content_complaint แยกต่างหาก ไม่ได้แชร์ t_content เหมือนกันทุกกรณี


DTO / constants / utils

src/dto/ (9 ไฟล์)

ไฟล์ใช้โดย
announce.dto.tsannounces
auth.dto.tsauth (username, password + @IsString)
common.dto.tsshared
complaint.dto.tscomplaints (ใหญ่)
employee.dto.tsemployees
line.dto.tsline
survey.dto.tssurveys (ใหญ่)
upload.dto.tsupload
user.dto.tsuser-related

src/constants/

ไฟล์เนื้อหา
complaints.constant.tsaction mapping (SECRETARY_*, config keys)
surveys.constant.tssurvey action / status constants

src/utils/

ไฟล์หน้าที่
profiles.util.tsถอด JWT จาก Authorization ด้วย SECRET_JWT_TWOWAY
encrypt.util.tsAES ผ่าน crypto-js; default secret 'CHANGE_ME_PLS'
surveys.util.tshelper + raw query สำหรับ survey
complaints.util.tshelper mapping action/status
ramdomText.util.tsrandom string (ชื่อสะกดผิดในไฟล์)

libs/ — cross-cutting

JwtAuthGuard + @Public()

typescript
canActivate(context) {
  const isPublic = this.reflector.getAllAndOverride<boolean>(isPublicKey, [...]);
  if (isPublic) return true;
  return super.canActivate(context);
}

แหล่ง: 2way-api/libs/jwt/jwt-auth.guard.ts:15-24
decorator: export const Public = () => SetMetadata(isPublicKey, true) (jwt-auth.guard.ts:41)

JwtStrategy

  • Extract จาก Authorization: Bearer
  • ignoreExpiration: false ที่ strategy (jwt.strategy.ts:13)
  • secretOrKey: process.env.SECRET_JWT_TWOWAY (jwt.strategy.ts:14)
  • validate คืน payload ทั้งก้อน (jwt.strategy.ts:19-22)

หมายเหตุ: AuthService.verifyJwt ใช้ ignoreExpiration: true คนละที่กับ strategy (auth.service.ts:34) — เส้นทาง verifyToken / redirectbypass จึงยอมรับ token หมดอายุได้

HttpExceptionFilter

จับเฉพาะ HttpException แล้วตอบ { statusCode, message } (http-exception.filter.ts:5-19) — error ที่ throw เป็น string ธรรมดา (เช่น 'No Bearer token') จะไม่ผ่าน filter นี้ในรูปแบบเดียวกับ Nest exception


Config และ env

config/configuration.ts map:

  • port, jwtSecretKeySECRET_KEY
  • db.host/port/user/password/databaseName
  • lineToken, lineCUToken
  • contentStatus.announcement|survey|complaint — status code tables ฝังในโค้ด

env ที่ service อ่านตรงจาก process.env (ไม่อยู่ใน configuration factory):

ตัวแปรใช้ที่
BASE_PATHmain.ts prefix
SECRET_JWT_TWOWAYJWT sign/verify
SECRET_KEYverify token จาก VTRC ใน redirectbypass
CENTRALIZE_SERVERemployees → NestJS centralize-api REST
CENTRALIZE_API_URLauth → cu-central-api GraphQL
VTRC_BASE_CONNECTGraphQL ไป vtrc-api (ถูก comment ใน caller)
API_KEY_OUTBOUND_VTRCapiKey header ไป vtrc-api
SSO_*SSO login (มี hardcoded fallback ใน AuthService)

ดูรายละเอียด integration ที่ 03-persistence-integrations


Deploy / CI

ไฟล์หมายเหตุ
Dockerfile.prodmulti-stage: build บน node:16-alpine, run บน node:18-alpine; copy .env.prod.env
Dockerfile.uatโครงเดียวกันสำหรับ UAT
Dockerfiledefault/dev
bitbucket-pipelines.ymlimage node:16; yarn build/lint; docker build → GCR
docker-compose.ymllocal compose
docker-compose.cloud.ymlcloud compose สั้น

engines.node ใน package.json คือ >=18.0.0 (package.json:27) แต่ build stage และ Bitbucket image ยังเป็น Node 16


แผนที่ความสัมพันธ์กับ repo อื่นในกลุ่ม 2way

2way-meeting-backoffice
  APP_CONNECT_API=.../2way-api


   2way-api (repo นี้)
        ├── REST → centralize-api (CENTRALIZE_SERVER)
        ├── GraphQL → cu-central-api (CENTRALIZE_API_URL)
        ├── GraphQL → vtrc-api (VTRC_BASE_CONNECT) [caller comment ปิด]
        ├── HTTPS → api.line.me
        └── HTTP  → 2way-line-service ที่ IP:8200 (hardcoded)

Consumer ที่ยืนยันจาก frontend env: 2way-meeting-backoffice ตั้ง APP_CONNECT_API ชี้มาที่ /2way-api — ดูรายละเอียดในบท 03


สิ่งที่บทนี้ไม่ครอบคลุม


Checklist สำหรับผู้อ่านที่เพิ่งเข้า repo

  1. Checkout branch prod ก่อนอ่านโค้ด (default master อาจว่าง/ไม่ครบ)
  2. อ่าน app.module.ts เพื่อรู้ว่าโมดูลไหน live
  3. อย่าสมมติว่า announces-new / employees-new ทำงาน — พวกมัน dead
  4. ทุก request ผ่าน JwtAuthGuard ยกเว้น @Public() — และ @Public() ถูกใช้กว้างมาก
  5. DB คือ MSSQL schema 2way (ชื่อจาก .env.example); PMS อยู่ schema pms บน instance เดียวกัน