ระบบ Apps เสริม
ภาพรวม
"Apps" คือฟีเจอร์เสริมที่ขายเป็นชิ้นให้ลูกค้าเปิดใช้เพิ่มจากระบบหลัก ปัจจุบันมีให้เลือกสามตัว
| id | ชื่อ | คำอธิบาย | Route |
|---|---|---|---|
appointment | Appointment | จองคิวและนัดหมาย | /apps/appointment |
loyalty | Loyalty | บัตรสะสมแต้มและแลกรางวัล | /apps/loyalty |
bulletin | Bulletin Board | กระดานประกาศ | /apps/bulletin |
โมดูลนี้ดูแลสองเรื่อง เรื่องแรกคือทะเบียนแอป (appRegistry ที่ hardcode ไว้ในโค้ด) พร้อมสถานะเปิดหรือปิดต่อองค์กร และเรื่องที่สองคือ AppEnabledGuard ซึ่งทุก route ของแอปต้องผ่าน
จุดออกแบบที่สำคัญคือ การเปิดหรือปิดแอปเป็น อำนาจของ platform admin เท่านั้น เพราะถ้าองค์กรเปิดแอปให้ตัวเองได้ การกำกับดูแลเชิงพาณิชย์ก็จะไร้ความหมาย จึงบังคับด้วย modulegate.PlatformOnly
Business Flow
- แสดงเมนู — หลังล็อกอิน cms-web เรียก
GET /api/appsเพื่อ render เมนู sidebar- endpoint นี้ใช้
JwtLoginAuthแทน global guard เพราะในจังหวะนั้น token ยังไม่มีlineOaIdหากใช้ guard ที่เข้มกว่าจะได้ 401 แล้ว interceptor ฝั่งเว็บจะเด้งผู้ใช้ออกจากระบบ - service อ่านตาราง
line_oa_appตามorganizationIdแล้ว merge กับappRegistryก่อนคืนข้อมูลแอปพร้อม fieldid,name,description,icon,routeและenabled
- endpoint นี้ใช้
- เปิดหรือปิดแอป — platform admin เรียก
PUT /api/apps/:appIdพร้อม body ที่มี fieldenabledเป็น boolean- ผ่าน
modulegate.PlatformOnly(d)หากผู้เรียกเป็นcustomerหรือระบุตัวตนไม่ได้จะได้ 403 - guard ตัวนี้ fail closed ต่างจาก
ModuleGateที่ fail open เพราะความเสี่ยงในกรณีนี้คือ privilege escalation
- ผ่าน
- ตรวจสิทธิ์ทุก request ของแอป — request ที่ path ตรงกับรูปแบบ
/apps/ตามด้วยชื่อแอปจะผ่านAppEnabledGuard- guard ดึง app id ออกจาก path ด้วย regular expression
- หาก path ไม่ตรงรูปแบบจะปล่อยผ่าน
- หากตรงรูปแบบแต่
IsAppEnabled(orgId, appId)คืนค่า false จะตอบ 403 พร้อมข้อความว่าแอปนั้นยังไม่ถูกเปิดให้องค์กรนี้ - หาก
organizationIdหายไปจาก CLS จะกลายเป็น 0 ทำให้หาแถวไม่พบและถือว่าแอปปิดอยู่
- เข้าใช้งาน — เมื่อแอปถูกเปิดแล้ว ผู้ใช้จึงเข้าถึง route ของแอปนั้นได้ (ดู แอปจองคิว/นัดหมาย, แอปสะสมแต้ม และ แอปกระดานประกาศ)
ไฟล์และฟังก์ชันหลัก
โค้ดอยู่ที่ internal/modules/apps/ ประกอบด้วย controller.go, service.go, service_appointment.go, guard.go และ dto.go
| Method | Route | Handler | Guard / Policy |
|---|---|---|---|
| GET | /api/apps | ct.listApps | JwtLoginAuth (group public) + readAll friend-track |
| PUT | /api/apps/:appId | ct.toggleApp | group authed + modulegate.PlatformOnly(d) + update friend-track |
หมายเหตุ policy metadata ใช้ PolicyModuleFriendTrack ซึ่งตรงกับระบบ TypeScript เดิม ไม่ใช่ความผิดพลาดจากการ port
ฟังก์ชันและโครงสร้างที่สำคัญ
appRegistryในservice.goเป็นทะเบียนแอปแบบ hardcode โดยลำดับ field ตรงกับ object literal เดิมAppsService.ListApps(ctx)และAppsService.IsAppEnabled(ctx, orgID, appID)apps.AppEnabledGuard(d)ถูก export ออกมาเพื่อให้โมดูล bulletin และ loyalty ซึ่งอยู่คนละ package เรียกใช้ได้- ตัวจับ path ของแอป
appPathRe = regexp.MustCompile(`/apps/(\w+)`)
จุดเชื่อมต่อกับ Service อื่น
- สิทธิ์การเข้าถึง —
PlatformOnly(fail closed) บน endpoint toggle,AppEnabledGuardบนทุก route ของแอป และJwtLoginAuthบน endpoint list - ตารางที่เกี่ยวข้อง —
line_oa_app(แถวที่บันทึกการเปิดแอปต่อองค์กร),organizationและuser - CLS —
organizationIdและselfId - เอกสารออกแบบ —
docs/superpowers/specs/2026-07-26-platform-admin-design.md - โมดูลที่เกี่ยวข้อง — Permission & Module Gate, Organization Module Setting, แอปจองคิว/นัดหมาย, แอปสะสมแต้ม และ แอปกระดานประกาศ