Skip to content

7.1.3 · Persistence & integrations (V1 depth)

branch: prod-meeting-vtrc-api

บทนี้ลงลึก Sequelize models ทุกตาราง, ความสัมพันธ์, และ outbound integrations (vtrc-api, employee directory, SMTP, PDF, Tinify)


Persistence จริง — Sequelize + MSSQL

util/db.config.ts:1-25 สร้าง instance เดียว:

javascript
const SequelizeDB = require('sequelize');
const {env} = require('./env.ts');
const sequelizeDB = new SequelizeDB(env.database, env.username, env.password, {
  host: env.host,
  dialect: env.dialect,
  port: env.port,
  dialectOptions: {
    options: {
      useUTC: false,
      encrypt: false,
    }
  },
  timezone: env.timezone,
  operatorsAliases: 0,
  pool: {
    max: env.pool.max,
    min: env.pool.min,
    acquire: env.pool.acquire,
    idle: env.pool.idle
  },
  define: {
    freezeTableName: true
  }
});
Configค่า / ที่มาหลักฐาน
dialectprocess.env.DIALECTmssql ใน .envprodutil/env.ts:11, .envprod
port1433 (prod placeholder) / 1436 (uat).envprod, .envuat
encryptfalsedb.config.ts:10 — ไม่ใช้ TLS ไปยัง MSSQL ตาม default tedious
timezonehardcode Asia/Bangkokenv.ts:10
poolmax 5, acquire 30000, idle 10000env.ts:12-17
boot checkauthenticate() เท่านั้นdb.config.ts:27-33
sync/migrateไม่พบ .sync() หรือ migration folder

Env โหลดจาก ./.env (env.ts:1-3) — ต้องมีขั้นตอน copy จาก .envdev/.envuat/.envprod (Docker: Dockerfile:24)

Credential ที่ comment ค้างใน env.ts:19-64 (host IP, password ตัวอย่าง) เป็นประวัติ environment เก่า — อย่าใช้เป็นค่าปัจจุบันโดยไม่ verify


Mongoose — ประกาศไว้แต่ตายแล้ว

util/mongo.config.ts:1-14 (ทั้งไฟล์ comment):

javascript
// const mongoose = require('mongoose')
// const portmongo = "27017";
// const dbconnect = '206.189.94.232';
// const username = 'dbuser'
// const password = 'databasebanzai'
// ...
// mongoose.connect('mongodb://'+username+':'+password+'@'+dbconnect+':'+portmongo+'/'+database+"?authSource=admin", ...)

app.ts:8 ยัง require ไฟล์นี้ — side-effect ว่าง; credential ในคอมเมนต์เป็นของ MongoDB เก่า (อย่า rotate เป็นของ MSSQL โดยสับสน)


วิธีโหลด model

util/db.config.ts:39-43:

javascript
db.Approver = require('../model/approver/approver.ts')(sequelizeDB, SequelizeDB);
db.Group = require('../model/usergroup/usergroup.ts')(sequelizeDB, SequelizeDB);
db.Meeting = require('../model/meeting/meeting.ts')(sequelizeDB, SequelizeDB);
db.login = require('../model/login.ts')(sequelizeDB, SequelizeDB);
db.* keyไฟล์ตารางที่ export
db.Approvermodel/approver/approver.tsApprover
db.Groupmodel/usergroup/usergroup.tsGroupMeeting, UserGroup
db.Meetingmodel/meeting/meeting.ts11 ตาราง (ด้านล่าง)
db.loginmodel/login.tsuser, user_group

Route ส่วนใหญ่ destructure จาก db.Meeting เช่น const { Meeting, Guest } = db.Meeting


ตาราง domain การประชุม — รายละเอียดฟิลด์

แหล่งเดียว: model/meeting/meeting.ts (565 บรรทัด) · associations ที่ :526-550

ทุก association ตั้ง constraints: false — Sequelize ไม่สร้าง FK จริงบน MSSQL; referential integrity พึ่ง application

Meeting (meeting.ts:236-314) · table name Meeting

FieldTypeหมายเหตุ
idINTEGER PK AI
subject, location, detailSTRING
approveIDINTEGERผู้อนุมัติ (emp-related id)
organizerIDSTRING
requireMeetingSummaryBOOLEANrequired
requireApproveBOOLEANrequired
meetingURLSTRING
startDate, endDateDATEdefault CURRENT_TIMESTAMP
documentDateDATEnullable — ใช้เป็น executeTime ของ job
isActiveINTEGER1=active, 2=draft, 0=deleted (จาก soft-delete)
isApprovedINTEGER0 pending / 1 approve / 2 reject / 3 after edit
remarkIDINTEGERชี้ Remark
created, updateDateDATE
summaryDueDateDATEnullable

Guest (:3-64) · table Guest

FieldTypeหมายเหตุ
idINTEGER PK
meetingIDINTEGERref Meeting (ORM only)
userIDSTRINGempCode
isActiveBOOLEANdefault 1
isStatusResponseINTEGERref StatusResponse
isDelegateBOOLEANdefault 0
delegateIDINTEGERref Delegate
created, updateDateDATE
noteSTRING

ความหมาย isStatusResponse จาก flow ตอบรับ: 1=pending, 2=join, 3=reject, 4=delegate (acceptancemeeting.ts)

OtherGuest (:66-128) · table OtherGuest

แขกภายนอก: name, email, asRole + สถานะตอบรับเหมือน Guest

หมายเหตุ association bug: OtherGuest.belongsTo(OtherGuest, …) ที่ :531 — น่าจะตั้งใจ belongsTo(Meeting) — ส่งผลต่อ include ที่พึ่ง relation นี้อาจผิดพลาด

Attached (:130-183) · table Attached

Fieldหมายเหตุ
meetingID / summaryIDผูกกับประชุมหรือสรุป
fileName, fileNameGen, fileType
fileDataTEXT — เก็บ base64/path ซ้ำกับไฟล์บน disk

Summary (:185-234) · table Summary

สรุปการประชุม: ไฟล์ + meetingID + userID + timestamps

StatusResponse (:316-332) · table StatusResponse

Lookup: id, statusName — seed อยู่นอกโค้ด (UNVERIFIED)

RemarkMeeting define name "Remark" (:334-362) · table Remark

detail, meetingID, createDate — ใช้ตอนอนุมัติมีหมายเหตุ

CommentSummaryMeeting define "Comment" (:364-403) · table Comment

changeDetail, status, meetingID, userID, timestamps — ความเห็นต่อสรุป

TokenMeeting define "Token" (:405-429) · table Token

Fieldใช้ทำ
tokenMeetingJWT ที่ออกให้ฝั่ง meeting
profileKeyผูกกับ profile จาก vtrc
createDate

ถูก stamp ใน checkbypasswhenredirect.ts:109-127 และอ่านใน checkbypass.ts:81-89

Delegate (:431-478) · table Delegate

delegateType (internal/external), meetingID, userID, name, email, asRole, timestamps

ScheduleJob (:480-525) · table ScheduleJob

Fieldหมายเหตุ
meetingID
executeTimeเมื่อไหร่ควรรัน
jobTypeเช่น "EMAIL"
jobStatusACTIVE / DONE / CANCLE
notiTypeinvite / required_approve / summary
created, updateDate

Relation: Meeting.hasOne(ScheduleJob) (:549-550) — logic checkMeethingScheduleExist ถือว่าหนึ่ง meeting หนึ่ง job


ผัง relation (ORM metadata)

Meeting 1──N Guest
Meeting 1──N OtherGuest
Meeting 1──N Attached
Meeting 1──N Summary
Meeting 1──N Remark          (RemarkMeeting)
Meeting 1──N Comment         (CommentSummaryMeeting)
Meeting 1──1 ScheduleJob
Summary 1──N Attached
Delegate 1──1 Guest          (via Guest.delegateID)
Delegate 1──1 OtherGuest
StatusResponse 1──1 Guest / OtherGuest  (via isStatusResponse)

หลักฐาน: model/meeting/meeting.ts:526-550


Approver + UserGroup

Approver (model/approver/approver.ts:1-41)

FieldType
idINTEGER PK
empCodeSTRING
sourceDBSTRING
orgCodeSTRING
createDate, updateDateDATE

ใช้โดย route /meeting/secure/approver/* สำหรับ backoffice ตั้งรายชื่อผู้อนุมัติ

GroupMeeting + UserGroup (model/usergroup/usergroup.ts:1-76)

GroupMeeting 1──N UserGroup (groupID, constraints: false)
GroupMeetingUserGroup
id, groupName, createDate, updateDateid, userID, groupID, sourceDB, timestamps

Route: /meeting/secure/group/* และ /meeting/secure/groupmeeting/* (duplicate legacy)


model/login.ts — นอก domain

ตาราง user / user_group มีฟิลด์เช่น user_gametype, user_register_channel, user_register_source, user_password (login.ts:56-71) — ลักษณะระบบเกม/community

โหลดตอน boot (db.config.ts:43) และถูก destructure ใน middleware/checkbypass.ts:3 (const { user, user_group } = db.login) แต่ ไม่พบการ query user ใน middleware นั้น — dead import ของตาราง

ไม่พบ route ที่ใช้ db.login เป็น persistence ของการประชุม — QUAL-MTVTRC-02


Integration ภายนอก

ตารางสรุป

ปลายทางClientEnv / hardcodeใช้ทำหลักฐาน
vtrc-api GraphQLvtrcConnectorVTRC_BASE_CONNECT, header apiKey=API_KEY_OUTBOUND_VTRC(1) profile ใน redirect middleware (2) push sendNotificationFrom3Partyvtrc.connector.ts:5-10, sendNoti.ts:81-98, checkbypasswhenredirect.ts:69-71
Employee / "TwoWay" GraphQLcdbConnectorCDB_BASE_CONNECT; header api-key hardcode; Bearer JWT hardcode ใน interceptorfetchListEmployeeForTwoWay map ชื่อ-อีเมลcdb.connector.ts:3-26
SMTP Outlooknodemailerhost/port hardcode; MAIL_PASS จาก envอีเมลเชิญ + iCalEmailsender.func.ts:54-65
PDFhtml-pdf-node / puppeteerใน container (CHROMIUM_PATH)หนังสือเชิญ / ลายเซ็นpdfgenbyhtml.func.ts, Dockerfile:4-6
Tinifytinify SDKkey hardcode ในซอร์สควรบีบอัดรูป — call ถูก commentUploadimage.class.ts:5,29-30
Sentry@sentry/nodeDSN hardcodeerror/tracingapp.ts:79-86

cdbConnector — hardcode ที่ต้องรู้

util/axiosinterceptors/cdb.connector.ts:3-26:

javascript
const cdbConnector = axios.create({
    baseURL: process.env.CDB_BASE_CONNECT,
    timeout: 10000,
    headers: { 'api-key': 'eyJz-CI6Ikp-4pWY-lhdCI6' }
});
cdbConnector.interceptors.request.use((request) => {
    request.headers.Authorization = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9....';
    return request;
});
  • api-key และ Bearer ฝังในซอร์ส (ไม่ใช่ env)
  • ใน .envuat CDB_BASE_CONNECT ชี้ http://uat-centralizeapi.redcross.or.th:25563/api-uat/graphql — สอดคล้องกับ centralize/employee API ไม่ใช่ 2way-meeting-backoffice

Query name fetchListEmployeeForTwoWay หมายถึง meeting-vtrc-api เป็นฝ่ายเรียกออก ไปหา employee directory ที่ทีมเรียกว่า TwoWay — ไม่ได้พิสูจน์ว่า 2way-meeting-backoffice เรียกเข้า service นี้

ตรวจใน 2way-meeting-backoffice ด้วย grep หา /meeting/secure / MEETING_API แล้วไม่พบ reference โดยตรงในรอบเอกสารก่อนหน้า — สรุป inbound จาก 2way-meeting-backoffice เป็น UNVERIFIED

vtrc push bridge (ย้ำจาก pipeline)

ดูรายละเอียด mutation ที่ 7.1.2 Push notification bridge — ที่นี่สรุปเฉพาะ transport:

  • Timeout 10s (vtrc.connector.ts:8)
  • ไม่มี response interceptor / retry
  • ไม่มี Authorization Bearer บน connector (มีแต่ apiKey) — ต่างจาก commented sample ที่ :12-16

PDF generation

ขั้นไฟล์
Template EJSutil/pdftemplate/sign_join_meeting.ejs, layout.html.ejs
Orchestrationutil/helper/createSignnature.helper.ts
Renderlib/pdfgenbyhtml.func.ts:6-26html-pdf-node.generatePdf
Alternate mergepdfGeneraterAndReturnBufferDataNa (:28-56) — puppeteer.launch({ args: ["--no-sandbox", ...] }) + pdf-merger-js

Docker ติดตั้ง Chromium และตั้ง PUPPETEER_EXECUTABLE_PATH (Dockerfile:4-6) — จำเป็นสำหรับ PDF ใน Alpine

--no-sandbox ลด isolation ของ Chromium — พื้นผิวเสี่ยงถ้ารender HTML จาก input ผู้ใช้โดยไม่ sanitize (UNVERIFIED ระดับ sanitize ของ EJS)

Fonts: fonts/ copy ไป /usr/share/fonts/kanit ใน image (Dockerfile:23)


File storage

Pathบทบาท
./storage/meeting/ไฟล์อัปโหลด + PDF
HTTP /meeting/fileexpress.static (app.ts:147) — ไม่มี auth (guard ที่เคยมีถูก comment ที่ app.ts:90-103)
Compose volume/home/hrappladmin/storage/meeting/app/storage/meeting

ไฟล์แนบเก็บทั้งบน disk และคอลัมน์ Attached.fileData — เพิ่มขนาด DB และความเสี่ยง data exfiltration ผ่าน API ที่คืนแถว Attached


Env inventory (ชื่อตัวแปรเท่านั้น — ห้ามคัดลอกค่า secret)

จาก .envprod / .envuat / .envdev (โครงสร้างคล้ายกัน):

Varบทบาท
API_KEYinbound x-api-key
API_KEY_OUTBOUND_VTRCheader ไป vtrc-api (ชื่อในไฟล์อาจเป็น API_KEY_OUTBOUND — ตรวจให้ตรงตอน deploy)
SECRET_JWT_VTRC / SECRET_JWT_MEETINGverify / sign ใน redirect middleware
CDB_BASE_CONNECTemployee GraphQL
VTRC_BASE_CONNECTvtrc GraphQL
MAIL_PASSSMTP
DATABASE / USERNAME / PASSWORD / HOST / PORT / DIALECTSequelize
BASE_URL / FILE_PATH / PROJECT_URLURL ประกอบลิงก์ไฟล์

.envuat มี password/host จริงใน plaintext ที่ commit แล้ว — ดู SEC-MTVTRC-01


สิ่งที่ไม่มีใน repo

  • SQL migration / seed สำหรับ StatusResponse
  • CI config (Bitbucket/GitHub Actions) ในโฟลเดอร์นี้ — UNVERIFIED pipeline ภายนอก
  • Redis / queue อื่นนอก ScheduleJob table
  • WebSocket / GraphQL server ของตัวเอง (เป็น REST caller เท่านั้น)