Skip to content

2way-api — Persistence & Integrations

บทนี้ map ชั้นข้อมูล MSSQL/TypeORM และทุก outbound integration ของ 2way-api (branch: prod) ที่พิสูจน์จากซอร์ส — รวม dual-centralize, SSO, LINE, vtrc-api, และ cross-schema PMS

แผนที่โมดูล: 01-repository-map
Request traces: 02-core-pipeline


Database connection (TypeORM → MSSQL)

typescript
TypeOrmModule.forRootAsync({
  name: 'main',
  useFactory: (configService: ConfigService) => ({
    type: 'mssql',
    host: configService.get('db.host'),
    port: +configService.get<number>('db.port'),
    username: configService.get('db.user'),
    password: configService.get('db.password'),
    database: configService.get('db.databaseName'),
    synchronize: false,
    logging: [`error`, `query`],
    entities: [__dirname + '/**/*.entity{.ts,.js}'],
    extra: {
      trustServerCertificate: true,
      encrypt: false,
    },
  }),
  inject: [ConfigService],
})

แหล่ง: 2way-api/src/app.module.ts:26-45

Settingค่าความหมาย
connection name'main'ใช้กับ @InjectEntityManager('main')
synchronizefalseไม่ auto-alter schema จาก entity
encryptfalseTDS ไม่บังคับ TLS encrypt
trustServerCertificatetrueถ้ามี TLS จะไม่ verify cert
loggingerror, querylog SQL ทุก query ในระดับ Nest logger

Mapping จาก env ผ่าน config/configuration.ts:3-9:

Config pathEnv varตัวอย่างใน .env.example
db.hostDB_HOST10.188.128.58
db.portDB_PORT1433
db.userDB_USERsa
db.passwordDB_PASSWORD(มีใน example — ห้าม commit secret จริงเพิ่ม)
db.databaseNameDB_NAME2way

Driver: mssql@9.1.1 (package.json:47) + typeorm@^0.3.11 (package.json:54)

SQL bootstrap อ้างอิง: 2way-api/sql/20230412_create_structure_table_prod.sql — ใช้เทียบ schema กับ entity เมื่อสงสัย drift; runtime ไม่ได้รันไฟล์นี้เอง


BaseEntity — คอลัมน์ร่วม

typescript
export class BaseEntity {
  @PrimaryGeneratedColumn('increment', { name: 'id' })
  id?: number;

  @Column({ name: 'create_date', default: () => 'CURRENT_TIMESTAMP' })
  createDate?: Date;

  @Column({ name: 'create_by', type: 'varchar', length: 50, nullable: true })
  createBy?: string;

  @Column({ name: 'update_date', default: () => 'CURRENT_TIMESTAMP' })
  updateDate?: Date;

  @Column({ name: 'update_by', type: 'varchar', length: 50, nullable: true })
  updateBy?: string;
}

แหล่ง: 2way-api/src/entities/base.ts:3-24

เกือบทุก m_* / t_* extends BaseEntity — ยกเว้น UserLine ที่นิยาม PK เอง (userLine.entity.ts:4-6)


Domain model — Content กลาง (Announces)

Announces ใช้ตาราง t_content เป็นเอกสารหลัก แยกประเภทผ่าน m_content_type (content_type_id)

t_content คอลัมน์สำคัญ

Column (DB)Propertyประเภทหมายเหตุ
topic_nametopicNamevarchar(1500)หัวข้อประกาศ
topic_etctopicEtcvarchar(1500)รายละเอียดเสริม
detailsdetailsvarchar(1500)เนื้อหา
offer_dateofferDatedateวันที่มีผล / แจ้ง
dir_attach / attach_filespath ไฟล์
link_url / link_resultURL
type_assigntypeAssigndefault ATTENDEEหรือ ALL
type_grouptypeGroupdefault RCRC vs CU
visible_btn / btn_textUI ปุ่มรับทราบ
show_when_registerint

แหล่ง: 2way-api/src/entities/t_content.entity.ts:12-102

Relations:

  • OneToOneTContentStatus, TAttachmentAnnounce
  • OneToManyTContentFlowHistory, TSendNotify, TContentAssign, TContentManager, TContentAssignAll

Assign tables

t_content_assign — มอบหมายรายบุคคล: emp_code, org/divi/dept, know_date, read_date, identitycard
แหล่ง: t_content_assign.entity.ts

t_content_assign_all — เมื่อ type_assign = 'ALL': emp_code, line_id, know_date, read_date
แหล่ง: t_content_assign_all.entity.ts

Flow history

t_content_flow_history เก็บ seq_no, emp/org/divi, his_dttm, remark — list query ของ announce เลือก MAX(seq_no) เพื่อได้สถานะล่าสุด (announces.service.ts:216-219)


Domain model — Surveys

ตารางหลัก t_content_survey แยกจาก t_content:

Columnหมายเหตุ
type_idm_type_surveyประเภท
reuse_type_idm_reuse_surveyรูปแบบ reuse
subject, detail, story_detailเนื้อหา
link_url, link_resultลิงก์แบบสำรวจ/ผล
org_code_assign, divi_code_assign, source_dbขอบเขต
type_assigndefault ATTENDEE

แหล่ง: t_content_survey.entity.ts:12-72

ตารางคู่ขนาน:

TableEntityบทบาท
t_assign_surveyTAssignSurveyมอบหมายรายคน
t_content_survey_allTContentSurveyAll (ไฟล์ t_content_assign_survey.entity.ts)assign-all
t_content_survey_ignoreTContentSurveyIgnoreคนที่ถูกตัดออก
t_history_surveyTHistorySurveyประวัติสถานะ
t_reuse_surveyTReuseSurveyรอบวันที่ reuse
m_status_surveyMStatusSurveymaster status
temp_surveyTempSurveytemp

Cron survey อ่าน t_content_survey + t_history_survey + t_assign_survey + t_reuse_survey ด้วย raw SQL (survey.task.service.ts:31-40)


Domain model — Complaints

ตารางหลัก t_content_complaint:

Columnหมายเหตุ
type_idm_type_complaintประเภท
subject_idm_subject_complaintหัวข้อ (+ subject_other)
mention, detailเนื้อหา
org_code_assign, divi_code_assign, emp_code_assignมอบหมาย
contact_no, source_dbติดต่อ / แหล่ง HR DB
is_call_back, is_know, is_know_date, is_draftflags

แหล่ง: t_content_complaint.entity.ts:9-68

ตารางคู่ขนาน:

Tableบทบาท
t_assign_complaintผู้รับผิดชอบ
t_attachment_complaintไฟล์แนบ
t_history_complaintประวัติ workflow
m_status_complaintstatus + next_status_code + stage
t_change_divicode_complaintsaudit เปลี่ยนฝ่าย
temp_complaint_roletemp role สำหรับ cron

m_subject_complaint มี org_code_admin / divi_code_admin / emp_code_admin สำหรับ routing เรื่องไปแอดมินที่ถูกฝ่าย (m_subject_complaint.entity.ts:17-23)


Domain model — LINE identity

userLine

Columnบทบาท
userIDรหัสพนักงาน / identity ที่ join กับระบบอื่น
identityCardบัตรประชาชน
birthDatestring
lineIDLINE user ID สำหรับ push
richMenuIDrich menu ปัจจุบัน
isActivebit

แหล่ง: userLine.entity.ts:3-25

PMS cron join: pms.dbo.tbnotify.empcode[2way].dbo.userline.userid ด้วย collation Thai_100_CI_AS (notification-pms.task.service.ts:34-36)

userLineProfile

Entity ใหญ่ (~6 KB ไฟล์) — ใช้ใน announce CU path กรองด้วย identityCardEmpCode (announces.service.ts ช่วง findListOfAnnounceCu)

m_user_manager

ใช้ใน EmployeesService.getManagers / getEmployees เพื่อ กรองออก empCode ที่เป็น manager ออกจากผลลัพธ์ centralize (employees.service.ts:90-101,128-139)

คอลัมน์: emp_code, org/divi/dept, is_active, is_manager, is_deputy (m_user_manager.entity.ts:5-52)


Domain model — Config / audit / notify

Tableบทบาท
m_configmodule, name, value, is_active
m_error_messageรหัส error ไทย/อังกฤษ + severity
m_rolerole_name, action_view
m_content_flowstage machine ของ announce content
t_send_notifyคิวแจ้งเตือนผูก t_content
t_logs_notificationaudit หลัง push (เช่น knowContent)
t_thumbnail_announce / t_attachment_announceสื่อประกาศ

Raw SQL vs TypeORM — แผนที่การเข้าถึง

ModuleTypeORM find/save/QBentityManager.query
announcescreate/update/know/readlist SQL หลัก (injection surface)
surveysส่วนใหญ่ workflowมีจุด ${item.id} และ util queries
complaintsworkflow หลักsummary/count queries
authteamMember level lookup (parameterized @0)
employeesMUserManager.findVM_PERMISSION_FOR_SEARCH_EMPLOYEE (static SQL)
crontab/*Line + occasional saveหลักเป็น raw SQL

หลักการอ่านโค้ด: ถ้าเห็น template string กับ ${dto.*} ใน query(...) ให้ถือว่าเป็น candidate SQL injection จนกว่าจะพิสูจน์ว่าค่ามาจาก DB ภายในเท่านั้น


Integration map — ภาพรวม

                    ┌─────────────────────┐
                    │   2way-api (NestJS) │
                    └──────────┬──────────┘
           ┌───────────────────┼───────────────────┐
           │                   │                   │
           ▼                   ▼                   ▼
   MSSQL DB_NAME=2way   CENTRALIZE_SERVER    CENTRALIZE_API_URL
   (+ schema pms)       NestJS REST          GraphQL (cu-central)
           │            centralize-api              │
           │                   │                    │
           ▼                   ▼                    ▼
   userLine / t_*      /api/employee/*     fetchListEmployeeForTwoWay

           ├──── SSO_END_POINT (HTTPS Basic + AES body)
           ├──── api.line.me (Bearer channel token)
           ├──── http://20.212.116.206:8200 (2way-line-service)
           └──── VTRC_BASE_CONNECT GraphQL (caller commented)

Integration 1 — Dual centralize (ยืนยันแล้ว)

2way-api เรียก สองบริการคนละตัว ที่ชื่อคล้ายกัน:

1A — NestJS centralize-api (REST)

รายการค่า
EnvCENTRALIZE_SERVER
ตัวอย่าง UAThttp://10.188.128.160:3000/ (.env.example:25)
ClientEmployeesService ผ่าน this.config.get('CENTRALIZE_SERVER')
ProtocolHTTP GET + query params
Auth ไปปลายทางไม่เห็น header apiKey/JWT ใน service นี้ — ยิง axios ตรง

Endpoints ที่เรียกจริง (employees.service.ts):

Local methodUpstream path
getOrganizationapi/employee/organize
getDivisionsapi/employee/division
getDepartmentsapi/employee/department
getManagersapi/employee/manager
getEmployeesapi/employee
getEmployeesCuapi/employee/cu
getEmployeesByEmpCodeapi/employee/getByEmpCode
getLevelsapi/employee/level
getPositionsapi/employee/position
getManagerPositionsapi/employee/posManage
getTechnicalPositionsapi/employee/posTechnical
getEmployeesGroupsapi/employee/group

หมายเหตุ config: CENTRALIZE_SERVER ไม่อยู่ ใน object ที่ configuration.ts return — Nest ConfigService ยังอ่านจาก process env ได้เมื่อ ConfigModule โหลด .env

Filter ฝั่ง 2way หลังได้ข้อมูล:

  • getOrganization: ถ้า query.type === 'CU' กรอง sourceDB ที่มี 'CU' (employees.service.ts:49-51)
  • getManagers / getEmployees / getEmployeesCu: ตัด empCode ที่อยู่ใน m_user_manager ที่ isManager=true

Stub ที่ไม่เรียก upstream: getAdmin, getContent, createManager, updateEmployee, updateManager, deleteManager — คืน input กลับอย่างเดียว (employees.service.ts:85-214)

1B — cu-central-api (GraphQL)

รายการค่า
EnvCENTRALIZE_API_URL
ตัวอย่าง UAThttps://uat-centralizeapi.redcross.or.th/api-uat/graphql (.env.example:32)
ClientAuthService.checkEmployeeCD
ProtocolHTTP POST GraphQL
OperationfetchListEmployeeForTwoWay
typescript
await this.axios.post(`${process.env.CENTRALIZE_API_URL}`, {
  query: `query fetchListEmployeeForTwoWay($user: [childEmployeeForTwoWay]) { ... }`,
  variables: { user: [{ userID: username }] },
});

แหล่ง: auth.service.ts:76-104

ชื่อ env มีคำว่า CENTRALIZE แต่ URL path เป็น /api-uat/graphql และ operation name เป็นของ cu-central GraphQL — ไม่ใช่ NestJS centralize-api ที่เป็น REST /api/employee

สรุป dual-centralize (canonical)

Env varระบบจริงใช้โดยTransport
CENTRALIZE_SERVERNestJS centralize-apiEmployeesServiceREST
CENTRALIZE_API_URLcu-central-apiAuthService.checkEmployeeCDGraphQL

นี่คือหลักฐานจากมุม 2way-api ว่ามี caller ไปทั้งสองระบบ — เกี่ยวข้องกับ debt การตั้งชื่อ "centralize" ที่สับสนข้าม repo


Integration 2 — SSO login

Env + hardcoded fallback

FieldEnvFallback ในโค้ด
AES keySSO_AES_SECRET_KEY'EoABvcD7RsFBeKRZgSOHS4LLD08To6Pc'
AES IVSSO_AES_INIT_VECTOR'AaNZRKzytx9yCAiA'
EndpointSSO_END_POINT'https://uatssoapi.redcross.or.th/SSOService_dev/api'
App IDSSO_APP_ID'VTRCBO'
Request IDSSO_REQUEST_ID'SSO220607000000031'
Basic userSSO_USERNAME'user'
Basic passSSO_PASSWORD'P@ssw0rd'

แหล่ง: auth.service.ts:20-26 และ .env.example:38-45

Flow

  1. aesEncryptFn(username) / aesEncryptFn(password)aes-256-cbc ผ่าน Node crypto.createCipheriv (auth.service.ts:122-131)
  2. POST ${SSO_END_POINT}/ssoLogin พร้อม body { requestAppId, requestId, username, password } และ HTTP Basic Auth (auth.service.ts:151-172)
  3. ถ้า responseCode === '0000'isSuccess: true (ssoAxiosInstance, auth.service.ts:138-141)
  4. ออก JWT two-way เก็บแค่ { loginToken: sso.loginToken } อายุ 48h

ความเสี่ยง: fallback secrets ฝังในซอร์ส — ถ้า deploy ลืมตั้ง env จะใช้ค่า UAT/dev ที่ commit ไว้ใน git


Integration 3 — vtrc-api GraphQL (ถูก comment ใน production path)

typescript
private async fetchEmployeeProfileForOtherService(token: string) {
  return await this.axios.post(process.env.VTRC_BASE_CONNECT, fetchEmployee, {
    headers: {
      apiKey: process.env.API_KEY_OUTBOUND_VTRC,
      Authorization: `Bearer ${token}`,
    },
  });
}

แหล่ง: auth.service.ts:44-67

Envตัวอย่าง .env.example
VTRC_BASE_CONNECThttps://uat-vtrcapi.redcross.or.th/api-uat/graphql
API_KEY_OUTBOUND_VTRCUUID string

Operation: fetchEmployeeProfileForOtherService — ตรงกับ pattern ของ legacy vtrc-api GraphQL + apiKey header

สถานะ runtime: ถูก comment ที่ signInRedirectByPass (auth.service.ts:295-296) และ profile fields ที่เคยเติมจากผลลัพธ์ถูก hardcode เป็น null (auth.service.ts:326-329)

สรุป: โค้ดเชื่อม vtrc-api มีอยู่ แต่เส้นทาง production ปัจจุบันไม่เรียก


Integration 4 — LINE Messaging API

Tokens จาก config

typescript
lineToken: process.env.LINE_AUTH_TOKEN,      // RC
lineCUToken: process.env.LINE_AUTH_CU_TOKEN, // CU

แหล่ง: configuration.ts:11-12

เลือก token ใน service:

typescript
const token = lineToken === 'CU' ? this.config.get('lineCUToken') : this.config.get('lineToken');

แหล่ง: line.service.ts:22,50

พารามิเตอร์ที่สองของ pushMassage ชื่อ lineToken แต่ค่าที่ส่งจาก announce คือ content.typeGroup ('RC'/'CU') — announces.service.ts:1382

Endpoints

MethodURLใช้โดย
POSThttps://api.line.me/v2/bot/message/pushpushMassage
POSThttps://api.line.me/v2/bot/message/multicastmultiCast

แหล่ง: line.service.ts:30,57

Payload รูปแบบ text อย่างเดียว: { to, messages: [{ type: 'text', text }] }

Logging: pushMassage log token เต็มใน logger (line.service.ts:23) — ความเสี่ยงเลอะ secret ใน log aggregate


Integration 5 — 2way-line-service (hardcoded IP)

typescript
await this.axios.post('http://20.212.116.206:8200/api/v1/line/nextPage', data);
await this.axios.delete('http://20.212.116.206:8200/api/v1/line/previousPage', data);

แหล่ง: line.service.ts:76,86

ข้อเท็จจริงรายละเอียด
HostIP คงที่ ไม่ผ่าน env
Port8200 — ตรงพอร์ต default ของ 2way services
Path/api/v1/line/nextPage, /previousPage
Schemehttp ไม่ใช่ https

ไม่มีการ authenticate ไปยัง line-service ในโค้ดส่วนนี้ — เชื่อถือ network path


Integration 6 — Cross-schema PMS

Database instance เดียวเข้าถึงได้ทั้ง:

  • [2way].dbo.* — แอปหลัก
  • pms.dbo.tbnotify — แจ้งเตือนจากระบบ PMS

Query หลัก (notification-pms.task.service.ts:27-46):

sql
SELECT tn.*, ul.lineID, ul.richMenuID
FROM pms.dbo.tbnotify AS tn
INNER JOIN [2way].dbo.userline AS ul
  ON tn.empcode COLLATE Thai_100_CI_AS = ul.userid COLLATE Thai_100_CI_AS
 AND ul.isActive = 1
WHERE tn.notifyDate <= cast(getdate() AS date)
  AND tn.lineStatus = 1
  AND tn.isSendLine = 0
  AND tn.notifyGroup = 'PMS'
  AND tn.fiscalYear = CASE
        WHEN DATEPART(MONTH, getdate()) >= 10 THEN YEAR(getdate()) + 543 + 1
        ELSE YEAR(getdate()) + 543
      END

หลัง multicast สำเร็จ → UPDATE pms.dbo.tbNotify ตั้ง isSendLine = 1 โดยต่อ notifyCode / empCode เป็น string (notification-pms.task.service.ts:77-84)

สถานะ: cron handler ถูก comment — ดูบท 02; schema coupling ยังอยู่ในซอร์ส


Integration 7 — Consumer frontend (route prefix)

ฝั่งค่าแหล่ง
API BASE_PATH2way-api/.env.example:3
Global prefix2way-api/api/v1main.ts:24-26
Swagger2way-api/docsmain.ts:42
Static filesURL prefix /2way-api/main.ts:27-29

Frontend 2way-meeting-backoffice production ตั้ง APP_CONNECT_API ชี้มาที่ host เดียวกันด้วย path /2way-api — ตรงกับ prefix นี้ (ยืนยันใน index ของ volume / env ของ backoffice)


Secrets และ JWT — แผนที่ตัวแปร

ตัวแปรใช้ทำอะไรปรากฏใน
SECRET_JWT_TWOWAYออก/ตรวจ JWT ของ 2wayjwt.strategy, auth.service, profiles.util
SECRET_KEYตรวจ token จาก VTRC ใน redirectbypass; map ใน config เป็น jwtSecretKeyauth.service, configuration.ts
LINE_AUTH_TOKENLINE OA RCconfiguration → LineService
LINE_AUTH_CU_TOKENLINE OA CUconfiguration → LineService
SSO_*SSO encrypt + BasicAuthService fields
API_KEY_OUTBOUND_VTRCheader ไป vtrc-apiAuthService (commented caller)
NEXT_PUBLIC_ENCRYPT_SECRETcrypto-js helperencrypt.util.ts default 'CHANGE_ME_PLS'

Backdoor ไม่ใช้ env — ใช้ string ตรงใน signInNonEmp (auth.service.ts:188)

.env.example มีค่าคล้าย production/UAT จำนวนมาก (DB password, LINE tokens, secrets) — ถือเป็น secret hygiene issue ระดับ repo; เอกสารนี้ไม่วางค่าเต็มซ้ำนอกที่จำเป็นต่อการอ้างอิง path


Status code tables ใน configuration

configuration.ts ฝัง status mapping สำหรับ announcement และ survey (ใช้เป็น business constant ในโค้ด ไม่ได้อ่านจาก DB อย่างเดียว):

Announcement (contentStatus.announcement)

shortNamestatusCodeความหมาย
CAS00Create Announcement Send Admin
CAU00Create Announcement Not send Admin
MRN10Manager received
ERA11Employee received

แหล่ง: configuration.ts:14-18

Survey (contentStatus.survey)

shortNamestatusCodeความหมาย
ACS00Create in-scope
ACD00Create out-scope
ERN40Employee received
HRN41HR Admin received
HAP42HR Admin approved
HUA43HR Admin unapproved

Action enum: approve 10, unapprove 20, adminReceive 30, assign 40, read 50 (configuration.ts:27-33)

Error codes SV100–SV105 ฝังข้อความไทย (configuration.ts:34-76)

Complaint ใน configuration

บล็อก contentStatus.complaint ส่วนใหญ่ถูก comment; เหลือ secretary.employeeCodes: ['', '', ''] (configuration.ts:79-111) — ค่าว่าง แสดงว่า secretary list ย้ายไปใช้ DB/m_config หรือ constant อื่นแทน

Complaints constants ใน complaints.constant.ts ยังอ้าง list.empCode.secretary และ action names SECRETARY_*


File storage persistence

ไม่ใช่ DB blob — ไฟล์อยู่บน filesystem:

รายการค่า
Write path/uploads/{prefixPath}/{YYYY-MM-DD}/...
Env hintMULTER_DEST=./uploads (.env.example:23) — upload service ใช้ path ตรงมากกว่า multer
HTTP readstatic prefix /2way-api/
MIME allowlistjpeg, jpg, png, pdf, mp4

แหล่ง: upload.service.ts:35-55, main.ts:27-29

dir_attach / attach_files ใน entity เก็บ path/ชื่อไฟล์อ้างอิงกลับมาที่ disk


Views / tables นอก entity glob

โค้ดอ้างชื่อที่ไม่มีไฟล์ .entity.ts ตรงๆ:

ชื่อที่ใช้หมายเหตุ
VM_PERMISSION_FOR_SEARCH_EMPLOYEEemployees.service.ts:267VIEW คืน JSON empList
teamMember / teamAdminauth.service.ts:299-304ใช้ใน redirectbypass หา level
pms.dbo.tbnotifynotification-pmsคนละ schema
t_content_survey_allsurveys.service raw SQLมี entity ชื่อ class TContentSurveyAll map ตารางนี้

สิ่งเหล่านี้ยืนยันว่า schema จริงกว้างกว่าชุด entity files — เมื่อ migrate ต้องดูทั้ง SQL folder และ raw queries


Docker / runtime กับ persistence

Dockerfile.prod:

  1. Build stage Node 16 + yarn build
  2. Production stage Node 18
  3. COPY .env.prod แล้ว mv .env.prod .env
  4. CMD yarn start:prodnode dist/src/main

แหล่ง: Dockerfile.prod (อ่านจาก repo)

ผล: connection string และ secrets ใน image มาจากไฟล์ .env.prod ที่ถูก bake ตอน build — หมุน secret ต้อง rebuild/redeploy


Checklist ตรวจ integration ตอน debug

  1. พนักงาน list ผิด → ดู CENTRALIZE_SERVER ถึง NestJS centralize-api หรือยัง + filter m_user_manager
  2. Login SSO ล้ม → ดู SSO_END_POINT / AES key-IV ตรงกับฝั่ง SSO หรือไม่; อย่าลืมว่า employee ที่พบใน CD จะถูกปฏิเสธก่อนถึง SSO
  3. Login ผ่านแต่เป็น admin ปลอม → ตรวจว่าใช้ backdoor i3admin หรือไม่
  4. LINE ไม่ถึง → แยก typeGroup RC/CU กับ token ที่ถูก; ดู log ว่า leak token หรือไม่
  5. Rich menu เพจไม่เปลี่ยน → ตรวจว่า IP 20.212.116.206:8200 ยังชี้ line-service จริง
  6. PMS notify เงียบ → ยืนยันว่า @Cron ของ NotificationPmsTaskService ยังถูก comment
  7. SQL error แปลกๆ จาก announce list → สงสัย injection/input พิเศษใน query string ก่อน

สิ่งที่บทนี้ยืนยัน vs ไม่ยืนยัน

ยืนยันจากซอร์สแล้ว

  • Dual call: REST CENTRALIZE_SERVER + GraphQL CENTRALIZE_API_URL
  • SSO AES + Basic + fallback secrets
  • LINE push/multicast URLs และ dual token
  • Hardcoded IP ไป line-service
  • PMS cross-schema SQL (แม้ cron ปิด)
  • vtrc-api client มีแต่ caller comment
  • synchronize: false, encrypt: false

ไม่ยืนยันในเอกสารนี้

  • ว่า production DNS/IP ปัจจุบันของแต่ละ env ตรงกับ .env.example หรือไม่ (ต้องดู secret store ตอน deploy)
  • ว่า centralize-api ปลายทาง enforce auth อย่างไร (อยู่นอก repo นี้ — ดู volume ของ centralize-api)
  • schema drift ระหว่าง sql/*.sql กับ DB จริงในแต่ละ environment