Skip to content

3 · Persistence & integrations — chat-center-api

อ้างอิง branch: prod


Data stores overview

StoreConnection nameDatabaseHost (จาก .env)synchronizeหลักฐาน
MSSQLmain2way10.188.128.61:1433trueapp.module.ts:33-38, configuration.ts:7-21, .env:7-12
MSSQLcenterdbHRMI_Center10.188.128.61:1433falseapp.module.ts:39-44, configuration.ts:22-37, .env:14-19
MongoDB(Mongoose default)chatDB10.188.128.64:27017 (จาก MONGODB URI)n/aapp.module.ts:45-51, .env:26
Local disk./uploads/{YYYY-MM-DD}/container filesystemn/achat.gateway.ts:82-87

NODE_ENV=uat ใน .env ที่ commit อยู่ (chat-center-api/.env (branch: prod):1) ทั้งที่ SERVER_URL ชี้ production API host — ค่า env ผสมระหว่าง UAT/prod ในไฟล์เดียว

Config ของ main

typescript
db: {
  type: 'mssql',
  host: process.env.DB_HOST,
  // ...
  database: process.env.DB_NAME,
  synchronize: true,
  logging: [`error`, `query`],
  entities: [__dirname + '/../src/entities/*.entity{.ts,.js}'],
  extra: { trustServerCertificate: true, encrypt: false },
}

หลักฐาน — chat-center-api/config/configuration.ts (branch: prod):7-21

synchronize: true ใน environment ที่ชี้ IP production = ความเสี่ยง schema drift / data loss (CORR-CHAT-02)

Config ของ center

  • อ่านจาก DB_HRMI_*
  • autoLoadEntities: false, synchronize: false
  • entities path ชี้ process.cwd() + '/dist/**/*.entity{.ts,.js}' — แต่โค้ดใช้ entityManagerCenter.query() raw SQL เป็นหลัก ไม่พึ่ง entity ของ center

หลักฐาน — configuration.ts:22-37


MSSQL entities (main / database 2way)

ทุก entity extend BaseEntity:

ColumnTypeDefaultหลักฐาน
idincrement PKbase.ts:4-5
createdAtdatetimeCURRENT_TIMESTAMPbase.ts:7-8
createdByvarchar(50)'SYSTEM ADMIN'base.ts:10-11
updatedAtdatetimeCURRENT_TIMESTAMPbase.ts:13-14
updatedByvarchar(50)'SYSTEM ADMIN'base.ts:16-17

chatRoomChatRoomEntity

ตารางหลักของห้องแชท — unique (empCode, sourceDB)

Columnหมายเหตุหลักฐาน
empCode, identityCard, fullNameข้อมูลพนักงานจาก HRMI ตอนสร้างห้องchatRoom.entity.ts:9-16
orgUnitCode, orgName, position*, divi*, dept*โครงสร้างองค์กรchatRoom.entity.ts:18-42
sourceDBtenant key ข้ามหน่วยงาน (default 'test')chatRoom.entity.ts:44-45
tagsIdJSON string ของ array id เช่น '["1","2"]' — ไม่ใช่ relationchatRoom.entity.ts:47-48
careTakerName, careTakerEmpCodeผู้ดูแลปัจจุบันchatRoom.entity.ts:50-54
noteTagJSON string ของ empCode ที่ถูกแท็กในโน้ตchatRoom.entity.ts:56-57
replyTokenLINE user ID (ใช้เป็น emit room key)chatRoom.entity.ts:59-60
status'w' / 'p' / 's' (default 'w')chatRoom.entity.ts:62-64
phoneNumberจาก HRMIchatRoom.entity.ts:66-67
isReadมีข้อความใหม่ที่ยังไม่อ่านchatRoom.entity.ts:69-70
isChatห้องเปิดรับข้อความทั่วไปจาก LINE หรือยังchatRoom.entity.ts:72-73

หลักฐาน entity — chat-center-api/src/entities/chatRoom.entity.ts (branch: prod):6-77

ตาราง userLine ที่ถูก query ใน webhook/gateway (SELECT * FROM userLine WHERE lineID = @0) ไม่มี TypeORM entity ใน repo — เป็นตารางใน schema 2way ที่โค้ดเข้าถึงแบบ raw SQL เท่านั้น

chatRoomStatusHistoryChatRoomStatusHistoryEntity

Columnหมายเหตุหลักฐาน
chatRoomIdFK ไป chatRoomchatRoomStatusHistory.entity.ts:13-14
status'w' / 'p' / 's'chatRoomStatusHistory.entity.ts:16-18
chatRoomManyToOne relationchatRoomStatusHistory.entity.ts:9-11

teamAdmin / teamMember / teamTags / tags

โมเดลสิทธิ์:

text
TeamAdminEntity (level: 1 = super admin, default 2)
  ├── members: TeamMemberEntity[]   (empCode, fullName)
  └── tags: TeamsTagsEntity[]       (tagsId → TagsEntity)
              └── TagsEntity (tagName, tagColor, isShow, isDelete)
EntityUniqueฟิลด์สำคัญหลักฐาน
TeamAdminEntity(teamName, sourceDB)level, isShow, isDeleteteamAdmin.entity.ts:6-32
TeamMemberEntityempCode, fullName, FK teamAdminIdteamMember.entity.ts:5-16
TeamsTagsEntitytagsId, FK teamAdminIdteamTags.entity.ts:24-41
TagsEntity(tagName, sourceDB)tagName, tagColor, isShowtags.entity.ts:5-25

Super admin สร้างผ่าน POST /teamAdmin/onlySuperAdmin ที่บังคับ level = 1 และ tags = [] (teamadmin.service.ts:118-134)

appointmentAppointmentEntity

Columnหมายเหตุหลักฐาน
empCodeRoomเจ้าของห้องที่นัดappointment.entity.ts:9-10
title, descriptionappointment.entity.ts:12-16
appointmentDateเวลานัดappointment.entity.ts:18-19
timeDiff, timeUnitใช้คำนวณเวลาแจ้งเตือนล่วงหน้าappointment.entity.ts:24-28
sourceDB, isDeletesoft deleteappointment.entity.ts:30-34

ไม่มี column เก็บ CronJob id หรือสถานะว่าแจ้งเตือนแล้วหรือยัง — reminder state อยู่ใน memory เท่านั้น

notificationsNotificationsEntity

Columnหมายเหตุหลักฐาน
empCodeRoomห้องที่เกี่ยวข้องnotifications.entity.ts:9-10
messageข้อความแจ้งเตือนnotifications.entity.ts:12-13
adminName, adminEmpCodeผู้รับ (care taker)notifications.entity.ts:15-19
superAdminถ้า true แจ้งกลุ่ม super adminnotifications.entity.ts:21-22
isReadnotifications.entity.ts:27-28

listNoti แยก query ตามว่า user เป็นสมาชิกทีม level: 1 หรือไม่ (chat.service.ts:283-324) — ใช้ QueryBuilder parameterized (ปลอดภัยกว่า rooms())

satisfactionSurveySatisfactionSurveyEntity

Columnหมายเหตุหลักฐาน
keyunique, default NEWID() — ใช้ใน URL feedbacksatisfactionSurvey.entity.ts:8-11
roomEmpCode, roomNameสำเนาจากห้องตอนปิดสถานะ ssatisfactionSurvey.entity.ts:13-17
score, descriptionคะแนนจากผู้ตอบ (@Public save)satisfactionSurvey.entity.ts:19-23
isActiontrue เมื่อตอบแล้วsatisfactionSurvey.entity.ts:28-29
careTaker*, tagsId, sourceDBsnapshot ตอนสร้างentity ทั้งไฟล์

genSurvey สร้างแถวเปล่าแล้วคืน URL UAT (satisfedurvey.service.ts:89-97) — path front hardcode เหมือนตอนปิดห้อง


MongoDB schemas (chatDB)

Conversation

Fieldใช้เมื่อหลักฐาน
empCodeuserID จาก userLineconversation.schema.ts:8-9
sourceDBจาก HRMI profileconversation.schema.ts:11-12
userActionMessage / userActionTimeข้อความจาก LINE/userconversation.schema.ts:14-18
adminActionMessage / adminActionTime / adminActionEmpCodeข้อความจากแอดมิน/botconversation.schema.ts:20-27
type'text' หรือ 'file'conversation.schema.ts:29-30
replyTokenLINE user IDconversation.schema.ts:32-33
createdAtใช้ทั้ง sort และ TTL delete 90 วันconversation.schema.ts:35-36

เอกสารหนึ่งแถว = หนึ่งทิศทางของข้อความ (user หรือ admin) — ไม่ใช่ thread document เดียว

ChatNote

Fieldหมายเหตุหลักฐาน
roomEmpCodeห้องchatNote.schema.ts:8-9
adminEmpCode, adminFullNameผู้เขียนโน้ตchatNote.schema.ts:11-15
messageเนื้อหาchatNote.schema.ts:17-18
tagEmpCode, tagFullNameคนที่ถูกแท็ก (optional)chatNote.schema.ts:20-24
createdAtchatNote.schema.ts:26-27

โน้ตไม่ถูก push ไป LINE — เห็นเฉพาะใน backoffice ผ่าน chatRoomHistory


Cross-database reads (center / HRMI)

จุดที่โค้ด query เข้า dbHRMI_Center โดยตรง:

ตำแหน่งSQL / ตารางหลักฐาน
Webhook โหลดโปรไฟล์temp_pee_na_table WHERE EmpCodeapp.service.ts:38-40
Gateway แอดมินตอบเหมือนกันchat.gateway.ts:70-72
imageProfilevw_EmployeeProfileWithImageapp.service.ts:265-267
imageProfileMultivw_EmployeeProfileWithImage + SourceDB interpolateapp.service.ts:277-281
listMembertemp_pee_na_table pagination OFFSET/FETCHteamadmin.service.ts:82-85
roomStatusHistorytemp_pee_na_table เพื่อประกอบ fullNamechat.service.ts:350-351

imageProfileMulti bind empCodes ด้วย @0,@1,... แต่ยัง interpolate sourceDB จาก JWT:

typescript
`... and SourceDB = '${user.profile.sourceDB}' `

หลักฐาน — chat-center-api/src/app.service.ts (branch: prod):277-281

ถ้า JWT ถูก forge (ดู SEC-CHAT-04 เรื่อง SECRET_KEY หลุด) ค่า sourceDB ใน token กลายเป็น injection vector ได้อีกจุด


External integrations

ปลายทางทิศทางEndpoint / pathAuthหลักฐาน
LINE Messaging APIออกhttps://api.line.me/v2/bot/message/pushBearer LINE_AUTHconfiguration.ts:42-44, chat.gateway.ts:105,148,194
LINE Platformเข้าPOST .../webhook@Public() — ไม่ verify signature ในโค้ดapp.controller.ts:18-22
2way-meeting-backofficeเข้า (caller หลัก)REST + Socket.IOJWT / ไม่มีบน WSดู .env ของ backoffice (ข้าม repo)
HRMI MSSQLอ่านตรงconnection centerDB credentialตารางด้านบน
cu-Gateway (dead)ออก (comment ออกแล้ว)http://20.212.116.206:8200/api/v1/linechat.gateway.ts:114-115

Config LINE ที่โหลด:

typescript
lineChannelSecret: process.env.LINE_CHANNEL_SECRET,
lineAuth: process.env.LINE_AUTH,
lineChannel: process.env.LINE_CHANNEL,
lineApi: { pushMessage: 'https://api.line.me/v2/bot/message/push' },

หลักฐาน — configuration.ts:39-44

ในโค้ด runtime ที่อ่านได้ — lineAuth ถูกใช้เป็น Bearer ตอน push; lineChannelSecret ถูกโหลดเข้า config แต่ ไม่พบ การนำไป verify webhook signature

CENTRALIZE_SERVER ใน .env — ไม่พบ reference ใน src/ หรือ libs/ (vestigial)


File storage

PathใครเขียนServe อย่างไร
./uploads/{YYYY-MM-DD}/{random}.{ext}ChatGateway เมื่อมี base64useStaticAssets prefix /chat-center-api/ (main.ts:28-30)

URL ที่ส่งให้ LINE เป็น text ของ full URL ผ่าน hostServer ไม่ใช่ LINE content API upload — ผู้รับต้องเปิดลิงก์ HTTP ไปที่ static file ของ chat-center-api

ถ้า container ไม่ mount volume — ไฟล์หายเมื่อ redeploy (OBS-CHAT-02)


Swagger

docs/document.config.ts ตั้ง title "Vtrc RestFull" และเพิ่ม server สามตัว:

  1. SERVER_URL จาก env
  2. http://localhost:{PORT||8200}
  3. ngrok URL hardcode https://3fe5-49-228-70-244.ngrok-free.app

หลักฐาน — chat-center-api/docs/document.config.ts (branch: prod):3-11

ngrok URL ที่ commit อาจหมดอายุแล้ว — เป็น leftover จาก dev tunnel


Tenant model (sourceDB)

เกือบทุกตารางและ query กรองด้วย sourceDB จาก user.profile.sourceDB ใน JWT — เป็น soft multi-tenant ภายใน database เดียว (2way / chatDB) ไม่ใช่ database แยกต่อหน่วยงาน

Super admin level === 1 ยังถูกกรอง sourceDB เหมือนกันใน rooms() — เห็นทุกห้องใน sourceDB เดียวกัน ไม่ใช่ข้ามทุก tenant