# Gumon > Gumon is a composable system: an application is assembled from ready-made microservices (NestJS + GraphQL + MongoDB + Redis are typical, but any stack works) that talk to each other **only through Kafka events**. Frontends call each service's GraphQL API directly. Docs are in Thai with English identifiers. Key rules an AI must know before writing Gumon code: - Never call another service's API directly — produce a Kafka event; any service may subscribe. The only requirement to join Gumon is speaking Kafka. - Every message carries headers `appKey` (the app/tenant) and `serviceKey` (the **destination** service, not the sender). Broadcasts to a whole app (`sync-application`, `sync-organization`, `sync-auth`, `sync-profile`, any service's `sync-`) carry only `appKey`. - Receiving: drop messages whose `serviceKey` is not yours **only on single-destination topics** (`refresh-data`, `hand-check`, `sync-app-credential`, `sync-user-policy`, `sync-*-setting`, `schedule-alarm`). Never filter broadcasts by `serviceKey`. `sync-app-certificate` carries the certificate owner's key but every service must store all of them. Drop anything for an app where you hold no app certificate. - Payload shape: `{ "action": "ADD" | "REMOVE" | ..., "": { ... } }`. Topic names are kebab-case. Consumer group: `-consumer-`. - A service keeps local copies of data it needs from others (received via `sync-*` topics) instead of querying them. - Minimum contract for any service: produce `register-service` on boot and answer core's `hand-check` with `hand-check-result` (the template does both); consume `sync-app-certificate` (which apps it may serve) and `refresh-data` (re-publish everything it owns, clear its cache). - Permissions say *whether* a user may do X, not *whose* data it is: filter "my" data yourself by `authId` from the token. - Services with user-facing APIs also consume `sync-app-credential` (JWT keys; credential type `SYSTEM` = "apiKey" via `X-APP-CLIENT-ID` + `X-APP-CLIENT-SECRET`) and `sync-user-policy`, and produce `sync-permission`. Permission check = look up `${SERVICE_KEY}::${permissionKey}:${appKey}:${organizationId}:${authId}` (organizationId empty for app level) in local storage. - In a Kafka consumer, let errors throw: the template retries with backoff, then sends the message to `KAFKA_DEAD_LETTER_TOPIC` and moves on. Write to MongoDB through `runInTransaction`; clear Redis after commit. Never swallow errors silently. - Local development from `gumon-backend-template`: `pnpm test:e2e:up` (Kafka/Mongo/Redis in Docker) · `pnpm dev:keys` (throw-away `.gumon` keys) · `pnpm start:dev` · `pnpm test:e2e`. Register with a real core: `pnpm gumon:register`. `SERVICE_KEY` must be set (placeholder `CHANGE_ME` refuses to boot); `GRAPHQL_PLAYGROUND=true` enables the GraphQL page. - Use central services instead of building your own: `set-schedule` → schedule (replaces in-process cron so services can run many replicas), `create-notification` → notification, `sync-file-upload` → storage. Full text of every page: https://docs.gumon.io/llms-full.txt ## Concepts - [Gumon concept](https://docs.gumon.io/concept.md): composable system, Kafka-only rule, tenancy (app / organization), headers, core set vs business services - [Standard Kafka topics](https://docs.gumon.io/standardTopics.md): every standard topic — producer, consumer, purpose, payload - [Service lifecycle](https://docs.gumon.io/serviceLifecycle.md): register / resign, add / remove service to an app, init-system, refresh-data, keys - [Frontends](https://docs.gumon.io/frontends.md): system-admin, dynamic-admin, embedding sub-admins, menu metadata ## Building a service - [What a service must have](https://docs.gumon.io/serviceX.md): the 3-level contract (must / has API / as needed) - [Create a new service](https://docs.gumon.io/createService.md): step-by-step from gumon-backend-template, checklist - [Gumon CLI](https://docs.gumon.io/gumoncli.md): local stack in one command — `gumon up` (databases, init once, core set), `status`, `down`; planned commands listed ## Core set services (API Reference + Kafka consume/produce) - [Core Service](https://docs.gumon.io/coreService.md): service registry, certificates, add service to app - [Application Service](https://docs.gumon.io/applicationService.md): apps, themes, host → app mapping - [Authentication Service](https://docs.gumon.io/authenticationService.md): accounts, login methods, app credentials - [ACL Service](https://docs.gumon.io/aclService.md): permissions, role bindings, user policies, menus - [Unit Service](https://docs.gumon.io/unitService.md): organizations, roles, types/tags, contacts, running numbers - [Profile Service](https://docs.gumon.io/userService.md): user profile data - [Notification Service](https://docs.gumon.io/notificationService.md): in-app (WebSocket), email, SMS - [Schedule Service](https://docs.gumon.io/scheduleService.md): central scheduler, `set-schedule` / `schedule-alarm` - [Storage Service](https://docs.gumon.io/storageService.md): presigned upload/download, file registry ## Optional - [Label Service](https://docs.gumon.io/labelService.md): deprecated — merged into Unit Service --- # Gumon Docs Gumon คือระบบแบบ **Composable System** — แอปหนึ่งตัวประกอบจาก service สำเร็จรูปหลายตัว service คุยกันผ่าน **Kafka** เท่านั้น ส่วนหน้าบ้านเรียก GraphQL API ของแต่ละ service ได้ตรง **จะต่อ service ใหม่เข้า Gumon ขอแค่คุยผ่าน Kafka ได้** — ภาษา ฐานข้อมูล framework เลือกเองได้ ## เริ่มที่ไหน | อยากทำ | อ่าน | | --- | --- | | เข้าใจภาพรวมก่อน | [แนวคิด Gumon](concept.md) | | ทำ service ใหม่ต่อเข้าระบบ | [สิ่งที่ Service ต้องมี](serviceX.md) → [สร้าง Service ใหม่](createService.md) | | หา topic และ payload | [Kafka topic มาตรฐาน](standardTopics.md) | | ลงทะเบียน / เพิ่ม service เข้าแอป | [วงจรชีวิตของ service](serviceLifecycle.md) | | ทำหน้า admin ฝังใน dynamic admin | [หน้าบ้าน (Admin UI)](frontends.md) | | ดู API ของ service แต่ละตัว | เมนู **Core Service** ทางซ้าย — ต้นหัวข้อ API Reference ของทุกหน้ามีตารางสรุป | ## Core set service ที่ระบบต้องมีเพื่อทำงานได้ก่อนมีธุรกิจใด ๆ | service | หน้าที่ | | --- | --- | | [Core](coreService.md) | ทะเบียน service · กุญแจ · เพิ่ม service เข้าแอป | | [Application](applicationService.md) | แอป · ธีม · โดเมน | | [Authentication](authenticationService.md) | บัญชีผู้ใช้ · login · credential ของแอป | | [ACL](aclService.md) | permission · ผูก role · UserPolicy | | [Unit](unitService.md) | องค์กร · role · ประเภท/แท็ก · ผู้ติดต่อ · เลขรัน | | [Profile](userService.md) | ข้อมูลส่วนตัวผู้ใช้ | | [Notification](notificationService.md) | แจ้งเตือน in-app · email · SMS | | [Schedule](scheduleService.md) | ตั้งเวลา / งานตามรอบ แทน cron ในแต่ละ service | | [Storage](storageService.md) | อัปโหลด / ดาวน์โหลดไฟล์ | ## สำหรับ AI ให้ AI อ่านเอกสารนี้ได้ทันทีที่ [`/llms.txt`](https://docs.gumon.io/llms.txt) (สารบัญ) และ [`/llms-full.txt`](https://docs.gumon.io/llms-full.txt) (ทุกหน้าในไฟล์เดียว) --- # แนวคิด Gumon Gumon คือระบบแบบ **Composable System = Microservices + Event-Driven** แอปหนึ่งตัวเกิดจากการหยิบ service สำเร็จรูปหลายตัวมาประกอบกัน service ตัวเดียวให้บริการได้หลายแอปพร้อมกัน · สร้างแอปใหม่จาก service ที่มีอยู่ได้ทันที · เพิ่ม service ใหม่ได้โดยไม่กระทบของเดิม - [3 ชั้นของระบบ](#3-layers) - [กฎ: service คุยกันผ่าน Kafka เท่านั้น](#kafka-only) - [app และ organization](#tenancy) - [header appKey และ serviceKey](#headers) - [core set กับ service ธุรกิจ](#core-set) ---
## 3 ชั้นของระบบ ``` Data Stream Layer (Kafka) ◀──▶ API Service Layer ◀──▶ Application Layer event streaming / data sync core set + service ธุรกิจ หน้าบ้าน: admin · เว็บ · มือถือ · kiosk ``` | ชั้น | หน้าที่ | |---|---| | **Data Stream Layer** | Kafka เป็นทางเดินข้อมูลระหว่าง service ทั้งหมด ทั้ง event และการ sync ข้อมูล | | **API Service Layer** | service พร้อมใช้ แต่ละตัวมี GraphQL API ของตัวเอง ฐานข้อมูลของตัวเอง และรองรับหลายแอป | | **Application Layer** | หน้าบ้านทุกชนิด เรียก GraphQL ของแต่ละ service ตรง ดู [หน้าบ้าน (Admin UI)](frontends.md) | ---
## กฎ: service ↔ service คุยกันผ่าน Kafka เท่านั้น - service **ห้าม** เรียก API (GraphQL/HTTP) ของ service อื่นตรง ๆ ให้ส่งและรับข้อมูลผ่าน Kafka topic - **หน้าบ้าน** เรียก GraphQL API ของแต่ละ service ได้ตรง ### ทำไมต้องเป็น event ถ้า service A เรียก service B ตรง ๆ วันที่มี service C อยากรู้เหตุการณ์เดียวกันเพื่อไปทำงานของตัวเองต่อ **ต้องกลับไปแก้ A** ถ้า A ปล่อย event ออกมา C แค่ subscribe topic นั้นเพิ่ม **ของเดิมไม่ต้องแตะเลย** ผลที่ได้ตามมา - แต่ละ service เก็บ **สำเนา** ข้อมูลที่ตัวเองต้องใช้ (ข้อมูลแอป, organization, สิทธิ์ผู้ใช้ ฯลฯ) ไว้ใน DB/Redis ของตัวเอง ⇒ ไม่ต้องรอถามใคร ทำงานต่อได้แม้ต้นทางไม่อยู่ - service ที่เข้ามาทีหลังตามข้อมูลเดิมทันได้ด้วย topic `refresh-data` (ดู [วงจรชีวิตของ service](serviceLifecycle.md)) - ตรวจสิทธิ์ได้ในตัว เพราะ UserPolicy ถูกส่งมาให้ล่วงหน้าผ่าน `sync-user-policy` ไม่ต้องถาม service อื่นตอนรับ request - service เดียวกันรันหลาย replica ได้ — consumer group ของ Kafka ส่งแต่ละข้อความให้ replica เดียว สิ่งที่ต้องออกแบบรองรับ: ข้อมูลระหว่าง service เป็นแบบ eventually consistent (สำเนาจะตามมาภายหลังเล็กน้อย) ---
## app และ organization ``` app (appKey) ─┬─ organization (organizationId / orgKey) ─ องค์กรลูก └─ user (authId) — บัญชีผูกกับแอป ``` | ระดับ | ความหมาย | |---|---| | **app** | โซลูชันหนึ่งชุด มีธีม โดเมน และ credential ของตัวเอง · ระบุด้วย `appKey` · แอป `SYSTEM` ถูกสร้างตอนตั้งระบบครั้งแรก | | **organization** | หน่วยงานภายในแอป จัดเป็นต้นไม้ (มีองค์กรลูกได้) · role ระดับองค์กรกำหนดสิทธิ์ลงองค์กรลูกได้ | | **user** | บัญชีผู้ใช้ผูกกับแอป — คนเดียวใช้ 2 แอปคือ 2 บัญชี | ทุก service เก็บข้อมูลของทุกแอปไว้รวมกัน โดยทุก record มี `appKey` กำกับ ---
## header appKey และ serviceKey ข้อความ Kafka ระหว่าง service มี header 2 ตัว | header | ความหมาย | |---|---| | `appKey` | ข้อความนี้เป็นข้อมูลของแอปไหน | | `serviceKey` | **service ปลายทาง** ที่ข้อความนี้ส่งถึง (ไม่ใส่ = ส่งถึงทุก service) | > ⚠️ `serviceKey` คือ **ปลายทาง** ไม่ใช่ตัวผู้ส่ง เช่น service ธุรกิจส่ง `set-schedule` ให้ใส่ `serviceKey: schedule` · ส่ง `create-notification` ใส่ `notification` · ส่ง `sync-permission` ใส่ `access-control` · ส่ง `sync-file-upload` ใส่ `storage` service ปลายทางรับข้อความของแอปใดได้ก็ต่อเมื่อตัวเองถูกเพิ่มเข้าแอปนั้นแล้ว (มี AppCertificate ของแอปนั้น) ดู [วงจรชีวิตของ service](serviceLifecycle.md#app-level) ---
## core set กับ service ธุรกิจ ตอนลงทะเบียน service มีค่า `isCoreSet` - `isCoreSet: true` — service ที่ระบบต้องมีเพื่อให้ทำงานได้ **โดยยังไม่มีธุรกิจใดเกี่ยวข้อง** · ถูกตั้งขึ้นพร้อมกันตอน `init-system` และถูกผูกเข้าทุกแอปใหม่อัตโนมัติ - `isCoreSet: false` — **service ธุรกิจ** (ค่าเริ่มต้น) เช่น CMS, content หรือ service ที่ทีมสร้างเองตามงาน · ลงทะเบียนเข้าระบบและเพิ่มเข้าแอปที่ต้องใช้ทีละแอป ### core set | serviceKey | หน้าที่ | |---|---| | `core` | ทะเบียน service · ผู้ออกกุญแจ (SystemCertificate / AppCertificate) · ค่าตั้ง service · สั่ง `refresh-data` · `init-system` — [Core Service](coreService.md) | | `authentication` | บัญชีผู้ใช้ · login ทุกแบบ · token · เจ้าของ AppCredential — [Authentication Service](authenticationService.md) | | `application` | ข้อมูลแอปและธีม · HostTheme (โดเมน → แอป) — [Application Service](applicationService.md) | | `access-control` | ทะเบียน permission · role · Custom Menu · คอมไพล์ UserPolicy แล้วกระจาย — [ACL Service](aclService.md) | | `unit` | organization (ต้นไม้) · นิยาม role · running number · รวมงาน label เดิมไว้ด้วย | | `profile` | ข้อมูลส่วนตัวผู้ใช้ · ฟิลด์เสริมที่แอปกำหนดเอง | | `notification` | แจ้งเตือน in-app · email · SMS — [Notification Service](notificationService.md) | | `schedule` | ตัวกลางตั้งเวลา (cron) ของทั้งระบบ — [Schedule Service](scheduleService.md) | | `storage` | ไฟล์ · presigned URL · metadata — [Storage Service](storageService.md) | > `label` เดิมถูกยุบรวมเข้า `unit` แล้ว ไม่นับเป็น service แยก service ธุรกิจใช้ความสามารถของ core set ผ่าน topic เท่านั้น ดูรายการที่ [Kafka topic มาตรฐาน](standardTopics.md) --- > อัปเดตจากคำผู้พัฒนาและโค้ด gumon-tech · 2026-10-05 --- # Kafka topic มาตรฐาน service ทุกตัวใน Gumon คุยกันผ่าน Kafka (ดู [แนวคิด Gumon](concept.md#kafka-only)) topic ใน Gumon แบ่ง 4 กลุ่ม — หน้านี้รวม 3 กลุ่มแรก กลุ่มสุดท้ายอยู่ในหน้าของแต่ละ service | กลุ่ม | คืออะไร | service ใหม่ต้องสนใจไหม | |---|---|---| | [topic มาตรฐาน](#engine-topics) | สัญญากลางระหว่าง core set กับ **ทุก** service | ✔ ตามระดับใน [สิ่งที่ Service ต้องมี](serviceX.md) | | [topic ภายใน core set](#core-set-internal) | core set ใช้ประสานกันเอง | ✘ แค่รู้ว่ามี | | [topic บริการกลาง](#central-topics) | เรียกใช้ schedule · notification · storage · เลขรัน | เมื่อต้องใช้บริการนั้น | | [topic ของแต่ละ service](#per-service) | ข้อมูลธุรกิจเฉพาะของ service นั้น (`sync-` ฯลฯ) | เมื่อต้องใช้ข้อมูลของ service นั้น | ชื่อ topic ในโค้ดเป็น kebab-case (`sync-app-certificate`) บางครั้งเรียกแบบ camelCase (`syncAppCertificate`) — คือตัวเดียวกัน - [รูปแบบข้อความ](#message-format) - [topic มาตรฐานของ engine](#engine-topics) - [topic บริการกลางที่ service ธุรกิจใช้](#central-topics) - [topic ของแต่ละ service](#per-service) ---
## รูปแบบข้อความ - header: `appKey` (ข้อมูลของแอปไหน) + `serviceKey` (**service ปลายทาง** · ไม่ใส่ = ส่งถึงทุกตัว) - payload รูปเดียวกันทุก topic: `{ action, : {...} }` โดย `action` เป็นเช่น `ADD` · `REMOVE` · `REMOVE_APP` - consumer group ตั้งชื่อ `${SERVICE_KEY}-consumer-${topic}` ⇒ หลาย replica ของ service เดียวกันแบ่งงานกัน ข้อความหนึ่งถูกประมวลผลครั้งเดียว - ก่อนทำงานกับข้อความของแอปใด service ตรวจว่าตัวเองมี AppCertificate ของแอปนั้น (ถูกเพิ่มเข้าแอปแล้ว) ไม่มี = ไม่รับ ---
## topic มาตรฐานของ engine คอลัมน์ "ทุก service ต้องรับ/ส่ง" หมายถึง service ธุรกิจตัวใหม่ต้องทำ topic นี้ด้วยหรือไม่ | topic (โค้ด) | ชื่อเรียก | ผู้ส่ง → ผู้รับ | ใช้ทำอะไร | ทุก service ต้องรับ/ส่ง | |---|---|---|---|---| | `init-system` | initSystem | core → core set | ตั้งระบบจากศูนย์: สร้างแอป `SYSTEM` แล้วให้ core set แต่ละตัวสร้างข้อมูลตั้งต้นของตัวเอง | ไม่ — **เฉพาะ core set** | | `refresh-data` | refreshData | core → ทุก service (header `serviceKey` = ปลายทาง) | สั่งให้ service ส่งข้อมูลที่ตัวเองเป็นเจ้าของขึ้นไปใหม่ (รวม `sync-permission`) เพื่อให้ service ที่เพิ่งเข้ามาทำงานต่อได้ · และล้าง cache Redis ของตัวเอง | **ต้องรับ** | | `health-check` → `health-check-result` | healthCheck | core → service → core | ถามว่า service ยังทำงานอยู่ไหม | ไม่บังคับ | | `hand-check` → `hand-check-result` | handCheck / handCheckResult | core → service → core | ถามว่าปลายทางยังถือกุญแจ (SystemCertificate) ถูกต้องไหม | ไม่บังคับ | | `register-service` | registerService | service → core | service ลงทะเบียน/ประกาศตัวเองกับ core ผ่าน Kafka · core ตอบด้วย `hand-check` | แนะนำ | | `sync-permission` | syncPermission | ทุก service → access-control (header `serviceKey: access-control`) | service ประกาศ permission ของตัวเองให้ ACL รู้ เพื่อนำไปผูกกับ role | **ต้องส่ง** (ตอนได้ `refresh-data`) | | `sync-app-certificate` | syncAppCertificate | core → ทุก service | แจก AppCertificate ของ service ในแอป (privateKey เข้ารหัสด้วย SystemCertificate ของปลายทาง) · เป็นตัวบอกว่า service ไหนอยู่ในแอปไหน | **ต้องรับ** | | `sync-app-credential` | syncAppCredential | authentication → ทุก service | ข้อมูลการเข้าใช้ของแอป: `clientId` · key ตรวจ token (`jwtAccessSecretKey`, `jwtRefreshSecretKey`) · กฎของ token · รวม credential type `SYSTEM` (= apiKey สำหรับระบบภายนอก: `clientId` + `clientSecret`) | **ต้องรับ** | | `sync-application` | syncApplication | application → ทุก service | ข้อมูลแอป (เพิ่ม/แก้/ลบ) · core ใช้ topic นี้ผูก core set เข้าแอปใหม่อัตโนมัติ | **ต้องรับ** | | `sync-organization` | syncOrganization | unit → service ที่ใช้ organization | ข้อมูล organization | รับถ้า service ใช้ org | | `sync-user-policy` | syncUserPolicy | access-control → service เจ้าของ permission (header `serviceKey` = ปลายทาง) | UserPolicy ที่คอมไพล์แล้ว พร้อมใช้ตรวจสิทธิ์ (ดูรูป key ด้านล่าง) | **ต้องรับ** | | `sync-service-setting` | syncServiceSetting | core → service | JSON ตั้งค่าเพิ่มเติมของ service **ทั้งระบบ** แก้ผ่าน core ได้โดยไม่ต้องแก้ env | รับถ้ามีค่าตั้ง | | `sync-app-service-setting` | syncAppServiceSetting | core → service | JSON ตั้งค่าเพิ่มเติมของ service **เฉพาะแอป** (เช่น ค่า OAuth ของ login ผ่าน Google/Facebook/Apple ใน authentication) | รับถ้ามีค่าตั้งต่อแอป | | `sync-auth` | syncAuth | authentication → service ที่ต้องใช้ | profile ของ user ที่ authentication ถือ (ชื่อ, `defaultOrganizationKey`) | ไม่บังคับ แต่ใช้บ่อย | ### รูป key ของ UserPolicy service รับ `sync-user-policy` มาเก็บไว้ใน DB/Redis ของตัวเอง ตอนรับ request ก็สร้าง key แล้วค้นได้ทันที ไม่ต้องถาม ACL ``` ${SERVICE_KEY}::${permissionKey}:${appKey}::${authId} สิทธิ์ระดับแอป ${SERVICE_KEY}::${permissionKey}:${appKey}:${organizationId}:${authId} สิทธิ์ระดับองค์กร ``` ### topic ที่ core set ใช้ประสานกันเอง **ไม่ใช่ topic มาตรฐาน** — service ธุรกิจไม่ต้องรับ แต่ควรรู้ว่ามีอยู่ | topic | ผู้ส่ง → ผู้รับ | ใช้ทำอะไร | |---|---|---| | `sync-service` | core → access-control | ทะเบียน service (ชนิด, `urlFrontend`, `urlGetMetaData`) ให้ ACL ดึงเมนูของหน้า admin ย่อย (ดู [หน้าบ้าน](frontends.md#menu-metadata)) | | `add-admin-app-role` | core → unit | แอปใหม่ ⇒ unit สร้าง role `admin` ของแอป | | `sync-profile` | profile → service ที่ใช้ profile | ข้อมูล profile หลังแก้ไข | ---
## topic บริการกลางที่ service ธุรกิจใช้ service ธุรกิจใช้ความสามารถของ core set ผ่าน topic เหล่านี้ — **header `serviceKey` ใส่ key ของบริการปลายทาง** | บริการ | ส่งขอ (serviceKey) | ผลที่ได้กลับ | ใช้ทำอะไร | |---|---|---|---| | ตั้งเวลา | `set-schedule` `{ADD/REMOVE, schedule}` (`schedule`) | `schedule-alarm` เมื่อถึงเวลา (header `serviceKey` = ผู้ขอ) → ผู้ขอตอบ `schedule-alarm-result` | นัดครั้งเดียวหรือวนซ้ำ · ฝาก `alarmData` ไปกับนัดได้ · service จึงไม่ต้องมี cron ของตัวเองและรันหลาย replica ได้โดยไม่ยิงซ้ำ — [Schedule Service](scheduleService.md) | | แจ้งเตือน | `create-notification` (`notification`) | `create-notification-result` | แจ้งเตือน in-app / email / SMS · ตั้งเวลาส่งได้ — [Notification Service](notificationService.md) | | ไฟล์ | `sync-file-upload` (`storage`) | — | ลงทะเบียนไฟล์ที่ service เขียนลง storage เอง (ปกติหน้าบ้านขอ presigned URL จาก GraphQL ของ storage ตรง) — [Storage Service](storageService.md) | | ประกาศ permission | `sync-permission` (`access-control`) | `sync-user-policy` | ดูตารางด้านบน — [ACL Service](aclService.md) | | เลขรันนิ่ง | `register-custom-running-number` · `generate-running-number` (`unit`) | `sync-custom-running-number` · `generated-running-number-result` | ขอเลขเอกสารต่อเนื่องตามรูปแบบที่กำหนด | ---
## topic ของแต่ละ service topic ที่เป็นข้อมูลเฉพาะของ service หนึ่ง (เช่น `sync-organization` ของ unit · `sync-auth` ของ authentication) อยู่ในหน้าของ service นั้น หัวข้อ **kafka consume** (รับ) และ **Kafka Produce** (ส่ง) | service | topic ที่รับ | topic ที่ส่ง | |---|---|---| | [Core](coreService.md) | [รับ](coreService.md#kafka-consume-reference) | [ส่ง](coreService.md#kafka-produce-reference) | | [Application](applicationService.md) | [รับ](applicationService.md#kafka-consume-reference) | [ส่ง](applicationService.md#kafka-produce-reference) | | [Authentication](authenticationService.md) | [รับ](authenticationService.md#kafka-consume-reference) | [ส่ง](authenticationService.md#kafka-produce-reference) | | [ACL](aclService.md) | [รับ](aclService.md#kafka-consume-reference) | [ส่ง](aclService.md#kafka-produce-reference) | | [Unit](unitService.md) | [รับ](unitService.md#kafka-consume-reference) | [ส่ง](unitService.md#kafka-produce-reference) | | [Profile](userService.md) | [รับ](userService.md#kafka-consume-reference) | [ส่ง](userService.md#kafka-produce-reference) | | [Notification](notificationService.md) | [รับ](notificationService.md#kafka-consume-reference) | [ส่ง](notificationService.md#kafka-produce-reference) | | [Schedule](scheduleService.md) | [รับ](scheduleService.md#kafka-consume-reference) | [ส่ง](scheduleService.md#kafka-produce-reference) | | [Storage](storageService.md) | [รับ](storageService.md#kafka-consume-reference) | [ส่ง](storageService.md#kafka-produce-reference) | --- > อัปเดตจากคำผู้พัฒนาและโค้ด gumon-tech · 2026-10-05 --- # วงจรชีวิตของ service service หนึ่งตัวจะทำงานกับข้อมูลของแอปใดได้ ต้องผ่าน 2 ระดับ: **เข้าระบบ** (ระดับระบบ) แล้ว **เข้าแอป** (ระดับแอป) หน้านี้อธิบายลำดับตั้งแต่ตั้งระบบจากศูนย์ การเสียบ service เข้า-ออก และกลไก "ตามทัน" ข้อมูล - [ตั้งระบบจากศูนย์ (init-system)](#init-system) - [ระดับระบบ: register / resign](#system-level) - [ระดับแอป: เพิ่ม / ถอด service ในแอป](#app-level) - [refresh-data: ตามข้อมูลให้ทัน](#refresh-data) - [กุญแจ 3 ชั้น (ภาพรวม)](#keys) - [ลำดับการเสียบ service ใหม่](#plug-in) ---
## ตั้งระบบจากศูนย์ (`init-system`) ทำครั้งเดียวตอนติดตั้งระบบ (ผ่าน [Gumon CLI](gumoncli.md) หรือชุด docker ของ core set) — **เกี่ยวเฉพาะ core set** 1. core สร้างแอป `SYSTEM` และผู้ใช้ผู้ดูแลระบบคนแรก 2. core ลงทะเบียน service ใน core set ทุกตัว พร้อมออก SystemCertificate ให้ทีละตัว 3. core ผูก core set ทุกตัวเข้าแอป `SYSTEM` (AppCertificate · AppCredential · ค่าตั้ง service) 4. core ส่ง `init-system` ⇒ core set แต่ละตัวสร้างข้อมูลตั้งต้นของตัวเอง 5. ได้กุญแจของแต่ละ service ไว้ติดตั้งคู่กับ service นั้น จากนั้นรันทุกตัวตามปกติ หลังจากนี้ ทุกแอปใหม่ที่สร้าง (`sync-application`) core จะผูก core set ทุกตัวเข้าแอปนั้นให้อัตโนมัติ และ unit สร้าง role `admin` ของแอปให้ (`add-admin-app-role`) ---
## ระดับระบบ: `registerService` / `resignService` ทำที่ **system-admin › Service** (GraphQL ของ core) | | ผล | |---|---| | **register** | service ถูกบันทึกในทะเบียนของระบบ · core ออก **SystemCertificate** ใหม่ให้ service นั้น (ได้รับครั้งเดียวตอนลงทะเบียน เก็บไว้กับ service ห้าม commit) · ระบุ `isCoreSet` (service ธุรกิจ = `false`) และ URL หน้า admin ย่อยถ้ามี | | **resign** | ลบข้อมูล service นั้นออกจากระบบ · ระบบไม่ส่งข้อมูลใดถึง service นั้นอีก · ถ้าจะกลับมาต้อง register ใหม่และได้กุญแจชุดใหม่ | service ต้องถอดออกจากทุกแอปก่อน resign และ service ใน core set ถอดออกไม่ได้ service ลงทะเบียน/ประกาศตัวผ่าน Kafka ได้ด้วย topic `register-service` (ดู [Kafka topic มาตรฐาน](standardTopics.md)) ---
## ระดับแอป: `addServiceToApp` / `removeServiceFromApp` ทำที่ **system-admin › app › app-service** - **add** — core สร้าง **AppCertificate** ของ service ในแอปนั้นแล้วส่งผ่าน `sync-app-certificate` · คัดลอกค่าตั้งของ service มาเป็นค่าตั้งของแอป · ถ้าเลือก refresh data core จะส่ง `refresh-data` ให้ทุก service ในแอป ⇒ service ที่เพิ่งเข้ามาได้ข้อมูลเดิมของแอปครบ - **remove** — ลบ AppCertificate ของ service ในแอปนั้น ⇒ service รับข้อมูลของแอปนั้นไม่ได้อีก **AppCertificate เป็นตัวตัดสินว่า service เห็นแอปไหนได้** — service ตรวจทุกข้อความว่ามี AppCertificate ของตัวเองในแอปนั้นก่อนทำงาน ไม่มี = ไม่รับ ดังนั้น service ที่ไม่ได้ถูกเพิ่มเข้าแอปใด ใช้ข้อมูลของแอปนั้นไม่ได้ และใช้ข้ามแอปไม่ได้ ---
## `refresh-data`: ตามข้อมูลให้ทัน เพราะแต่ละ service เก็บสำเนาข้อมูลที่ตัวเองต้องใช้ไว้เอง service ที่เพิ่งเข้ามาจึงต้องได้ข้อมูลเดิมก่อนจะทำงานได้ 1. core ส่ง `refresh-data` (header `serviceKey` = service ปลายทาง) 2. service ปลายทางกันการทำซ้ำด้วย id ของคำสั่ง แล้ว **ส่งข้อมูลที่ตัวเองเป็นเจ้าของขึ้นไปใหม่** เช่น application ส่ง `sync-application` · unit ส่ง organization/role · service ธุรกิจส่งข้อมูล domain ของตัวเอง 3. ทุก service ส่ง `sync-permission` ของตัวเองให้ access-control 4. service ล้าง cache Redis ของตัวเอง สั่งเองได้ที่ **system-admin › app › refresh-data** (เลือกทุก service หรือบางตัว) เช่นหลังแก้เมนูของหน้า admin ย่อย หรือเมื่อต้องการให้สำเนาข้อมูลตรงกันใหม่ ---
## กุญแจ 3 ชั้น (ภาพรวม) | ชั้น | ผูกกับ | เกิดเมื่อ | ใช้ทำอะไร | |---|---|---|---| | **SystemCertificate** | service หนึ่งตัว | register service (core set: ตอน init-system) | ยืนยันตัวตนของ service ต่อ core · ให้ core ส่งข้อมูลที่มีแต่ service นั้นถอดได้ (เช่น privateKey ของ AppCertificate) | | **AppCertificate** | service หนึ่งตัวในแอปหนึ่งแอป | เพิ่ม service เข้าแอป | publicKey/privateKey สำหรับเข้ารหัสข้อมูลถึง service นั้นในแอปนั้นเท่านั้น · เป็นตารางเดียวที่บอกว่า service ไหนมีสิทธิ์ในแอปไหน | | **AppCredential** | แอป (ต่อ `clientId`) | สร้าง credential ของแอปใน authentication | ตรวจ token ของผู้ใช้ในแอป (key ตรวจ JWT + กฎของ token) · credential type `SYSTEM` ให้ระบบภายนอกเรียก API ด้วย `clientId` + `clientSecret` | ทั้ง AppCertificate และ AppCredential ถูก sync มาไว้ที่ทุก service ล่วงหน้า ⇒ service ตรวจ token และสิทธิ์ของ request ได้เองโดยไม่ต้องถามใคร ---
## ลำดับการเสียบ service ใหม่ 1. สร้าง service จาก template แล้วตั้ง `serviceKey` ของตัวเอง (ดู [สร้าง Service ใหม่](createService.md) · [สิ่งที่ Service ต้องมี](serviceX.md)) 2. register service ใน system-admin (`isCoreSet: false`) แล้วเก็บกุญแจที่ได้ไว้กับ service 3. เพิ่ม service เข้าแอปที่ต้องใช้ ⇒ service ได้ AppCertificate · AppCredential · ข้อมูลแอป · UserPolicy ⇒ ตรวจ request จากหน้าบ้านได้ทันที 4. สั่ง `refresh-data` ⇒ service ส่ง `sync-permission` ให้ access-control แล้วผู้ดูแลผูก permission เข้ากับ role 5. ต่องาน domain: เพิ่ม topic ที่ต้องรับ/ส่ง · เก็บสำเนาข้อมูลของ service อื่นไว้ใช้เอง 6. ใช้บริการกลางผ่าน topic (ตั้งเวลา · แจ้งเตือน · ไฟล์) — ไม่ทำ cron เอง ให้ใช้ schedule 7. ถ้ามีหน้า admin ย่อย: ทำตาม [หน้าบ้าน (Admin UI)](frontends.md#menu-metadata) --- > อัปเดตจากคำผู้พัฒนาและโค้ด gumon-tech · 2026-10-05 --- # หน้าบ้าน (Admin UI) หน้าบ้านอยู่ใน Application Layer — **เรียก GraphQL API ของแต่ละ service ตรง** (ต่างจาก service ↔ service ที่ต้องผ่าน Kafka · ดู [แนวคิด Gumon](concept.md)) หน้า admin ของ Gumon แบ่งเป็น 3 ชนิด - [3 ชนิดของหน้า admin](#types) - [dynamic-admin ทำงานอย่างไร](#dynamic-admin) - [เมนูของ admin ย่อยเข้าระบบได้อย่างไร](#menu-metadata) ---
## 3 ชนิดของหน้า admin - **system admin** คือที่ที่ผู้ดูแลระบบตั้งค่าไว้ก่อน (แอป · service · permission · เมนูกลาง) แล้วส่งต่อให้ผู้ใช้จริง - **dynamic-admin** คือที่ที่ผู้ใช้จริงของแต่ละแอปตั้งค่าต่อเอง (ผูก permission เข้า role · user · องค์กร) ทั้งระดับแอปและระดับองค์กร - **หลักการ: 1 API ธุรกิจ = 1 หน้า admin ย่อยประกบเสมอ** ฝังใน dynamic-admin ให้ขึ้นงานได้เร็วด้วยของกลาง · ทำ **admin แยก** เฉพาะเมื่อลูกค้าต้องการหน้าของตัวเองที่มี flow เฉพาะมาก - **ทางที่นิยมตอนนี้:** เริ่มจาก `gumon-admin-template` (มี AGENTS.md และ skill สำหรับให้ AI สร้างหน้าจาก GraphQL) พร้อมไฟล์ `.gql` ของ service ที่ใช้ — ขึ้นหน้า admin ได้เร็วกว่าการทำ admin ย่อยแบบ iframe | ชนิด | repo | ใช้ทำอะไร | |---|---|---| | **system admin** — ตั้งค่าทั้งระบบ | `gumon-system-admin` | สร้างแอป · Custom Menu · Permission · Service (register / resign) · Service hand-check · Service ready · service settings · system certificate · Theme · HostTheme · และงานระดับแอป เช่น เพิ่ม/ถอด service ในแอป · credential · refresh-data · role · user | | **dynamic-admin** — ศูนย์กลางของแต่ละแอป | `gumon-dynamic-admin` | ผู้ดูแลของแอปเข้ามาจัดการ user / role / organization แล้วเปิดหน้า admin ย่อยของแต่ละ service ได้จากที่เดียว ตามเมนูที่ role ของตัวเองเห็น | | **admin เฉพาะงาน** — ตรงงาน 100% | `gumon-admin-template` (แบบ standalone) · admin ย่อยจาก `gumon-dynamic-admin-iframe-template` (เช่น `gumon-project-management-admin`) | standalone: แอป admin แยกของตัวเอง login เอง · admin ย่อย: หน้าจอของ service หนึ่งตัวที่ถูกโหลดเข้าไปอยู่ใน dynamic-admin | ทุกตัวใช้ Next.js (App Router) + Ant Design + Apollo Client · ส่ง header `Authorization` (token ผู้ใช้ ซึ่งมี clientId และ appKey อยู่ในตัวแล้ว) ไปหา service · `X-APP-CLIENT-ID` ใช้เฉพาะตอนยังไม่มี token เช่น หน้า login หรือหน้าสาธารณะ · admin ย่อยจึงส่งแค่ `Authorization` ได้ ---
## dynamic-admin ทำงานอย่างไร 1. เปิดเว็บที่โดเมนของแอป → dynamic-admin หาแอปจาก hostname ด้วย **HostTheme** ของ application (ได้ appKey, clientId, ธีม) ⇒ deployment เดียวรับได้หลายแอปตามโดเมน 2. ผู้ใช้ login ผ่าน authentication 3. ดึงเมนูของผู้ใช้จาก access-control (`getMyCustomMenus` ระดับแอปหรือระดับองค์กร) — เมนูถูกกรองตาม role ของผู้ใช้แล้ว 4. แสดงหน้าตามชนิดของเมนู (`CustomMenu.type`) | type | พฤติกรรม | |---|---| | `INTERNAL` | หน้าที่อยู่ในตัว dynamic-admin เอง (เช่น จัดการ user / role / organization) | | `IFRAME` | โหลดหน้า admin ย่อยของ service ผ่าน iframe — **วิธีที่ใช้อยู่ในปัจจุบัน** | | `MICRO_FRONTEND` | โหลดหน้า admin ย่อยแบบ micro frontend — **อยู่ในแผน** | | `EXTERNAL_LINK` · `EXTERNAL_DOWNLOAD` | ลิงก์ออกนอกระบบ / ดาวน์โหลด — อยู่ในแผน | ### admin ย่อยแบบ iframe dynamic-admin เป็นผู้ถือ session ของผู้ใช้ แล้วส่งข้อมูลที่ admin ย่อยต้องใช้ให้ผ่าน `postMessage` | ทิศ | ข้อความ | เนื้อหา | |---|---|---| | dynamic-admin → admin ย่อย | `'are-you-ready'` | ถามว่าพร้อมรับข้อมูลหรือยัง | | admin ย่อย → dynamic-admin | `'iframe-ready'` | พร้อมรับข้อมูล | | dynamic-admin → admin ย่อย | `{type:'auth:update', ...}` | `accessToken` · `themeData` (appKey, clientId, theme) · `locale` · `isDarkMode` · `paramData` (query params ของเมนู) · `orgKey` | | admin ย่อย → dynamic-admin | `{type:'resize', height}` | ปรับความสูงของ iframe | | admin ย่อย → dynamic-admin | `{type:'path-change', path}` | แจ้ง path ปัจจุบัน | | admin ย่อย → dynamic-admin | `{type:'loading-status', loading}` | แจ้งสถานะกำลังโหลด | - dynamic-admin เป็นผู้ต่ออายุ token แล้วส่ง `auth:update` ใหม่ให้ admin ย่อยเอง — admin ย่อยใช้แค่ accessToken เรียก GraphQL ของ service - เริ่มทำ admin ย่อยจาก `gumon-dynamic-admin-iframe-template` ซึ่งรับส่งข้อความชุดนี้ไว้ให้แล้ว - dynamic-admin มีหน้า dev tool ไว้ทดสอบ admin ย่อยที่รันบนเครื่องตัวเอง (localhost) โดยไม่ต้องลงทะเบียนเมนู ---
## เมนูของ admin ย่อยเข้าระบบได้อย่างไร 1. admin ย่อยเปิด endpoint **menu metadata** `GET /api/menuMetaData` คืนรายการ `CustomMenu[]` ของตัวเอง (ชื่อเมนู · type · url · query params) 2. ตอน register service ใน system-admin กรอก `urlFrontend` (ที่อยู่ของ admin ย่อย) และ `urlGetMetaData` (ที่อยู่ของ endpoint ข้อ 1) 3. core ส่ง `sync-service` ให้ access-control 4. access-control เรียก `urlGetMetaData` แล้วบันทึกเมนูของ service นั้น 5. แก้เมนูภายหลัง → สั่ง `refresh-data` ให้ access-control ดึงเมนูใหม่ 6. ผู้ดูแลแอปผูกเมนูเข้ากับ role ใน dynamic-admin › user management ⇒ ผู้ใช้ที่มี role นั้นเห็นเมนูใน dynamic-admin --- > อัปเดตจากคำผู้พัฒนาและโค้ด gumon-tech · 2026-10-05 --- # สิ่งที่ Service ต้องมี **ข้อจำกัดเดียวของการต่อเข้า Gumon: service ต้องคุยผ่าน Kafka ได้** จะเขียนด้วยภาษาอะไร ใช้ฐานข้อมูลอะไรก็ได้ ขอแค่รับ–ส่งข้อความ Kafka ตามกติกาในหน้านี้ หน้านี้รวมเฉพาะสิ่งที่จำเป็นจริง แบ่ง 3 ระดับ — เริ่มจากระดับ 1 แล้วเพิ่มเท่าที่ service ต้องใช้ ถ้าเริ่มจาก `gumon-backend-template` จะได้ทั้งหมดนี้มาแล้ว ดูขั้นตอนที่ [สร้าง Service ใหม่](createService.md) | ระดับ | เมื่อไร | รับ (consume) | ส่ง (produce) | | --- | --- | --- | --- | | **1 · ต้องมีทุกตัว** | ทุก service | `sync-app-certificate` · `refresh-data` · `hand-check` | `register-service` · `hand-check-result` | | **2 · มี API ให้ผู้ใช้** | service มี GraphQL/REST ที่ต้อง login | `sync-app-credential` · `sync-user-policy` | `sync-permission` | | **3 · ใช้เมื่อจำเป็น** | ต้องการข้อมูลหรือบริการกลาง | `sync-application` · `sync-organization` · `sync-auth` · `sync-service-setting` · `sync-app-service-setting` · `schedule-alarm` | `set-schedule` · `create-notification` · `sync-file-upload` · `sync-<ข้อมูลของตัวเอง>` | --- ## กติกาการส่งข้อความ **ห้ามยิง API ตรงระหว่าง service** — ส่ง event ผ่าน Kafka แทน เหตุผล: ถ้า A เรียก B ตรง วันที่ C อยากรับเหตุการณ์เดียวกัน ต้องไปแก้ A · ถ้าเป็น event, C แค่ subscribe เพิ่ม (หน้าบ้านเรียก GraphQL ของ service ได้ตรงตามปกติ) | เรื่อง | กติกา | | --- | --- | | header `appKey` | แอปที่ข้อมูลนี้เป็นของ | | header `serviceKey` | **service ปลายทาง** — ไม่ใช่ตัวผู้ส่ง (ข้อความกระจายทั้งแอปใส่แค่ `appKey`) | | payload | JSON `{ "action": "ADD" \| "REMOVE" \| ..., "": { ... } }` | | ชื่อ topic | kebab-case เช่น `sync-app-credential` | | consumer group | `-consumer-` ⇒ replica หลายตัวของ service เดียวกันแบ่งงานกัน ข้อความหนึ่งทำครั้งเดียว | ### ฝั่งรับ: กรองข้อความอย่างไร ข้อความมี 2 แบบ — **กรอง `serviceKey` เฉพาะแบบส่งถึง service เดียว** ถ้าไปกรองแบบกระจายด้วย จะทิ้งข้อมูลที่ควรได้ | แบบ | topic | header `serviceKey` | ฝั่งรับทำอะไร | | --- | --- | --- | --- | | **ส่งถึง service เดียว** | `refresh-data` · `hand-check` · `sync-app-credential` · `sync-user-policy` · `sync-service-setting` · `sync-app-service-setting` · `schedule-alarm` · ทุกข้อความที่เราส่งหาบริการกลาง | key ของปลายทาง | ไม่ใช่ของเรา ⇒ **ทิ้ง** | | **กระจายทั้งแอป** | `sync-application` · `sync-organization` · `sync-auth` · `sync-profile` · `sync-` ของ service อื่น | ว่าง | **ห้ามกรอง** `serviceKey` | | **พิเศษ** | `sync-app-certificate` | service เจ้าของใบรับรอง | **ห้ามกรอง** — เก็บใบของทุก service ในแอป (ใช้ public key เข้ารหัสข้อมูลถึง service นั้น) · ถอด private key เฉพาะใบที่เป็นของเรา | ทั้งสองแบบ: ข้อความของแอปที่เราไม่มี App Certificate ⇒ ทิ้ง (เราไม่ได้อยู่ในแอปนั้น) --- ## ระดับ 1 · ต้องมีทุกตัว ### กุญแจของ service (`.gumon`) ได้มาตอนลงทะเบียน service กับ core อยู่ที่ `.gumon/certificates//` · service อ่านตอนบูต · **ห้าม commit ลง git** ### `register-service` (ส่ง) และ `hand-check` (รับ) template ทำทั้งสองอย่างให้แล้ว — พิสูจน์กับ core ว่าเราถือกุญแจที่ core ออกให้ ⇒ core ขึ้นสถานะ Service-ready 1. ตอนบูต ส่ง `register-service` หา core (header `serviceKey: core`) 2. core ตอบ `hand-check` ที่มีค่าอ้างอิงเข้ารหัสด้วยกุญแจของเรา 3. ถอดด้วยไฟล์ `certificate` แล้วตอบ `hand-check-result` (header `serviceKey: core`) — ถอดไม่ได้ให้ส่ง `resultData: ""` payload ของ `register-service` | field | ค่า | | --- | --- | | `systemCertificateId` | จากไฟล์ `certificate-id.key` | | `serviceKey` | key ของ service เรา | | `encryptData` | `systemCertificateId` เข้ารหัสด้วย `certificate.pub` | ### `sync-app-certificate` (รับ) บอกว่า service ไหนอยู่ในแอปไหน — **ไม่มี certificate ของเราในแอปนั้น = เราไม่มีสิทธิ์รับข้อมูลแอปนั้น** - `ADD` → เก็บ `{ id, appKey, serviceKey, publicKey, ... }` ของทุก service (ใช้ `publicKey` เข้ารหัสข้อมูลที่ส่งถึง service นั้น) - `REMOVE` → ลบ · ถ้าเป็นของเราเอง แปลว่าเราถูกถอดออกจากแอป ### `refresh-data` (รับ) core สั่งให้ส่งข้อมูลที่เราเป็นเจ้าของขึ้น Kafka ใหม่ — ให้ service ที่เพิ่งเข้าแอปได้ข้อมูลครบ 1. ข้ามถ้าเคยทำ `refreshDataId` นี้แล้ว 2. ส่ง `sync-permission` และ `sync-*` ทุกอย่างที่เราเป็นเจ้าของในแอปนั้นใหม่ 3. ล้าง cache ของตัวเอง (Redis key ที่ขึ้นต้นด้วย `:`) --- ## ระดับ 2 · มี API ให้ผู้ใช้ ### ยืนยันตัวตน | ผู้เรียก | header | ตรวจกับ | | --- | --- | --- | | ผู้ใช้ | `Authorization: Bearer ` | กุญแจ JWT ของแอป จาก `sync-app-credential` | | ระบบภายนอก (apiKey) | `X-APP-CLIENT-ID` + `X-APP-CLIENT-SECRET` | credential ชนิด `SYSTEM` จาก `sync-app-credential` | | ยังไม่ login (หน้า login · สาธารณะ) | `X-APP-CLIENT-ID` | บอกว่าเป็นแอปไหน — เมื่อมี token แล้วไม่ต้องส่ง เพราะ token มี clientId + appKey อยู่แล้ว | ### ตรวจสิทธิ์ ``` service ──sync-permission──▶ access-control ประกาศ permission ของเรา (serviceKey: access-control) ผู้ดูแลผูก permission เข้ากับ role access-control ──sync-user-policy──▶ service UserPolicy พร้อมใช้ (เก็บไว้ใน DB/Redis ของเรา) ``` ตอนเรียก API: สร้าง key แล้วค้นในที่เก็บของเรา — เจอ = มีสิทธิ์ ``` ระดับแอป: ${SERVICE_KEY}::${permissionKey}:${appKey}::${authId} ระดับองค์กร: ${SERVICE_KEY}::${permissionKey}:${appKey}:${organizationId}:${authId} ``` `sync-permission` หนึ่งข้อความต่อหนึ่ง permission: `{ serviceKey, permissionKey, title, description, isGenerateApplication, isGenerateOrganization, isActive }` --- ## ระดับ 3 · ใช้เมื่อจำเป็น | ต้องการ | ทำอย่างไร | | --- | --- | | ข้อมูลแอป · องค์กร · ผู้ใช้ | รับ `sync-application` · `sync-organization` · `sync-auth` แล้วเก็บสำเนาไว้ใช้เอง · `sync-auth` มีชื่อ เบอร์โทร (`phoneNumber`) อีเมล ของผู้ใช้ — ใช้ส่ง SMS / อีเมลหาผู้ใช้ได้โดยไม่ต้องให้กรอกซ้ำ | | ค่าตั้งค่าที่แก้ได้โดยไม่ต้องแก้ env | รับ `sync-service-setting` (ทั้งระบบ) · `sync-app-service-setting` (ต่อแอป) | | ตั้งเวลา / งานตามรอบ | ส่ง `set-schedule` (`serviceKey: schedule`) → รับ `schedule-alarm` เมื่อถึงเวลา — **ใช้แทน cron ในตัว** service จึงรันหลาย replica ได้โดยงานไม่ซ้ำ · [Schedule Service](scheduleService.md) | | ส่งแจ้งเตือน in-app / email / SMS | ส่ง `create-notification` (`serviceKey: notification`) · [Notification Service](notificationService.md) | | เก็บไฟล์ | อัปโหลดผ่าน storage แล้วส่ง `sync-file-upload` (`serviceKey: storage`) · [Storage Service](storageService.md) | | ให้ service อื่นใช้ข้อมูลของเรา | ประกาศ `sync-` ของตัวเอง ส่งเมื่อข้อมูลเปลี่ยน และส่งซ้ำทั้งชุดเมื่อได้ `refresh-data` | | มีหน้า admin ฝังใน dynamic admin | เปิด `GET /api/menuMetaData` คืนรายการเมนู แล้วลงทะเบียนพร้อม `urlFrontend` + `urlGetMetaData` · ดู [หน้าบ้าน](frontends.md) | payload ของทุก topic ดูที่ [Kafka topic มาตรฐาน](standardTopics.md) --- > อัปเดตจากคำผู้พัฒนาและโค้ด gumon-backend-template@6e26695 · gumon-core-service@d872cc3 · 2026-10-05 --- # การสร้าง Service ขั้นตอนสร้าง service ใหม่จาก `gumon-backend-template` (NestJS) ตั้งแต่ clone จนเสียบเข้าแอปและใช้งานได้ > service จะเขียนด้วยภาษา/framework อื่นก็ได้ ขอแค่คุยผ่าน Kafka ตาม [สิ่งที่ Service ต้องมี](serviceX.md) — template แค่ทำให้เริ่มเร็ว - [ขั้นตอน](#ขนตอน) - [API Reference](#api-reference) - [kafka consume Reference](#kafka-consume-reference) - [Kafka Produce Reference](#kafka-produce-reference) - [Checklist](#checklist) --- ## ขั้นตอน ### 1. เริ่มจาก template clone repo `gumon-backend-template` (branch `master`) แล้วตั้งชื่อ service - `src/constants/serviceKey.ts` → `export const SERVICE_KEY: string = 'my-service';` (kebab-case · ตรงกับที่จะลงทะเบียนกับ core) ค่าเริ่มต้น `CHANGE_ME` ทำให้ service **บูตไม่ขึ้นโดยตั้งใจ** — กันลืมตั้ง - `package.json` → `"name": "gumon-my-service-service"` stack: NestJS + GraphQL (schema-first) + MongoDB (Mongoose) + Redis + Kafka (kafkajs) โครงโฟลเดอร์หลัก | โฟลเดอร์ | ใช้ทำอะไร | | --- | --- | | `src/graphqls//` | `*.graphql`, resolver, service, dto ของ API | | `src/constants/` | `serviceKey.ts`, `kafka/kafkaTopic.ts`, `permissionKey.ts`, `permissions.ts`, `redisKey.ts` | | `src/database/schemas/` | schema ของ MongoDB (collection ขึ้นต้นด้วย `_`) | | `src/kafka/` | producer / consumer wrapper | | `src/kafka-consumer//` | 1 โฟลเดอร์ต่อ 1 topic ที่รับ | | `src/guards/` | ยืนยันตัวตน + ตรวจ UserPolicy | | `scripts/` | `dev-keys.ts` · `gumon-register.ts` | | `test/` | e2e + `docker-compose.e2e.yml` | AI ที่ช่วยเขียนโค้ดอ่านกติกาจาก `AGENTS.md` และ `.github/skills/` ใน repo ได้ทันที ### 2. รันในเครื่อง (ยังไม่ต้องมี core) template มี Kafka + MongoDB (replica set) + Redis ใน Docker และกุญแจสำหรับใช้ในเครื่องให้พร้อม ```bash pnpm install pnpm generate # สร้าง typings จาก *.graphql pnpm test:e2e:up # เปิด Kafka (localhost:19092) · MongoDB (localhost:27099) · Redis (localhost:6399) pnpm dev:keys # สร้างกุญแจ .gumon สำหรับใช้ในเครื่อง pnpm start:dev ``` `.env` สำหรับรันในเครื่อง (ตั้งต้นจาก `.env.example`) ``` DB_URI='mongodb://localhost:27099/dev?directConnection=true' KAFKA_BROKERS='localhost:19092' REDIS_HOST='localhost' REDIS_PORT='6399' GRAPHQL_PLAYGROUND='true' ``` - กุญแจจาก `dev:keys` มีรูปแบบเดียวกับที่ core ออก แต่ **core จริงไม่รู้จัก** — ใช้ในเครื่องและในเทสต์เท่านั้น - `dev:keys` ไม่เขียนทับกุญแจที่มีอยู่ (ใส่ `--force` เมื่อแน่ใจ) - ปิด infra: `pnpm test:e2e:down` (ลบข้อมูลด้วย) **ทดสอบ:** `pnpm test` (unit) · `pnpm test:e2e` (บูต service จริงแล้วคุยผ่าน Kafka เหมือน service อื่น) ### 3. ลงทะเบียน service กับ core (ระดับระบบ) **ด้วยคำสั่ง** — ใช้ได้ทั้ง core ในเครื่องและบน server ```bash cp .env.register.example .env.register # กรอก GUMON_CORE_URL + credential (git ไม่เก็บไฟล์นี้) pnpm gumon:register --dry-run # ดูสิ่งที่จะส่ง (ซ่อนค่าลับ) pnpm gumon:register # เรียก registerService แล้วเขียนกุญแจลง .gumon/certificates// ``` - ยืนยันตัวด้วย credential ชนิด `SYSTEM` (`GUMON_CLIENT_ID` + `GUMON_CLIENT_SECRET`) หรือ access token ของผู้ดูแล (`GUMON_ACCESS_TOKEN`) — ผู้เรียกต้องมีสิทธิ์ `registerService` - service ที่มีหน้า admin ใส่ `GUMON_SERVICE_TYPE`, `GUMON_URL_FRONTEND`, `GUMON_URL_GET_METADATA` เพิ่ม **หรือผ่านหน้าเว็บ** — system admin › Service › register (GraphQL [`registerService`](coreService.md#registerservice)) แล้วนำกุญแจไปวางใน `.gumon/certificates//` (`certificate-id.key`, `certificate`, `certificate.pub`, `hash.key`, `symmetric.key`) ทั้งสองทาง: `serviceKey` = ค่าเดียวกับ `SERVICE_KEY` · `isCoreSet: false` · **กุญแจได้ครั้งเดียว ห้าม commit `.gumon/`** ตอนเริ่มทำงาน service ส่ง `register-service` ประกาศตัวกับ core · ตรวจสถานะได้ที่ system admin › Service-ready ### 4. เพิ่ม service เข้าแอป (ระดับแอป) ที่หน้า system admin › app › app-service › add (หรือ [`addServiceToApp`](coreService.md#addservicetoapp) กับ `refreshData: true`) service จะได้รับ `sync-app-certificate`, `sync-app-credential`, `sync-application`, `sync-user-policy`, `sync-app-service-setting` ของแอปนั้น ⇒ ตรวจ token ของผู้ใช้ในแอปได้ทันที ### 5. ผูก permission กับ role `refresh-data` ทำให้ service ส่ง `sync-permission` ให้ access-control → ผู้ดูแลผูก permission เข้ากับ role → access-control ส่ง `sync-user-policy` กลับมา ⇒ ผู้ใช้ที่มี role นั้นเรียก API ได้ ### 6. เขียนงานของ service เพิ่ม API ([API Reference](#api-reference)) และ topic ([Kafka](#kafka-consume-reference)) · ใช้บริการกลางผ่าน Kafka แทนการทำเอง (ตั้งเวลา → `set-schedule`, แจ้งเตือน → `create-notification`, ไฟล์ → `sync-file-upload`) ### 7. (ถ้ามี) หน้า admin ทำหน้า admin ย่อยจาก `gumon-dynamic-admin-iframe-template` เปิด `GET /api/menuMetaData` แล้วลงทะเบียนพร้อม `urlFrontend`, `urlGetMetaData` ดู [เมนูของหน้า admin](frontends.md) --- ## API Reference API ของ service เป็น GraphQL ที่ `POST /graphql` · หน้าบ้านเรียกได้ตรง · **service อื่นห้ามเรียก** ### Query / Mutation 1. เขียน schema ใน `src/graphqls//.graphql` แล้ว `pnpm generate` 2. เพิ่ม permission ของ operation ใน `src/constants/permissionKey.ts` และรายละเอียดใน `permissions.ts` ```ts // permissionKey.ts export const PERMISSION_KEY = { getOrders: 'getOrders', createOrder: 'createOrder', }; // permissions.ts export const PERMISSIONS: IPermission[] = [ { permissionKey: PERMISSION_KEY.createOrder, title: 'Create order', description: 'สร้างคำสั่งซื้อ', isGenerateApplication: true, // ให้สิทธิ์ทั้งแอปได้ (ผ่าน appRole) isGenerateOrganization: false, // ให้สิทธิ์แยกตามองค์กรได้ (ผ่าน organizationRole) systemNote: '', }, ]; ``` 3. resolver ใช้ `AuthGuard` (ต้อง login) หรือ `AppCredentialGuard` (ยอมรับ `X-APP-CLIENT-ID` สำหรับข้อมูลที่อ่านได้ก่อน login) 4. ใน service ตรวจสิทธิ์ก่อนทำงาน ```ts await this.verifyUserPolicyService.verifyUserPolicy({ appKey, authId, permissionKey: PERMISSION_KEY.createOrder, // organizationId, // ถ้าตรวจระดับองค์กร }); ``` **ระดับแอป หรือ ระดับองค์กร** | ตั้ง | ใช้เมื่อ | ตอนตรวจ | | --- | --- | --- | | `isGenerateApplication: true` | สิทธิ์นี้ใช้ได้ทั้งแอป (เช่น ผู้ดูแลแอปจัดการข้อมูลทุกองค์กร) | `verifyUserPolicy({ appKey, authId, permissionKey })` | | `isGenerateOrganization: true` | สิทธิ์นี้ให้เฉพาะในองค์กร (เช่น พนักงานสาขา A จัดการได้แค่สาขา A) | ใส่ `organizationId` ขององค์กรที่ข้อมูลนั้นอยู่ | ตั้ง `true` ทั้งคู่ได้ ⇒ ผู้ดูแลเลือกผูกได้ทั้งสองระดับ **ข้อมูลของใครของมัน** (เช่น ผู้ป่วยเห็นเฉพาะนัดของตัวเอง) — permission ตอบได้แค่ "ทำ X ได้ไหม" ไม่ได้ตรวจว่าเป็นเจ้าของ ให้ service กรองเองด้วย `authId` จาก token (`ctx.user.authId`) เช่น query `getMyAppointments` คืนเฉพาะรายการที่ `patientAuthId === authId` · ตั้งชื่อ API แบบ `getMy…` ให้รู้ว่าเป็นข้อมูลของคนที่ login query ที่เป็นรายการใช้ input รูปเดียวกับ service อื่น `{ filter, search, sort: { sortBy, sortOrder }, pagination: { page, limit } }` --- ## kafka consume Reference topic ที่ต้องรับแบ่งตามระดับใน [สิ่งที่ Service ต้องมี](serviceX.md) — ทุกตัว: `sync-app-certificate`, `refresh-data` · มี API: `sync-app-credential`, `sync-user-policy` · ตามการใช้งาน: `sync-application`, `sync-organization`, `sync-auth`, `sync-service-setting`, `sync-app-service-setting`, `schedule-alarm` · payload ดูที่ [Kafka topic มาตรฐาน](standardTopics.md) เพิ่ม topic ที่รับ 1. เพิ่มชื่อใน `KAFKA_TOPIC_CONSUMER` (`src/constants/kafka/kafkaTopic.ts`) 2. สร้าง `src/kafka-consumer//.module.ts` + `.service.ts` แล้ว import module ใน `app.module.ts` 3. ใน `onModuleInit` เรียก `await this.kafkaConsumerService.kafkaInitProcess(topic, this.processMessage.bind(this))` · consumer group จะเป็น `-consumer-` · ต้อง `await` เพื่อให้บูตล้มชัด ๆ ถ้าต่อ Kafka ไม่ได้ 4. ใน `processMessage(headers, message)`: - **เฉพาะ** topic ที่ส่งถึง service เดียว → ทิ้งถ้า `headers.serviceKey !== SERVICE_KEY` · topic กระจายทั้งแอป (`sync-application`, `sync-auth`, `sync-` ของ service อื่น ฯลฯ) **ห้ามกรอง** — ดูตารางใน [สิ่งที่ Service ต้องมี](serviceX.md#ฝงรบ-กรองขอความอยางไร) - ทิ้งถ้าไม่มี App Certificate ของตัวเองใน `headers.appKey` (ใช้ `kafkaConsumerHelper.getAppCertificate(appKey, SERVICE_KEY)`) - แยกงานตาม `action` (`ADD`, `REMOVE`, ...) แล้วบันทึกสำเนาลง DB ของตัวเอง โดยห่อด้วย `runInTransaction(connection, async (session) => { ... })` — commit / abort / ปิด session ให้เสมอ - ล้าง Redis key ที่เกี่ยวข้อง **หลัง** transaction commit แล้ว ตัวอย่าง consumer ของข้อมูลที่ service อื่นประกาศ (กระจายทั้งแอป จึงไม่กรอง `serviceKey`) ```ts async processMessage(headers: IKafkaHeaders, message: string) { const { action, appointment } = JSON.parse(message); // JSON ผิดรูป → throw → ระบบลองซ้ำ/dead-letter ให้ const appCertificate = await this.kafkaConsumerHelper.getAppCertificate(headers.appKey, SERVICE_KEY); if (!appCertificate) return null; // เราไม่ได้อยู่ในแอปนี้ await runInTransaction(this.sectionConnection, async (session) => { if (action === 'ADD') { await this.appointmentModel.findByIdAndUpdate(appointment.id, appointment, { upsert: true, session }); } if (action === 'REMOVE') { await this.appointmentModel.findByIdAndDelete(appointment.id, { session }); } }); await deleteRedisKeysWithPrefix({ prefix: `${SERVICE_KEY}:appointment:`, redis: this.redis }); // หลัง commit } ``` **เมื่อประมวลผลไม่สำเร็จ ให้ throw ได้เลย** — `KafkaConsumerService` จัดการให้ | สถานการณ์ | ระบบทำอะไร | | --- | --- | | handler throw (ข้อมูลผิดรูป · DB ล่มชั่วคราว ฯลฯ) | ลองซ้ำแบบเว้นระยะ `KAFKA_MESSAGE_MAX_RETRY` ครั้ง (ค่าเริ่มต้น 3) | | ยังไม่สำเร็จ | ส่งไป `KAFKA_DEAD_LETTER_TOPIC` (ถ้าตั้ง) พร้อม header บอก topic / offset / สาเหตุ แล้วไปข้อความถัดไป — ข้อความเสียข้อความเดียวไม่ทำให้ทั้ง topic ค้าง | | consumer ล่มแบบกู้ไม่ได้ | ปิด process ให้ระบบ (เช่น k8s) เริ่มใหม่ — ไม่ปล่อยให้ service รันต่อโดยไม่รับ topic นั้น | ⛔ อย่า catch แล้วกลืน error เงียบ ๆ — ข้อมูลจะหายโดยไม่มีใครรู้ --- ## Kafka Produce Reference topic ที่ต้องส่ง: `register-service` + `hand-check-result` (ทุกตัว · template ทำให้แล้ว) · `sync-permission` (ถ้ามี API) · `sync-` ของข้อมูลที่ตัวเองเป็นเจ้าของ (ส่งเมื่อเปลี่ยน และส่งซ้ำทั้งชุดเมื่อได้ `refresh-data`) · payload ดูที่ [Kafka topic มาตรฐาน](standardTopics.md) เพิ่ม topic ที่ส่ง: เพิ่มชื่อใน `KAFKA_TOPIC_PRODUCE` แล้วส่งด้วย ```ts await this.kafkaProducerService.produceApp({ topic: KAFKA_TOPIC_PRODUCE.setSchedule, headerAppKey: appKey, headerServiceKey: 'schedule', // service ปลายทาง ไม่ใช่ SERVICE_KEY ของตัวเอง dataArray: [{ action: 'ADD', schedule: { /* ... */ } }], }); ``` **ประกาศข้อมูลของตัวเองให้ service อื่น (`sync-`)** — กระจายทั้งแอป ใส่เฉพาะ `headerAppKey` ```ts // ส่งทุกครั้งที่สร้าง / แก้ / ยกเลิก และส่งซ้ำทั้งชุดเมื่อได้ refresh-data await this.kafkaProducerService.produceApp({ topic: 'sync-appointment', headerAppKey: appKey, // ไม่ใส่ headerServiceKey = ถึงทุก service ในแอป dataArray: [{ action: 'ADD', // ADD = สร้างหรือแก้ (ผู้รับ upsert ตาม id) · REMOVE = ลบออก appointment: { id, appKey, status: 'CANCELLED', patientAuthId, startAt }, }], }); ``` - ข้อมูลที่ยังอยู่แต่เปลี่ยนสถานะ (เช่น ยกเลิกนัด) ส่ง `ADD` พร้อม `status` · ใช้ `REMOVE` เมื่อลบข้อมูลออกจริง - payload ใส่ข้อมูลที่ service อื่นต้องใช้ให้ครบ ผู้รับจะเก็บสำเนาไว้ ไม่ย้อนมาถามเรา - ชื่อ topic: `sync-` (kebab-case) · ประกาศ payload ไว้ใน README ของ service --- ## Checklist - [ ] ตั้ง `SERVICE_KEY` และชื่อ package แล้ว (ไม่ใช่ `CHANGE_ME`) - [ ] `.gumon/certificates//` เป็นกุญแจจาก core (ไม่ใช่กุญแจจาก `dev:keys`) และอยู่ใน `.gitignore` - [ ] ส่ง `register-service` ตอนเริ่ม - [ ] รับ `sync-app-certificate` และ `refresh-data` (ทุกตัว) · รับ `sync-app-credential` และ `sync-user-policy` (ถ้ามี API) - [ ] `refresh-data`: กันทำซ้ำด้วย `refreshDataId`, ส่ง `sync-permission` + ข้อมูลของตัวเองใหม่, ล้าง Redis `:` - [ ] ทุก API ตรวจ permission ผ่าน UserPolicy และทุก permission อยู่ใน `permissions.ts` - [ ] header `serviceKey` = service ปลายทาง ทุกข้อความที่ส่ง - [ ] ไม่เรียก API ของ service อื่นตรง · ไม่ตั้ง cron เอง (ใช้ schedule) - [ ] consumer เขียน DB ผ่าน `runInTransaction` และไม่กลืน error - [ ] บน server: `GRAPHQL_PLAYGROUND` ไม่ตั้งหรือเป็น `false` · `BY_PASS_ACL='false'` · ตั้ง `KAFKA_DEAD_LETTER_TOPIC` - [ ] `pnpm test` และ `pnpm test:e2e` ผ่าน --- > อัปเดตจากโค้ด gumon-backend-template@f1ae0e8 · 2026-10-06 --- # Gumon CLI `gumon` เป็นเครื่องมือบรรทัดคำสั่งสำหรับ **ตั้งและรันระบบ Gumon บนเครื่องนักพัฒนา** ด้วยคำสั่งเดียว — ขึ้นฐานข้อมูล ตั้งระบบครั้งแรก (init) และขึ้น core set ให้พร้อมใช้ โดย **ไม่ต้อง init ใหม่ทุกครั้งที่เปิดเครื่อง** CLI ตัวนี้เขียนใหม่ทั้งหมดในปี 2026 (TypeScript บน Node) มาแทน CLI รุ่นเดิมที่มีแค่ `gumon init` และแทนชุด docker compose ที่เคยแยกเป็นสองรีโป !!! note "สถานะ" ใช้ได้แล้ว 11 คำสั่ง — ดูตาราง "คำสั่งทั้งหมด" ข้างล่าง · `gumon --help` บอกสถานะล่าสุดของทุกคำสั่งเสมอ (ตัวที่มี `·` นำหน้า = ยังไม่ทำ) --- ## สิ่งที่ต้องมี - Docker พร้อม `docker compose` รุ่น 2 - Node.js 20 ขึ้นไป และ `pnpm` - สิทธิ์ดึง image ของ core set จาก registry ของทีม (login ด้วย `docker login` ไว้ก่อน) - RAM ว่างราว 4 GB สำหรับทั้งชุด (ฐานข้อมูล + core set 9 service + หน้า system admin) ## ติดตั้ง source อยู่ในรีโป `gumon-cli` ```bash git clone <รีโป gumon-cli> cd gumon-cli pnpm install pnpm run check # build + ทดสอบ node dist/cli.js --help # หรือ pnpm link --global แล้วเรียก gumon ได้ทุกที่ ``` ตัวอย่างในหน้านี้เขียนเป็น `gumon …` — ถ้ายังไม่ได้ link ให้แทนด้วย `node <ที่อยู่ gumon-cli>/dist/cli.js …` --- ## ใช้งานประจำวัน ```bash mkdir my-gumon && cd my-gumon # โฟลเดอร์ทำงาน — สถานะของ stack เก็บที่นี่ gumon up # ขึ้นทั้งชุด gumon status # ดูว่าอะไรพร้อมแล้ว gumon down # หยุด (ข้อมูลยังอยู่) ``` สั่ง `gumon up` จากโฟลเดอร์เดิมทุกครั้ง — ทั้งเครื่องมี stack ได้ชุดเดียว ### up `gumon up` ทำตามลำดับ และหยุดพร้อมบอกเหตุถ้าขั้นใดไม่ผ่าน 1. ขึ้นฐานข้อมูล: MongoDB (replica set), Kafka, Redis และที่เก็บไฟล์แบบ S3 — แล้วรอจนทุกตัวพร้อม 2. ตรวจว่า **ระบบนี้เคย init แล้วหรือยัง** (ดูทั้งไฟล์กุญแจของ core และข้อมูลในฐาน ไม่เชื่อฝั่งเดียว) - ยังไม่เคย → ให้ Core Service รัน init-system หนึ่งครั้ง (ดู [วงจรของ Service](serviceLifecycle.md)) - เคยแล้ว → ข้ามขั้นนี้ 3. ขึ้น core set ทุกตัวและหน้า system admin แล้วรอจนตอบได้ ครั้งแรกจากศูนย์ใช้เวลาราว 2–3 นาที (ไม่รวมเวลาดึง image) · ครั้งถัดไปไม่ถึงนาที เพราะไม่ init ซ้ำ !!! warning "ยังต้องทำเองหลัง `up` ครั้งแรก" `up` ยังไม่สั่ง refresh-data ให้ — permission ของแต่ละ service จะยังไม่ถึง Access Control จนกว่าจะสั่ง refresh-data ของแต่ละ service จากหน้า system admin (เมนู refresh data) หรือ GraphQL ของ [Core Service](coreService.md) · คำสั่ง `gumon refresh-all` ที่จะทำขั้นนี้ให้อยู่ในแผน ### status แสดงสถานะของฐานข้อมูลแต่ละตัว, ระบบ init แล้วหรือยัง และ service ใดตอบได้แล้ว ### down หยุดและลบ container ทั้งชุด **โดยเก็บข้อมูลไว้ครบ** (ฐานข้อมูล, ข้อความใน Kafka, ไฟล์, กุญแจ) — `gumon up` รอบถัดไปกลับมาที่สถานะเดิม ### ตัวเลือกรวม | ตัวเลือก | คำอธิบาย | | --- | --- | | `--help` | รายการคำสั่งพร้อมสถานะ | | `--version` | รุ่นของ CLI | | `--json` | ผลลัพธ์เป็น JSON สำหรับสคริปต์และ AI | --- ## โฟลเดอร์ `.gumon-stack/` CLI สร้างโฟลเดอร์นี้ในโฟลเดอร์ทำงานตอน `up` ครั้งแรก | ที่ | คำอธิบาย | | --- | --- | | `.gumon/login.txt` | ข้อมูลเข้าระบบของผู้ดูแลระบบคนแรก — ใช้เข้าหน้า system admin · เก็บเป็นความลับ | | `.gumon/certificates//` | กุญแจของแต่ละ service ที่ Core Service ออกให้ตอน init | | `infra.env` | รหัสผ่านของฐานข้อมูลที่ CLI สุ่มให้ครั้งแรก | ห้าม commit โฟลเดอร์นี้ลง git และห้ามส่งต่อให้ผู้อื่น — ลบโฟลเดอร์นี้โดยไม่ล้างฐานข้อมูลจะทำให้กุญแจกับข้อมูลไม่ตรงกัน --- ## คำสั่งทั้งหมด | คำสั่ง | ทำอะไร | สถานะ | | --- | --- | --- | | `gumon up` | ขึ้นฐานข้อมูล → init เฉพาะถ้ายังไม่เคย → ขึ้น core set · `--only a,b` ขึ้นเฉพาะที่ระบุ | ใช้ได้ | | `gumon status` | สถานะของทั้งชุด และ RAM ที่แต่ละตัวใช้ | ใช้ได้ | | `gumon down` | หยุด เก็บข้อมูลครบ | ใช้ได้ | | `gumon snapshot [ชื่อ]` | เก็บสถานะปัจจุบันทั้งชุดเป็นไฟล์เดียว | ใช้ได้ | | `gumon restore <ชื่อ>` | กลับสู่สถานะที่เก็บไว้โดยไม่ต้อง init ใหม่ (ต้องยืนยัน) | ใช้ได้ | | `gumon reset` | ล้างทั้งหมดเพื่อเริ่มจากศูนย์ (ต้องยืนยัน) | ใช้ได้ | | `gumon dev [path]` | สลับ service ตัวเดียวไปรันจากโค้ดในเครื่อง ตัวอื่นยังรันจาก image · `--stop` สลับกลับ | ใช้ได้ | | `gumon enable` / `disable ` | เปิด/ปิด service ที่งานนี้ไม่ใช้ เพื่อประหยัด RAM (จำค่า) | ใช้ได้ | | `gumon update [service…]` | ดึง image รุ่นใหม่แล้วสร้าง container ใหม่ · `up` บอกให้เองว่าตัวใดล้าหลัง | ใช้ได้ | | `gumon service register ` | ลงทะเบียน service ใหม่กับ Core Service วางกุญแจ และผูกเข้าแอประบบ | ใช้ได้ | | `gumon refresh-all` | สั่ง refresh-data ทุกแอปและทุก service | อยู่ในแผน | | `gumon logs` · `gumon doctor` | ดู log · ตรวจ docker, พอร์ต และ env ที่ขาด | อยู่ในแผน | | `gumon app create` | ขึ้นแอปใหม่ครบขั้นในคำสั่งเดียว | อยู่ในแผน | งานที่ยังไม่มีคำสั่ง เช่น เพิ่ม service เข้าแอปอื่นหรือถอด service ออก ทำผ่านหน้า system admin หรือ GraphQL ของ [Core Service](coreService.md) (`registerService`, `addServiceToApp`, `removeServiceFromApp`, `resignService`) ดูขั้นตอนที่ [สร้าง Service ใหม่](createService.md) --- ## เมื่อมีปัญหา | อาการ | ทำอย่างไร | | --- | --- | | `up` หยุดที่ขั้นฐานข้อมูล | ดู `gumon status` ว่าตัวใดไม่พร้อม · ตรวจว่าไม่มีโปรแกรมอื่นใช้พอร์ตของ MongoDB, Kafka หรือ Redis อยู่ | | ดึง image ไม่ได้ | ตรวจ `docker login` กับ registry ของทีม | | `up` แจ้งว่า init ค้างครึ่งทาง (มีกุญแจแต่ฐานว่าง หรือกลับกัน) | CLI จะไม่ init ทับให้เอง — ล้างแล้วเริ่มใหม่ด้วย `gumon reset` (ข้อมูลใน stack หายทั้งหมด · เก็บ `gumon snapshot` ไว้ก่อนถ้ายังต้องใช้) | | เข้าหน้า system admin ไม่ได้ | ใช้ข้อมูลใน `.gumon-stack/.gumon/login.txt` | --- > อัปเดตจากโค้ด gumon-cli@bc19acf · 2026-10-10 --- # Core Service Core Service คือทะเบียนกลางของ service ทั้งระบบและเป็นผู้ออกกุญแจ ทำหน้าที่ - ลงทะเบียน / ถอด service ออกจากระบบ (ระดับระบบ) และเพิ่ม / ถอด service ออกจากแอป (ระดับแอป) - ออก **System Certificate** (กุญแจระดับ service) และ **App Certificate** (กุญแจระดับ service × แอป) - เก็บค่าตั้งค่าของ service ทั้งระบบ (service setting) และค่าตั้งค่าต่อแอป (app service setting) - สั่ง `refresh-data` ให้ service ส่งข้อมูลที่ตัวเองถืออยู่ขึ้น Kafka ใหม่ - ตรวจว่า service ปลายทางถือกุญแจถูกต้อง (hand-check) - ตั้งระบบจากศูนย์ (`init-system`) ครั้งแรก serviceKey: `core` · เป็นส่วนหนึ่งของ core set - [API Reference](#api-reference) - [kafka consume Reference](#kafka-consume-reference) - [Kafka Produce Reference](#kafka-produce-reference) - ดูเพิ่ม: [สิ่งที่ Service ต้องมี](serviceX.md) · [สร้าง Service ใหม่](createService.md) --- ## แนวคิดสำคัญ ### core set service ที่ระบบต้องมีเพื่อให้ทำงานได้โดยยังไม่มีธุรกิจเกี่ยว (`isCoreSet: true`): `core` · `authentication` · `application` · `access-control` · `unit` · `profile` · `notification` · `schedule` · `storage` service ธุรกิจที่ลงทะเบียนภายหลังเป็น `isCoreSet: false` (ค่า default ของ `registerService`) ### ถอดเข้าถอดออก 2 ระดับ | ระดับ | เข้า | ออก | ผล | | --- | --- | --- | --- | | ระบบ | `registerService` | `resignService` | register ออก System Certificate ใหม่ให้ service · resign ลบข้อมูลของ service นั้น ไม่ส่งข้อมูลให้อีก ถ้าจะกลับมาต้อง register ใหม่ด้วยกุญแจใหม่ | | แอป | `addServiceToApp` | `removeServiceFromApp` | จัดการ App Certificate ของ service ในแอปนั้น · ไม่มี App Certificate ในแอปไหน = รับข้อมูลของแอปนั้นไม่ได้ ใช้ข้ามแอปไม่ได้ | แอปที่สร้างใหม่ (ผ่าน `sync-application`) จะถูกผูก service core set ทุกตัวเข้าให้อัตโนมัติ ### กุญแจ - **System Certificate** — ออกตอน `registerService` · service เก็บไว้ใน `.gumon/certificates//` ของตัวเอง (ห้าม commit) · ใช้ถอดข้อมูลที่ core ส่งถึง service นั้นโดยเฉพาะ (hand-check, App Certificate) - **App Certificate** — คู่กุญแจ RSA ต่อ (appKey, serviceKey) · core ส่งผ่าน `sync-app-certificate` โดย privateKey ถูกเข้ารหัสด้วย System Certificate ของ service เจ้าของ ⇒ มีแต่ service นั้นถอดได้ · ทุก service ใน app เก็บ publicKey ของกันและกัน ⇒ ตารางนี้ตารางเดียวบอกได้ว่า service ไหนมีสิทธิ์ในแอปไหน --- ## API Reference endpoint: `POST /graphql` ทุก operation ต้องส่ง header `Authorization: Bearer ` และผู้เรียกต้องมี permission ชื่อเดียวกับ operation (serviceKey `core`) เว้นแต่ระบุไว้เป็นอย่างอื่น query ที่เป็นรายการรับ input รูปเดียวกัน `{ filter, search, sort: { sortBy, sortOrder }, pagination }` และคืน `{ <รายการ>[], pagination }` --- ### Query --- #### getServices ดึงรายการ service ทั้งหมดในระบบ ```graphql query { getServices(getServicesInput: { filter: { isCoreSet: false }, pagination: { page: 1, limit: 20 } }) { services { _id serviceKey name description version author isCoreSet isActive type urlFrontend urlGetMetaData createdAt } pagination { totalItems page limit } } } ``` | field ของ Service | Type | คำอธิบาย | | --- | --- | --- | | serviceKey | String! | key ของ service (ไม่ซ้ำ) | | name / description / version / author | String | ข้อมูลแสดงผล | | isCoreSet | Boolean | เป็น core set หรือไม่ | | isActive | Boolean | สถานะใช้งาน | | type | `BACKEND` \| `MAIN_FRONTEND` \| `MICRO_FRONTEND` \| `OTHER` | ชนิดของ service | | urlFrontend | String | URL หน้าบ้าน (กรณีเป็นหน้า admin ย่อย) | | urlGetMetaData | String | URL ที่คืนเมนู (`CustomMenu[]`) ของหน้า admin ย่อย ให้ access-control ดึงไปสร้างเมนู | permission: `getServices` --- #### getServiceById ```graphql query { getServiceById(serviceId: "") { _id serviceKey name type } } ``` permission: `getServiceById` --- #### getServiceByServiceKey ```graphql query { getServiceByServiceKey(serviceKey: "storage") { _id serviceKey name isCoreSet } } ``` permission: `getServiceByServiceKey` --- #### getServicesInApp รายการ service ที่อยู่ในแอป (`AppService`: `appKey`, `serviceKey`) ```graphql query { getServicesInApp(getInput: { filter: { appKey: "my-app" } }) { appServices { _id appKey serviceKey createdAt } pagination { totalItems } } } ``` permission: `getServicesInApp` --- #### getMyServicesInApp รายการ service ในแอปของผู้ที่ login อยู่ (input เหมือน `getServicesInApp`) · ต้อง login --- #### getServiceInAppById ```graphql query { getServiceInAppById(id: "") { appKey serviceKey } } ``` permission: `getServiceInAppById` --- #### getSystemCertificates / getSystemCertificateById ดูรายการ System Certificate ของแต่ละ service (`serviceId`, `serviceKey`, `createdAt`) ```graphql query { getSystemCertificates(getSystemCertificatesInput: { filter: { serviceKey: "storage" } }) { systemCertificates { _id serviceId serviceKey createdAt } } } ``` permission: `getSystemCertificates` · `getSystemCertificateById` --- #### getAppCertificates / getAppCertificateById ดู App Certificate (`appKey`, `serviceKey`, `publicKey`) ```graphql query { getAppCertificates(input: { filter: { appKey: "my-app" } }) { appCertificates { _id appKey serviceKey publicKey } } } ``` permission: `getAppCertificates` · `getAppCertificateById` --- #### getServiceSettings / getServiceSettingByServiceKey ค่าตั้งค่าทั้งระบบของ service (`setting`: JSON) — ใช้ตั้งค่าเพิ่มเติมผ่าน core แทนการแก้ env ```graphql query { getServiceSettingByServiceKey(serviceKey: "notification") { serviceKey setting } } ``` permission: `getServiceSettings` · `getServiceSettingByServiceKey` --- #### getAppServiceSettings / getAppServiceSetting / getAppServiceSettingById ค่าตั้งค่าเฉพาะแอปของ service (`appKey`, `serviceKey`, `setting`: JSON) ```graphql query { getAppServiceSetting(serviceKey: "authentication", appKey: "my-app") { appKey serviceKey setting } } ``` permission: `getAppServiceSettings` · `getAppServiceSetting` · `getAppServiceSettingById` --- #### getServiceHandChecks / getServiceHandCheckById / getServiceHandCheckByHandCheckRefId ประวัติการ hand-check (`handCheckRefId`, `systemCertificateId`, `serviceKey`, `start`, `end`, `isSuccess`) ```graphql query { getServiceHandChecks(input: { filter: { serviceKey: "storage" } }) { serviceHandChecks { handCheckRefId serviceKey start end isSuccess } } } ``` permission: `getServiceHandChecks` · `getServiceHandCheckById` · `getServiceHandCheckByHandCheckRefId` --- #### getServiceReady / getServiceReadyById สถานะความพร้อมของ service (`serviceKey`, `isHandCheck`, `lastHandCheckAt`) ```graphql query { getServiceReady(input: {}) { serviceReady { serviceKey isHandCheck lastHandCheckAt } } } ``` permission: `getServiceReady` · `getServiceReadyById` --- ### Mutation --- #### registerService ลงทะเบียน service ใหม่เข้าระบบ (ระดับระบบ) และออก System Certificate ให้ ```graphql mutation { registerService(registerServiceInput: { name: "My Service" serviceKey: "my-service" description: "..." version: "1.0.0" type: BACKEND urlFrontend: null urlGetMetaData: null }) { service { _id serviceKey isCoreSet } systemCertificateId publicKey privateKey hashKey symmetricKey } } ``` | input | Type | คำอธิบาย | | --- | --- | --- | | name | String! | ชื่อ service | | serviceKey | String! | key ของ service ห้ามซ้ำ | | description / version / author | String | | | isCoreSet | Boolean = false | service ธุรกิจใช้ false | | isActive | Boolean = true | | | type | EnumServiceType = BACKEND | | | urlFrontend / urlGetMetaData | String | สำหรับหน้า admin ย่อย | ผลลัพธ์มีกุญแจที่ service ต้องเก็บไว้ใน `.gumon/certificates//` ของตัวเอง (ได้ครั้งเดียว เก็บให้ดี ห้าม commit): | field | ไฟล์ | | --- | --- | | systemCertificateId | `certificate-id.key` | | privateKey | `certificate` | | publicKey | `certificate.pub` | | hashKey | `hash.key` | | symmetricKey | `symmetric.key` | หลังลงทะเบียน core จะส่ง `sync-service` และ `sync-service-setting` ออกไป permission: `registerService` --- #### updateService ```graphql mutation { updateService(serviceId: "", update: { name: "New name", urlGetMetaData: "https://.../api/menuMetaData" }) { _id name } } ``` permission: `updateService` --- #### resignService ถอด service ออกจากระบบ · ต้องถอดออกจากทุกแอปก่อน (`removeServiceFromApp`) และถอด service ที่เป็น core set ไม่ได้ ```graphql mutation { resignService(serviceKey: "my-service") { serviceKey } } ``` permission: `resignService` --- #### addServiceToApp เพิ่ม service เข้าแอป (ระดับแอป) · สร้าง App Certificate (ส่ง `sync-app-certificate`) และ app service setting (คัดลอกจาก service setting ส่ง `sync-app-service-setting`) · ถ้า `refreshData: true` จะส่ง `refresh-data` ให้ทุก service ในแอป เพื่อให้ service ใหม่ได้ข้อมูลเดิมครบ ```graphql mutation { addServiceToApp(input: { appKey: "my-app", serviceKeys: ["my-service"], refreshData: true }) { appKey serviceKey } } ``` permission: `addServiceToApp` --- #### removeServiceFromApp ถอด service ออกจากแอป · ลบ App Certificate (ส่ง `sync-app-certificate` REMOVE) และ app service setting ```graphql mutation { removeServiceFromApp(input: { appKey: "my-app", serviceKeys: ["my-service"] }) { appKey serviceKey } } ``` permission: `removeServiceFromApp` --- #### refreshData สั่งให้ service ในแอปส่งข้อมูลที่ตัวเองถือขึ้น Kafka ใหม่ (`type: ALL` ทุก service · `SELECT` เฉพาะที่ระบุใน `serviceKeys`) ```graphql mutation { refreshData(input: { appKey: "my-app", type: SELECT, serviceKeys: ["access-control"] }) { serviceKey } } ``` permission: `refreshData` --- #### generateSystemCertificate / resetSystemCertificate / revokedSystemCertificate ออกใหม่ / รีเซ็ต / เพิกถอน System Certificate ของ service · `generate` และ `reset` คืน `publicKey`, `privateKey`, `systemCertificateId` ให้นำไปแทนไฟล์ใน `.gumon` ของ service ```graphql mutation { resetSystemCertificate(resetSystemCertificateInput: { serviceKey: "my-service", systemCertificateId: "" }) { systemCertificateId publicKey privateKey } } ``` permission: `generateSystemCertificate` · `resetSystemCertificate` · `revokedSystemCertificate` --- #### resetAppCertificate / resetAppCertificateByServiceKey / revokeAppCertificateByServiceKey ออก App Certificate ใหม่ทั้งแอป / เฉพาะ service / เพิกถอนของ service ในแอป (1 แอป × 1 service มี App Certificate เดียว) · ผลถูกส่งออกทาง `sync-app-certificate` ```graphql mutation { resetAppCertificateByServiceKey(appKey: "my-app", serviceKey: "my-service") { appKey serviceKey publicKey } } ``` permission: `resetAppCertificate` · `resetAppCertificateByServiceKey` · `revokeAppCertificateByServiceKey` --- #### updateServiceSetting / resetServiceSetting แก้ / คืนค่าเริ่มต้นของ service setting แล้วส่ง `sync-service-setting` ```graphql mutation { updateServiceSetting(serviceKey: "notification", update: { setting: { exampleKey: "value" } }) { setting } } ``` permission: `updateServiceSetting` · `resetServiceSetting` --- #### updateAppServiceSetting / resetAppServiceSetting แก้ / คืนค่าเริ่มต้นของ app service setting แล้วส่ง `sync-app-service-setting` ```graphql mutation { updateAppServiceSetting(appKey: "my-app", serviceKey: "authentication", update: { setting: { exampleKey: "value" } }) { setting } } ``` permission: `updateAppServiceSetting` · `resetAppServiceSetting` --- #### handCheckService สั่ง hand-check ใหม่ (`type: ALL` ทุก service · `SELECT` เฉพาะ `serviceKeys`) · core ส่ง `hand-check` ให้ service ปลายทาง ```graphql mutation { handCheckService(input: { type: SELECT, serviceKeys: ["my-service"] }) { handCheckRefId serviceKey start } } ``` permission: `handCheckService` --- ## kafka consume Reference ทุกข้อความมี header `appKey` (ถ้าเกี่ยวกับแอป) และ `serviceKey` = service **ปลายทาง** · payload รูป `{ action, : {...} }` --- ### register-service service ประกาศตัวกับ core ตอนเริ่มทำงาน → core เริ่ม hand-check (ส่ง `hand-check`) topic: register-service header: serviceKey = core Action: ADD | key ใน `registerService` | Type | คำอธิบาย | | --- | --- | --- | | systemCertificateId | string | id ของ System Certificate (จากไฟล์ `certificate-id.key`) | | serviceKey | string | key ของ service ผู้ประกาศ | | encryptData | string | systemCertificateId ที่เข้ารหัสด้วย `certificate.pub` | --- ### hand-check-result คำตอบของ `hand-check` · core ถือว่าผ่านเมื่อ `resultData` ตรงกับ `handCheckRefId` แล้วอัปเดตสถานะ service ready topic: hand-check-result header: serviceKey = core | key ใน `serviceHandCheckResult` | Type | คำอธิบาย | | --- | --- | --- | | handCheckRefId | string | id อ้างอิงที่ core สร้าง | | systemCertificateId | string | | | serviceKey | string | service ผู้ตอบ | | encryptData | string | ข้อมูลเข้ารหัสที่ได้รับจาก core | | resultData | string | ผลถอดรหัส (ถอดไม่ได้ให้ส่ง `""`) | --- ### refresh-data เมื่อปลายทางเป็น `core`: ส่ง `sync-app-certificate`, `sync-service-setting`, `sync-app-service-setting`, `sync-service` และ `sync-permission` ของแอปนั้นออกไปใหม่ แล้วล้าง cache ของตัวเอง topic: refresh-data header: appKey, serviceKey = core payload ดูที่ [refresh-data](#refresh-data_1) ในหัวข้อ produce --- ### sync-application รับข้อมูลแอปจาก application service · ถ้าเป็นแอปใหม่ที่ active core จะผูก service core set ทุกตัวเข้าแอปให้อัตโนมัติ (สร้าง App Certificate + app service setting) แล้วส่ง `add-admin-app-role` topic: sync-application header: appKey Action: ADD | REMOVE | key ใน `application` | Type | คำอธิบาย | | --- | --- | --- | | id | string | | | appKey | string | | | appName | string | | | isActive | boolean | | | isSystem | boolean | แอป SYSTEM | --- ### sync-app-credential รับข้อมูลการเข้าใช้ของแอป (clientId, กุญแจตรวจ JWT, กฎของ token) จาก authentication service · ค่าลับถูกเข้ารหัสด้วย App Certificate ของปลายทาง · credential ชนิด `SYSTEM` (clientId + clientSecret) คือ apiKey สำหรับระบบภายนอก topic: sync-app-credential header: appKey, serviceKey = core Action: ADD | REMOVE field ดูที่ [สิ่งที่ Service ต้องมี › sync-app-credential](serviceX.md) --- ### sync-user-policy รับ UserPolicy พร้อมใช้จาก access-control เพื่อใช้ตรวจสิทธิ์ของ API ใน core topic: sync-user-policy header: appKey, serviceKey = core Action: ADD | REMOVE | REMOVE_APP | REMOVE_PERMISSION | REMOVE_USER | REMOVE_ORGANIZATION field ดูที่ [สิ่งที่ Service ต้องมี › sync-user-policy](serviceX.md) --- ## Kafka Produce Reference --- ### init-system ส่งครั้งเดียวตอนตั้งระบบจากศูนย์ (`GUMON_INIT_SYSTEM=true`) · **เฉพาะ core set** · ให้ core set ตัวอื่นสร้างข้อมูลตั้งต้นของแอป SYSTEM (แอป, App Certificate, app credential, ผู้ดูแลระบบ, app role) topic: init-system --- ### refresh-data สั่งให้ service ปลายทางส่งข้อมูลที่ตัวเองถือขึ้นไปใหม่ (ให้ service ใหม่ในแอปทำงานต่อได้) และล้าง cache Redis ของตัวเอง · ส่งจาก `refreshData` หรือ `addServiceToApp(refreshData: true)` topic: refresh-data header: appKey, serviceKey = service ปลายทาง Action: ADD | key ใน `refreshData` | Type | คำอธิบาย | | --- | --- | --- | | refreshDataId | string | id ของรอบ refresh (ใช้กันทำซ้ำ) | | appKey | string | | | serviceKey | string | service ปลายทาง | | note | string | | --- ### hand-check ถามว่า service ปลายทางยังถือกุญแจถูกต้องไหม · ส่งเมื่อได้รับ `register-service` หรือสั่ง `handCheckService` · ปลายทางถอด `encryptData` ด้วยไฟล์ `certificate` แล้วตอบ `hand-check-result` topic: hand-check header: serviceKey = service ปลายทาง Action: ADD | key ใน `handCheck` | Type | คำอธิบาย | | --- | --- | --- | | handCheckRefId | string | id อ้างอิงที่ core สุ่ม | | systemCertificateId | string | | | serviceKey | string | | | encryptData | string | handCheckRefId ที่เข้ารหัสด้วย System Certificate ของปลายทาง | --- ### sync-app-certificate แจก App Certificate ของ service ในแอป · ส่งเมื่อ add / remove service ในแอป, reset / revoke app certificate, แอปใหม่ และ refresh-data topic: sync-app-certificate header: appKey, serviceKey = service ปลายทาง Action: ADD | REMOVE | key ใน `appCertificate` | Type | คำอธิบาย | | --- | --- | --- | | id | string | | | appKey | string | | | serviceKey | string | service เจ้าของ certificate | | publicKey | string | ใช้เข้ารหัสข้อมูลที่ส่งถึง service เจ้าของในแอปนี้ | | privateKeyRsaEncrypt | string | privateKey ที่ถูกเข้ารหัส (ADD เท่านั้น) | | symmetricKeyEncrypt | string | กุญแจถอด `privateKeyRsaEncrypt` เข้ารหัสด้วย System Certificate ของเจ้าของ (ADD เท่านั้น) | --- ### sync-service ทะเบียน service · access-control ใช้ `urlGetMetaData` ดึงเมนูของหน้า admin ย่อย topic: sync-service header: serviceKey = service ปลายทาง Action: ADD | key ใน `service` | Type | | --- | --- | | id, serviceKey, name, description, version, author | string | | isCoreSet, isActive | boolean | | type | `BACKEND` \| `MAIN_FRONTEND` \| `MICRO_FRONTEND` \| `OTHER` | | urlFrontend, urlGetMetaData | string | --- ### sync-service-setting JSON ตั้งค่าทั้งระบบของ service topic: sync-service-setting header: serviceKey = service ปลายทาง Action: ADD | REMOVE | key ใน `serviceSetting` | Type | | --- | --- | | id | string | | serviceKey | string | | setting | JSON | --- ### sync-app-service-setting JSON ตั้งค่าเฉพาะแอปของ service topic: sync-app-service-setting header: appKey, serviceKey = service ปลายทาง Action: ADD | REMOVE | key ใน `appServiceSetting` | Type | | --- | --- | | id | string | | appKey | string | | serviceKey | string | | setting | JSON | --- ### sync-permission permission ของ API ใน core ส่งให้ access-control เพื่อผูกกับ role (ตอบ refresh-data) topic: sync-permission header: appKey, serviceKey = access-control Action: ADD payload ดูที่ [สิ่งที่ Service ต้องมี › sync-permission](serviceX.md) --- ### add-admin-app-role แอปใหม่ถูกสร้าง ⇒ ให้สร้าง app role ผู้ดูแล (`admin`) ของแอปนั้น topic: add-admin-app-role header: appKey, serviceKey = unit Action: ADD | key ใน `adminAppRole` | Type | | --- | --- | | appKey | string | --- > อัปเดตจากโค้ด gumon-core-service@d872cc3 · 2026-10-05 --- # Application Service Application Service เป็นเจ้าของข้อมูลแอป (Application) และ theme ของระบบ - **Application** — แอปในระบบ (appKey, ชื่อ, รูป, สถานะ) · การสร้างแอปที่นี่จะส่ง `sync-application` ให้ core ผูก service core set ทุกตัวเข้าแอปใหม่โดยอัตโนมัติ - **Theme** — theme กลางของทั้งระบบ (ต้นไม้ parent/child ผ่าน `themeKeyPath`) - **AppTheme** — theme ที่แอปเลือกใช้ - **CredentialAppTheme** — theme ต่อ app credential (clientId) - **HostTheme** — ผูก hostName → แอป + app credential + theme ให้หน้าบ้านรู้ว่าโดเมนนี้คือแอปไหน ใช้ clientId ไหน และใช้ theme อะไร serviceKey: `application` · เป็นส่วนหนึ่งของ core set - [API Reference](#api-reference) - [kafka consume Reference](#kafka-consume-reference) - [Kafka Produce Reference](#kafka-produce-reference) - ดูเพิ่ม: [Core Service](coreService.md) · [สิ่งที่ Service ต้องมี](serviceX.md) --- ## API Reference endpoint: `POST /graphql` การยืนยันตัวตนที่ใช้ในหน้านี้ | ชนิด | header | ใช้กับ | | --- | --- | --- | | login | `Authorization: Bearer ` + permission ตามที่ระบุ | mutation ทั้งหมด และ query ที่ระบุ permission | | app credential | `X-APP-CLIENT-ID: ` (หรือ `Authorization`) | query อ่านข้อมูลแอป / theme ก่อน login | | ไม่ต้องยืนยัน | – | ค้นหา HostTheme เพื่อ resolve โดเมน | query ที่เป็นรายการรับ input รูป `{ filter, search, sort: { sortBy, sortOrder }, pagination: { page, limit } }` และคืน `{ <รายการ>[], pagination }` --- ### Query --- #### getApplications ```graphql query { getApplications(getAppInput: { filter: { isActive: true }, pagination: { page: 1, limit: 20 } }) { applications { _id appKey appName iconImageKey logoImageKey backgroundImageKey isActive isSystem createdAt } pagination { totalItems page limit } } } ``` | field ของ Application | Type | คำอธิบาย | | --- | --- | --- | | appKey | String | key ของแอป (ไม่ซ้ำ) | | appName | String | ชื่อแอป | | iconImageKey / logoImageKey / backgroundImageKey | String | key ไฟล์รูปใน storage | | isActive | Boolean | | | isSystem | Boolean | แอป SYSTEM (สร้างตอนตั้งระบบ) | ยืนยันตัวตน: app credential --- #### getApplicationById / getApplicationByAppKey ```graphql query { getApplicationByAppKey(appKey: "my-app") { _id appKey appName isActive } } ``` ยืนยันตัวตน: app credential --- #### getThemes / getThemeById / getThemeByThemeKey ```graphql query { getThemes(getThemeInput: { filter: { isDefault: true } }) { themes { _id themeKey themeKeyPath themeName isDefault variable calVariable description } } } ``` | field ของ Theme | Type | คำอธิบาย | | --- | --- | --- | | themeKey | String | key ของ theme | | themeKeyPath | String | เส้นทาง parent ของ theme | | themeName | String | | | isDefault | Boolean | | | variable | JSON | ค่าที่ theme นี้กำหนดเอง | | calVariable | JSON | ค่าที่คำนวณรวมกับ theme แม่แล้ว (หน้าบ้านใช้ค่านี้) | ยืนยันตัวตน: app credential --- #### getAppThemes ```graphql query { getAppThemes(getAppThemeInput: { filter: { appKey: "my-app" } }) { appThemes { _id appKey themeKey isDefault theme { themeName } } } } ``` permission: `getAppThemes` --- #### getCredentialAppThemes / getCredentialAppThemeById theme ต่อ app credential (`appKey`, `credentialId`, `themeKey`, `isDefault`, `theme`) ```graphql query { getCredentialAppThemes(getCredentialAppThemeInput: { filter: { appKey: "my-app" } }) { credentialAppThemes { credentialId themeKey isDefault } } } ``` ยืนยันตัวตน: app credential --- #### getConfigByHost ค่าตั้งต้นของหน้าบ้านตาม host: credentialId, theme และ app service setting ของแอป ```graphql query { getConfigByHost(getConfigByHostInput: { host: "admin.example.com" }) { credentialId theme { themeKey calVariable } appServiceSetting { serviceKey setting } } } ``` ยืนยันตัวตน: app credential --- #### getHostThemes ```graphql query { getHostThemes(getHostThemeInput: { filter: { appKey: "my-app" } }) { hostThemes { _id hostThemeKey hostName appKey clientId themeKey isDefault isActive } } } ``` permission: `getHostThemes` --- #### getHostThemeByHostName / getHostThemeById / getHostThemeByKey หน้าบ้านเรียกตอนเปิดเว็บเพื่อรู้ว่าโดเมนนี้คือแอปไหน ใช้ clientId ไหน และ theme อะไร (เรียกได้ก่อน login) ```graphql query { getHostThemeByHostName(hostName: "admin.example.com") { appKey clientId hostThemeKey application { appKey appName } appCredential { clientId appCredentialType } theme { themeKey calVariable } } } ``` | field ของ HostTheme | Type | คำอธิบาย | | --- | --- | --- | | hostThemeKey | String | key ของ host theme | | hostName | String | โดเมน | | appKey / applicationId / application | | แอปที่ผูก | | appCredentialId / clientId / appCredential | | app credential ที่หน้าบ้านต้องใช้ | | themeId / themeKey / themeKeyPath / theme | | theme ที่ใช้ | | isDefault / isActive | Boolean | | --- ### Mutation --- #### createApplication สร้างแอปใหม่ แล้วส่ง `sync-application` (ADD) ⇒ core ผูก service core set ทุกตัวเข้าแอป และสร้าง app role ผู้ดูแลให้อัตโนมัติ ```graphql mutation { createApplication(createApplicationInput: { appKey: "my-app", appName: "My App", isActive: true }) { _id appKey appName } } ``` | input | Type | คำอธิบาย | | --- | --- | --- | | appKey | String! | ห้ามซ้ำ | | appName | String! | | | iconImageKey / logoImageKey / backgroundImageKey | String | | | isActive | Boolean = true | | permission: `createApplication` --- #### updateApplication แก้ข้อมูลแอป (`appName`, รูป, `isActive`) แล้วส่ง `sync-application` (ADD) ```graphql mutation { updateApplication(id: "", update: { appName: "New name" }) { appKey appName } } ``` permission: `updateApplication` --- #### deleteApplication ลบแอป แล้วส่ง `sync-application` (REMOVE) ```graphql mutation { deleteApplication(id: "") { appKey } } ``` permission: `deleteApplication` --- #### createTheme / updateTheme / deleteTheme / resetTheme จัดการ theme กลาง · `parentThemeId` ทำให้ theme สืบค่าจาก theme แม่ · `resetTheme` คำนวณ `calVariable` ของทุก theme ใหม่ ```graphql mutation { createTheme(createThemeInput: { themeName: "Dark Blue", parentThemeId: null, variable: { colorPrimary: "#1d4ed8" }, isDefault: false }) { _id themeKey themeKeyPath calVariable } } ``` permission: `createTheme` · `updateTheme` · `deleteTheme` · `resetTheme` --- #### createAppTheme / deleteAppTheme เลือก theme ให้แอป ```graphql mutation { createAppTheme(createAppThemeInput: { appKey: "my-app", themeKey: "", isDefault: true }) { _id appKey themeKey } } ``` permission: `createAppTheme` · `deleteAppTheme` --- #### createCredentialAppTheme / deleteCredentialAppTheme เลือก theme ให้ app credential ```graphql mutation { createCredentialAppTheme(createCredentialAppThemeInput: { appKey: "my-app", credentialId: "", themeKey: "" }) { _id } } ``` ใช้โดยผู้ดูแลแอป --- #### createHostTheme / updatedHostTheme / deleteHostTheme ผูกโดเมนกับแอป + app credential + theme ```graphql mutation { createHostTheme(createHostThemeInput: { hostName: "admin.example.com", appCredentialId: "", themeKey: "", isDefault: true }) { _id hostThemeKey hostName appKey clientId } } ``` | input (create) | Type | คำอธิบาย | | --- | --- | --- | | hostName | String! | โดเมน | | appCredentialId | String! | app credential ที่หน้าบ้านจะใช้ | | themeKey | String! | | | hostThemeKey | String | ไม่ระบุได้ | | isDefault | Boolean = false | | | isActive | Boolean = true | | | description | String | | `updatedHostTheme(hostThemeId, updateHostThemeInput: { isDefault, themeKey, description, isActive })` permission: `createHostTheme` · `updateHostTheme` · `deleteHostTheme` --- ## kafka consume Reference header ทุกข้อความ: `appKey` และ `serviceKey` = service ปลายทาง · topic ที่ส่งถึง service เดียวจะทำงานเฉพาะเมื่อ `serviceKey` = `application` และ application มี App Certificate ในแอปนั้น | topic | ผู้ส่ง | ทำอะไร | | --- | --- | --- | | `init-system` | core | ตั้งระบบครั้งแรก: สร้างแอป SYSTEM, เก็บ App Certificate / app credential และสร้าง Theme / AppTheme / CredentialAppTheme ตั้งต้น | | `refresh-data` | core | ส่ง `sync-application` ของแอปนั้น + `sync-permission` ขึ้นไปใหม่ แล้วล้าง cache ของตัวเอง | | `sync-app-certificate` | core | เก็บ App Certificate ของ service ในแอป | | `sync-app-credential` | authentication | เก็บ app credential (ใช้ตรวจ token / clientId) | | `sync-service-setting` | core | เก็บ service setting ของ application | | `sync-app-service-setting` | core | เก็บ app service setting (ใช้ตอบ `getConfigByHost`) | | `sync-user-policy` | access-control | เก็บ UserPolicy ใช้ตรวจ permission ของ API | รายละเอียด payload ของแต่ละ topic ดูที่ [สิ่งที่ Service ต้องมี](serviceX.md) --- ## Kafka Produce Reference --- ### sync-application ข้อมูลแอป กระจายให้ทุก service (รวม core) topic: sync-application header: appKey Action: ADD (สร้าง / แก้ไข) | REMOVE (ลบ) | key ใน `application` | Type | คำอธิบาย | | --- | --- | --- | | id | string | | | appKey | string | | | appName | string | | | description | string | | | iconImageKey / logoImageKey / backgroundImageKey | string | | | isActive | boolean | | | isSystem | boolean | | --- ### sync-permission permission ของ API ใน application ส่งให้ access-control (ตอบ refresh-data) topic: sync-permission header: appKey, serviceKey = access-control Action: ADD payload ดูที่ [สิ่งที่ Service ต้องมี › sync-permission](serviceX.md) --- > อัปเดตจากโค้ด gumon-application-service@45a5f62 · 2026-10-05 --- # Authentication Service Service สำหรับยืนยันตัวตนผู้ใช้ เป็นเจ้าของบัญชีผู้ใช้ (แยกตามแอป), การสมัครและ login ทุกแบบ, session และ token, AppCredential (ตัวตนของแอปที่ใช้เรียก API) และข้อมูลอุปกรณ์มือถือ / push token serviceKey: authentication - บัญชีผูกกับแอป (`appKey`) — คนเดียวกันใช้ 2 แอป จะมี 2 บัญชี - ข้อมูลชื่อ-นามสกุลและโปรไฟล์อยู่ที่ [Profile Service](userService.md) โดย authentication ส่งค่าเริ่มต้นไปให้ผ่าน topic `sync-auth` - สิทธิ์การใช้งาน (role / permission) อยู่ที่ [ACL Service](aclService.md)
- [วิธี login ที่มี](#login-methods) - [AppCredential และ header ที่ต้องส่ง](#app-credential) - [API Reference](#api-reference) - [kafka consume Reference](#kafka-consume-reference) - [Kafka Produce Reference](#kafka-produce-reference) --- ## วิธี login ที่มี ทุกวิธีคืน token ให้โดยตรง (ไม่มี authorization-code flow) | วิธี | API | ผลลัพธ์ | | --- | --- | --- | | username / email / เบอร์โทร + password | `loginWithAccessType`, `loginWithUserNameAccessType`, `loginWithEmailAccessType`, `loginWithPhoneNumberAccessType` | ได้ `token { accessToken refreshToken }` ทันที | | WebSocket | `getLoginSocketId` แล้ว `loginWithUserNameSocketType` / `loginWithEmailSocketType` / `loginWithPhoneNumberSocketType` | token ถูกส่งเข้า socket ที่ได้จาก `getLoginSocketId` (เหมาะกับหน้า login ที่แยกจากแอปปลายทาง) | | OTP ทาง email / SMS | `loginWithEmailOtpType` / `loginWithPhoneNumberOtpType` → `verifierOtp` | ขั้นแรกได้ `otpRef` แล้วยืนยัน OTP เพื่อรับ token | | Google | `loginWithGoogleOAuth`, `registerWithGoogleOAuth`, `linkAccountWithGoogleOAuth`, `unlinkAccountFromGoogleOAuth` | ส่ง `id_token` ของ Google | | Facebook | `loginWithFaceBookOAuth`, `registerWithFaceBookOAuth`, `linkAccountWithFaceBookOAuth`, `unlinkAccountFromFaceBookOAuth` | ส่ง `access_token` ของ Facebook | | Apple | `loginWithAppleOAuth`, `registerWithAppleOAuth`, `linkAccountWithAppleOAuth`, `unlinkAccountFromAppleOAuth` | ส่ง `id_token` ของ Apple | - ลืมรหัสผ่าน: `resetPasswordWithEmail` / `resetPasswordWithPhoneNumber` → `verifierOtp` → `resetMyPasswordByOtpVerifierRef` - ต่ออายุ token: `refreshToken` (ได้ refreshToken ชุดใหม่ทุกครั้ง ชุดเดิมใช้ซ้ำไม่ได้) · ออกจากระบบ: `logout`, `logoutAll` - การ login ด้วย Google / Facebook / Apple ต้องตั้งค่า provider ต่อแอปก่อน ผ่าน App Service Setting ของ service `authentication` (topic `sync-app-service-setting`) ในรูป ```json { "googleOAuth": { "clientId": "" }, "facebookOAuth": { "appId": "", "appSecret": "" }, "appleOAuth": { "clientId": "" } } ``` --- ## AppCredential และ header ที่ต้องส่ง AppCredential คือตัวตนของ "ผู้เรียก API" ในแต่ละแอป สร้างด้วย `generateAppCredential` แต่ละตัวมี `clientId` และตั้งกฎได้ เช่น host ที่อนุญาต (`authorizedHosts`), redirect URL ที่อนุญาต (`authorizedRedirectUrls`), อายุ token (`jwtAccessExpireTime`, `jwtRefreshExpireTime` หน่วยวินาที), การล็อกบัญชีเมื่อ login ผิด (`numberOfFail`, `minutesTimeFail`, `minutesTimeLock`) และวันหมดอายุ (`expiryAt`) AppCredential มี 2 ประเภท (`appCredentialType`) | ประเภท | ใช้กับ | ข้อมูลที่ใช้ | | --- | --- | --- | | `USER` | หน้าบ้าน (เว็บ / แอปมือถือ) ที่ผู้ใช้ login | `clientId` | | `SYSTEM` | ระบบภายนอก / server-to-server (ที่มักเรียกกันว่า **apiKey**) | `clientId` + `clientSecret` · `clientSecret` แสดงครั้งเดียวตอน `generateAppCredential` ถ้าทำหายต้องสร้างใหม่ | header ที่ service ทุกตัวใน Gumon ใช้ตรวจตัวตน (ตรวจได้เองในแต่ละ service เพราะ AppCredential ถูกส่งไปให้ทุก service ผ่าน topic `sync-app-credential`) | ผู้เรียก | header | | --- | --- | | ผู้ใช้ที่ login แล้ว | `authorization: ` (+ `X-APP-CLIENT-ID` ได้ ถ้าส่งต้องตรงกับ clientId ใน token) | | ระบบภายนอก (SYSTEM) | `X-APP-CLIENT-ID: ` + `X-APP-CLIENT-SECRET: ` | | ระดับแอป ไม่ต้อง login (เช่น login / register) | `X-APP-CLIENT-ID: ` | ใน API Reference ด้านล่าง "การยืนยันตัวตน" บอกว่า API นั้นต้องใช้ header แบบไหน และ "สิทธิ์" คือ permissionKey ของ service `authentication` ที่ผู้เรียกต้องได้รับผ่าน role ใน [ACL Service](aclService.md) ---

## API Reference --- ### Query เป็น API ที่ใช้สำหรับการ Query ข้อมูลออกมา ไม่มีการแก้ไข Data --- #### getMySession ดึงข้อมูล session ปัจจุบันของผู้ใช้ที่ login อยู่ - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetMySession { getMySession { _id authId appCredentialId appCredential { _id appKey clientId description validateAuthorizedHosts validateAuthorizedRedirectUrls } clientId host ipAddress userAgent cookie isActive # ... } } ``` Response: `AccountSession` --- #### getMyAllSessions ดึงข้อมูล Session ทั้งหมดของผู้ใช้ปัจจุบัน - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getMyAllSessions` ```graphql query GetMyAllSessions($getInput: GetAccountSessionByAuthIdInput) { getMyAllSessions(getInput: $getInput) { sessions { _id authId appCredentialId clientId host ipAddress } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetAccountSessionByAuthIdInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetAccountSessionByAuthIdFilterInput` | | | | search | `GetAccountSessionByAuthIdSearchInput` | | | | sort | `GetAccountSessionByAuthIdSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `AccountSessionPagination` --- #### getSessionByAuthId ดึงรายการ session ของผู้ใช้ตาม authId - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getSessionByAuthId` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetSessionByAuthId($authId: String!, $getInput: GetAccountSessionByAuthIdInput, $appKey: String) { getSessionByAuthId(authId: $authId, getInput: $getInput, appKey: $appKey) { sessions { _id authId appCredentialId clientId host ipAddress } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetAccountSessionByAuthIdInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetAccountSessionByAuthIdFilterInput` | | | | search | `GetAccountSessionByAuthIdSearchInput` | | | | sort | `GetAccountSessionByAuthIdSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `authId: String!`, `appKey: String` Response: `AccountSessionPagination` --- #### getAppCredentials ดึงข้อมูล AppCredentials ทั้งหมดที่มีในระบบ สามารถจัดการข้าม app ได้ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getAppCredentials` ```graphql query GetAppCredentials($input: GetAppCredentialsInput) { getAppCredentials(input: $input) { appCredentials { _id appKey clientId description validateAuthorizedHosts validateAuthorizedRedirectUrls } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetAppCredentialsInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetAppCredentialsFilterInput` | | | | search | `GetAppCredentialsSearchInput` | | | | sort | `GetAppCredentialsSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `AppCredentialPagination!` --- #### getAppCredentialById ดึงข้อมูล AppCredentials ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getAppCredentialById` ```graphql query GetAppCredentialById($appCredentialId: ID!) { getAppCredentialById(appCredentialId: $appCredentialId) { _id appKey clientId description authorizedHosts { _id hostName description isActive } validateAuthorizedHosts authorizedRedirectUrls { _id redirectUrl description isActive } validateAuthorizedRedirectUrls minutesTimeLock numberOfFail # ... } } ``` | argument | Type | | --- | --- | | appCredentialId | `ID!` | Response: `AppCredential!` --- #### getAppCredentialByClientId ดึงข้อมูล AppCredentials ตาม ClientId - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getAppCredentialByClientId` ```graphql query GetAppCredentialByClientId($clientId: String!) { getAppCredentialByClientId(clientId: $clientId) { _id appKey clientId description authorizedHosts { _id hostName description isActive } validateAuthorizedHosts authorizedRedirectUrls { _id redirectUrl description isActive } validateAuthorizedRedirectUrls minutesTimeLock numberOfFail # ... } } ``` | argument | Type | | --- | --- | | clientId | `String!` | Response: `AppCredential!` --- #### checkMobileAppUpdate เช็กว่าแอปมือถือต้องอัปเดตหรือไม่ จาก clientId, platform และ appVersion - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query CheckMobileAppUpdate($input: CheckMobileAppUpdateInput!) { checkMobileAppUpdate(input: $input) { clientId platform appVersion minimumAppVersion shouldUpdate forceUpdate appUpdateUrl } } ``` `input`: `CheckMobileAppUpdateInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | clientId | `String!` | ใช่ | | | platform | `EnumMobilePlatform!` | ใช่ | | | appVersion | `String!` | ใช่ | | Response: `CheckMobileAppUpdateResult!` --- #### getLoginSocketId ขอ socketId สำหรับ login แบบ WebSocket (ใช้คู่กับ `loginWith*SocketType`) - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetLoginSocketId($redirectURL: String, $socketId: String) { getLoginSocketId(redirectURL: $redirectURL, socketId: $socketId) { socketId redirectURL } } ``` | argument | Type | | --- | --- | | redirectURL | `String` | | socketId | `String` | Response: `GetLoginSocketIdType!` --- #### getMyAccount ดึงข้อมูลบัญชีของผู้ใช้ที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetMyAccount { getMyAccount { authId username emails phoneNumbers { countryCode phoneNumber } oauthProviders { provider providerSubject linkedAt additionalFields } defaultOrganizationKey isActive } } ``` Response: `AccountLogin` --- #### getAccountByOAuthProvider ค้นบัญชีจาก OAuth provider (`GOOGLE`, `FACEBOOK`, `APPLE`) และ subject ของ provider - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetAccountByOAuthProvider($provider: String!, $providerSubject: String!) { getAccountByOAuthProvider(provider: $provider, providerSubject: $providerSubject) { authId username emails phoneNumbers { countryCode phoneNumber } oauthProviders { provider providerSubject linkedAt additionalFields } defaultOrganizationKey isActive } } ``` | argument | Type | | --- | --- | | provider | `String!` | | providerSubject | `String!` | Response: `AccountLogin` --- #### getAccount ค้นบัญชีตามเงื่อนไขใน input - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetAccount($getAccountInput: GetAccountInput) { getAccount(getAccountInput: $getAccountInput) { authId username emails phoneNumbers { countryCode phoneNumber } isActive } } ``` `getAccountInput`: `GetAccountInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | username | `String!` | ใช่ | | | countryCode | `String` | | | Response: `Account` --- #### getAccountByUsername ค้นบัญชีตาม username - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetAccountByUsername($getAccountByUsernameInput: GetAccountByUsernameInput) { getAccountByUsername(getAccountByUsernameInput: $getAccountByUsernameInput) { authId username isActive } } ``` `getAccountByUsernameInput`: `GetAccountByUsernameInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | username | `String!` | ใช่ | | Response: `AccountUsername` --- #### getAccountByPhoneNumber ค้นบัญชีตามเบอร์โทรศัพท์ - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetAccountByPhoneNumber($getAccountByPhoneNumberInput: GetAccountByPhoneNumberInput) { getAccountByPhoneNumber(getAccountByPhoneNumberInput: $getAccountByPhoneNumberInput) { authId username countryCode phoneNumber verifyStatus isActive } } ``` `getAccountByPhoneNumberInput`: `GetAccountByPhoneNumberInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | countryCode | `String!` | ใช่ | | | phoneNumber | `String!` | ใช่ | | Response: `AccountPhoneNumber` --- #### getAccountByEmail ค้นบัญชีตาม email - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetAccountByEmail($getAccountByEmailInput: GetAccountByEmailInput) { getAccountByEmail(getAccountByEmailInput: $getAccountByEmailInput) { authId username email verifyStatus isActive } } ``` `getAccountByEmailInput`: `GetAccountByEmailInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | email | `String!` | ใช่ | | Response: `AccountEmail` --- #### getEmailsByAuthId ดึง email ทั้งหมดของบัญชีตาม authId - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetEmailsByAuthId($authId: String!, $getInput: GetEmailsByAccountInput) { getEmailsByAuthId(authId: $authId, getInput: $getInput) { emails { authId username email verifyStatus isActive } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetEmailsByAccountInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetEmailsByAccountFilterInput` | | | | search | `GetEmailsByAccountSearchInput` | | | | sort | `GetEmailsByAccountSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `authId: String!` Response: `EmailAccountPagination` --- #### getPhoneNumbersByAuthId ดึงเบอร์โทรศัพท์ทั้งหมดของบัญชีตาม authId - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetPhoneNumbersByAuthId($authId: String!, $getInput: GetPhoneNumbersByAccountInput) { getPhoneNumbersByAuthId(authId: $authId, getInput: $getInput) { phoneNumbers { authId username countryCode phoneNumber verifyStatus isActive } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetPhoneNumbersByAccountInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetPhoneNumbersByAccountFilterInput` | | | | search | `GetPhoneNumbersByAccountSearchInput` | | | | sort | `GetPhoneNumbersByAccountSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `authId: String!` Response: `PhoneNumberAccountPagination` --- #### getAccountsByAppKey ดึงบัญชีทั้งหมดของแอปตาม appKey แบบแบ่งหน้า - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetAccountsByAppKey($appKey: String!, $getInput: GetAccountByAppKeyInput) { getAccountsByAppKey(appKey: $appKey, getInput: $getInput) { accounts { authId username emails isActive } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetAccountByAppKeyInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetAccountByAppKeyFilterInput` | | | | search | `GetAccountByAppKeySearchInput` | | | | sort | `GetAccountByAppKeySortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `appKey: String!` Response: `AccountPagination` --- #### getLockAccountsByAppKey ดึงบัญชีที่ถูกล็อก (login ผิดเกินกำหนด) ของแอปตาม appKey แบบแบ่งหน้า - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getLockAccountsByAppKey` ```graphql query GetLockAccountsByAppKey($appKey: String!, $getInput: GetAccountByAppKeyInput) { getLockAccountsByAppKey(appKey: $appKey, getInput: $getInput) { locks { authId username startDate endDate } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetAccountByAppKeyInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetAccountByAppKeyFilterInput` | | | | search | `GetAccountByAppKeySearchInput` | | | | sort | `GetAccountByAppKeySortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `appKey: String!` Response: `AccountLockPagination` --- #### getSessionLoggings ดึงข้อมูล session logging แบบมีเงื่อนไข - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getSessionLoggings` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetSessionLoggings($input: GetSessionLoggingInput) { getSessionLoggings(input: $input) { sessionLoggings { _id appKey clientId totalSession startTime endTime } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetSessionLoggingInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetSessionLoggingFilterInput` | | | | search | `GetSessionLoggingSearchInput` | | | | sort | `GetSessionLoggingSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `SessionLoggingPagination!` --- #### getSessionLoggingById ดึงข้อมูล session logging โดยใช้ sessionLoggingId - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getSessionLoggingById` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetSessionLoggingById($sessionLoggingId: ID!) { getSessionLoggingById(sessionLoggingId: $sessionLoggingId) { _id appKey clientId totalSession startTime endTime createdAt updatedAt } } ``` | argument | Type | | --- | --- | | sessionLoggingId | `ID!` | Response: `SessionLogging` --- #### getMyMobileDevices ดึงอุปกรณ์ทั้งหมดของตัวเอง (ผู้ที่ login) แบบแบ่งหน้า — ไว้ให้ user มอนิเตอร์เครื่องของตัวเอง - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetMyMobileDevices($input: GetMyMobileDevicesInput) { getMyMobileDevices(input: $input) { userMobileDevices { _id appKey installationId userId platform deviceName } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetMyMobileDevicesInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | status | `EnumDeviceStatus` | | กรองตามสถานะเครื่อง (ถ้าไม่ระบุ = ทั้งหมด) | | pagination | `CustomPaginateInput` | | แบ่งหน้า | Response: `UserMobileDevicePagination!` --- #### getMobileDevicesByUser ดึงอุปกรณ์ของผู้ใช้ที่ระบุ แบบแบ่งหน้า (ภายใน app เดียวกับผู้เรียก) — ต้องมีสิทธิ์ getMobileDevicesByUser - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getMobileDevicesByUser` ```graphql query GetMobileDevicesByUser($input: GetMobileDevicesByUserInput!) { getMobileDevicesByUser(input: $input) { userMobileDevices { _id appKey installationId userId platform deviceName } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetMobileDevicesByUserInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | userId | `ID!` | ใช่ | authId ของผู้ใช้ที่จะดึงอุปกรณ์ | | status | `EnumDeviceStatus` | | กรองตามสถานะเครื่อง (ถ้าไม่ระบุ = ทั้งหมด) | | pagination | `CustomPaginateInput` | | แบ่งหน้า | Response: `UserMobileDevicePagination!` --- #### getMobileDevicesByApp ดึงอุปกรณ์ทั้งหมดของ app ที่ระบุ แบบแบ่งหน้า — ต้องมีสิทธิ์ getMobileDevicesByApp (ข้าม app ต้องมี systemApp) - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getMobileDevicesByApp` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetMobileDevicesByApp($input: GetMobileDevicesByAppInput!) { getMobileDevicesByApp(input: $input) { userMobileDevices { _id appKey installationId userId platform deviceName } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetMobileDevicesByAppInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String!` | ใช่ | appKey ของ app ที่จะดึงอุปกรณ์ (ข้าม app ที่ login ต้องมีสิทธิ์ systemApp) | | status | `EnumDeviceStatus` | | กรองตามสถานะเครื่อง (ถ้าไม่ระบุ = ทั้งหมด) | | pagination | `CustomPaginateInput` | | แบ่งหน้า | Response: `UserMobileDevicePagination!` --- ### Mutation เป็น API ที่ใช้สำหรับการแก้ไขข้อมูล --- #### registerWithUsername สมัครบัญชีด้วย username + password - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation RegisterWithUsername($registerUsernameInput: RegisterUsernameInput) { registerWithUsername(registerUsernameInput: $registerUsernameInput) { status } } ``` `registerUsernameInput`: `RegisterUsernameInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | username | `String!` | ใช่ | | | password | `String!` | ใช่ | | | confirmPassword | `String!` | ใช่ | | | roleKey | `RoleKeyEnum` | | | | inviteCodeKey | `String` | | | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | | firstName | `String` | | firstName: ชื่อ | | middleName | `String` | | middleName: ชื่อกลาง | | lastName | `String` | | lastName: ชื่อสกุล | | displayName | `String` | | displayName: ชื่อที่ใช้แสดงผล | | gender | `String` | | gender: เพศ | | profileImage | `String` | | profileImage: fileKey ที่ได้จากการอัปโหลดไฟล์ไปยัง File Service | | electronicSignatureKey | `String` | | electronicSignatureKey: fileKey ที่ได้จากการอัปโหลดไฟล์ไปยัง File Service | Response: `RegisterStatus` --- #### registerWithEmail สมัครบัญชีด้วย email + password - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation RegisterWithEmail($registerEmailInput: RegisterEmailInput) { registerWithEmail(registerEmailInput: $registerEmailInput) { status } } ``` `registerEmailInput`: `RegisterEmailInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | email | `String!` | ใช่ | | | password | `String!` | ใช่ | | | confirmPassword | `String!` | ใช่ | | | roleKey | `RoleKeyEnum` | | | | inviteCodeKey | `String` | | | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | | firstName | `String` | | firstName: ชื่อ | | middleName | `String` | | middleName: ชื่อกลาง | | lastName | `String` | | lastName: ชื่อสกุล | | displayName | `String` | | displayName: ชื่อที่ใช้แสดงผล | | gender | `String` | | gender: เพศ | | profileImage | `String` | | profileImage: fileKey ที่ได้จากการอัปโหลดไฟล์ไปยัง File Service | | electronicSignatureKey | `String` | | electronicSignatureKey: fileKey ที่ได้จากการอัปโหลดไฟล์ไปยัง File Service | Response: `RegisterStatus` --- #### registerWithPhoneNumber สมัครบัญชีด้วยเบอร์โทรศัพท์ + password - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation RegisterWithPhoneNumber($registerPhoneNumberInput: RegisterPhoneNumberInput) { registerWithPhoneNumber(registerPhoneNumberInput: $registerPhoneNumberInput) { status } } ``` `registerPhoneNumberInput`: `RegisterPhoneNumberInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | countryCode | `String!` | ใช่ | | | phoneNumber | `String!` | ใช่ | | | password | `String!` | ใช่ | | | confirmPassword | `String!` | ใช่ | | | roleKey | `RoleKeyEnum` | | | | inviteCodeKey | `String` | | | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | | firstName | `String` | | firstName: ชื่อ | | middleName | `String` | | middleName: ชื่อกลาง | | lastName | `String` | | lastName: ชื่อสกุล | | displayName | `String` | | displayName: ชื่อที่ใช้แสดงผล | | gender | `String` | | gender: เพศ | | profileImage | `String` | | profileImage: fileKey ที่ได้จากการอัปโหลดไฟล์ไปยัง File Service | | electronicSignatureKey | `String` | | electronicSignatureKey: fileKey ที่ได้จากการอัปโหลดไฟล์ไปยัง File Service | Response: `RegisterStatus` --- #### register สมัครบัญชีด้วย username, email หรือเบอร์โทรศัพท์ (รวมทุกแบบไว้ใน mutation เดียว) - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation Register($registerInput: RegisterInput) { register(registerInput: $registerInput) { status } } ``` `registerInput`: `RegisterInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | username | `String` | | | | email | `String` | | | | countryCode | `String` | | | | phoneNumber | `String` | | | | password | `String!` | ใช่ | | | confirmPassword | `String!` | ใช่ | | | roleKey | `RoleKeyEnum` | | | | inviteCodeKey | `String` | | | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | | firstName | `String` | | firstName: ชื่อ | | middleName | `String` | | middleName: ชื่อกลาง | | lastName | `String` | | lastName: ชื่อสกุล | | displayName | `String` | | displayName: ชื่อที่ใช้แสดงผล | | gender | `String` | | gender: เพศ | | profileImage | `String` | | profileImage: fileKey ที่ได้จากการอัปโหลดไฟล์ไปยัง File Service | | electronicSignatureKey | `String` | | electronicSignatureKey: fileKey ที่ได้จากการอัปโหลดไฟล์ไปยัง File Service | Response: `RegisterStatus` --- #### setAccountDefaultOrganization กำหนด DefaultOrganization ของ Account ที่ระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation SetAccountDefaultOrganization($input: [SetAccountDefaultOrganizationInput]!) { setAccountDefaultOrganization(input: $input) { status } } ``` `input`: `SetAccountDefaultOrganizationInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | authId | `String!` | ใช่ | | | organizationKey | `String` | | | Response: `RegisterStatus` --- #### useInviteCode ใช้ InviteCodeKey ในการลงทะเบียน - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation UseInviteCode($input: UseInviteCodeInput!) { useInviteCode(input: $input) { status } } ``` `input`: `UseInviteCodeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | inviteCodeKey | `String!` | ใช่ | | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `RegisterStatus` --- #### addEmailToAccount เพิ่ม email ให้บัญชี - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation AddEmailToAccount($input: AddEmailToAccountInput!) { addEmailToAccount(input: $input) { status } } ``` `input`: `AddEmailToAccountInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String` | | | | authId | `String!` | ใช่ | | | email | `String!` | ใช่ | | Response: `RegisterStatus` --- #### addPhoneNumberToAccount เพิ่มเบอร์โทรศัพท์ให้บัญชี - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation AddPhoneNumberToAccount($input: AddPhoneNumberToAccountInput!) { addPhoneNumberToAccount(input: $input) { status } } ``` `input`: `AddPhoneNumberToAccountInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String` | | | | authId | `String!` | ใช่ | | | countryCode | `String!` | ใช่ | | | phoneNumber | `String!` | ใช่ | | Response: `RegisterStatus` --- #### updateAccount อัปเดตข้อมูล Account ที่ระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateAccount` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation UpdateAccount($input: UpdateAccountInput!) { updateAccount(input: $input) { status } } ``` `input`: `UpdateAccountInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String` | | | | authId | `String!` | ใช่ | | | isActive | `Boolean` | | | Response: `RegisterStatus` --- #### updateEmail แก้ไข email ของบัญชี - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateEmail` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation UpdateEmail($input: UpdateEmailInput!) { updateEmail(input: $input) { status } } ``` `input`: `UpdateEmailInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String` | | | | authId | `String!` | ใช่ | | | email | `String!` | ใช่ | | | isActive | `Boolean` | | | Response: `RegisterStatus` --- #### updatePhoneNumber อัปเดต phone number ของ Account ที่ระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updatePhoneNumber` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation UpdatePhoneNumber($input: UpdatePhoneNumberInput!) { updatePhoneNumber(input: $input) { status } } ``` `input`: `UpdatePhoneNumberInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String` | | | | authId | `String!` | ใช่ | | | countryCode | `String!` | ใช่ | | | phoneNumber | `String!` | ใช่ | | | isActive | `Boolean` | | | Response: `RegisterStatus` --- #### removeEmailFromAccount ลบ email ที่ระบุออกจาก Account ที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `removeEmailFromAccount` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation RemoveEmailFromAccount($input: RemoveEmailInput!) { removeEmailFromAccount(input: $input) { status } } ``` `input`: `RemoveEmailInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String` | | | | authId | `String!` | ใช่ | | | email | `String!` | ใช่ | | Response: `RegisterStatus` --- #### removePhoneNumberFromAccount ลบ phone number ที่ระบุออกจาก Account ที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `removePhoneNumberFromAccount` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation RemovePhoneNumberFromAccount($input: RemovePhoneNumberInput!) { removePhoneNumberFromAccount(input: $input) { status } } ``` `input`: `RemovePhoneNumberInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String` | | | | authId | `String!` | ใช่ | | | countryCode | `String!` | ใช่ | | | phoneNumber | `String!` | ใช่ | | Response: `RegisterStatus` --- #### generateAppCredential สร้าง AppCredential ใหม่ขึ้นมาในระบบ โดยที่ถ้า AppCredentialType = SYSTEM จะทำการสร้าง clientAuthId สำหรับ AppCredential นั้นด้วย - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `generateAppCredential` ```graphql mutation GenerateAppCredential($input: GenerateAppCredentialInput!) { generateAppCredential(input: $input) { appCredential { _id appKey clientId description validateAuthorizedHosts validateAuthorizedRedirectUrls } clientSecret } } ``` `input`: `GenerateAppCredentialInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String` | | appKey ไว้ใช้ในการบอกว่าอยู่ app ใด ถ้าไม่ระบุจะใช้ app ที่ login อยู่ | | clientId | `String` | | clientId ไว้ใช้ในการใส่ไว้ใน headers เพื่อใช้งาน 'X-APP-CLIENT-ID' ถ้าไม่ระบุ ระบบ จะ auto gen ให้ | | description | `String` | | คำอธิบาย | | authorizedHosts | `[AppCredentialAuthorizedHostInput]` | | ตั้งค่าว่า AppCredential นี้สามารถใช้งานผ่าน hosts ใดได้บ้าง | | validateAuthorizedHosts | `Boolean` | | ตั้งค่าว่า AppCredential นี้จะตรวจสอบการใช้งานผ่าน hosts | | authorizedRedirectUrls | `[AppCredentialAuthorizedRedirectUrlInput]` | | ตั้งค่าว่า AppCredential นี้ยอมรับการ RedirectUrl ใดบ้าง | | validateAuthorizedRedirectUrls | `Boolean` | | ตั้งค่าว่า AppCredential นี้จะตรวจสอบการ RedirectUrl | | minutesTimeLock | `Int` | | จำนวนนาที ที่ lock เมื่อ login ผิด | | numberOfFail | `Int` | | จำนวนครั้งที่ login ผิด แล้วจะ lock | | minutesTimeFail | `Int` | | จำนวนนาที ที่เมื่อ login ผิด แล้วจะไม่ให้ login ซ้ำ ก่อนจะ lock | | jwtAccessExpireTime | `Int` | | เวลาหมดอายุของ Access token (วินาที) ถ้าเป็น 0 คือไม่มีวันหมดอายุ | | jwtRefreshExpireTime | `Int` | | เวลาหมดอายุของ Refresh token (วินาที) ถ้าเป็น 0 คือไม่มีวันหมดอายุ | | expiryAt | `Date` | | เวลาหมดอายุของ AppCredential นี้(ถ้ามี) | | appCredentialType | `EnumAppCredentialType` | | ประเภทของ appCredential | | minimumAppVersion | `String` | | เวอร์ชันขั้นต่ำของ mobile app ที่อนุญาตให้ใช้งาน | | appUpdateUrlAndroid | `String` | | ลิงก์อัปเดตแอปสำหรับ Android | | appUpdateUrlIos | `String` | | ลิงก์อัปเดตแอปสำหรับ iOS | | … | | | (มีอีก 4 ฟิลด์ ดู schema) | Response: `GenerateAppCredential!` --- #### updateAppCredential แก้ไขข้อมูล AppCredential โดยจะแก้ได้แค่ข้อมูลการตั้งค่าบางส่วนเท่านั้น - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateAppCredential` ```graphql mutation UpdateAppCredential($appCredentialId: ID!, $input: UpdateAppCredentialInput!) { updateAppCredential(appCredentialId: $appCredentialId, input: $input) { _id appKey clientId description authorizedHosts { _id hostName description isActive } validateAuthorizedHosts authorizedRedirectUrls { _id redirectUrl description isActive } validateAuthorizedRedirectUrls minutesTimeLock numberOfFail # ... } } ``` `input`: `UpdateAppCredentialInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | description | `String` | | คำอธิบาย | | authorizedHosts | `[AppCredentialAuthorizedHostInput]` | | ตั้งค่าว่า AppCredential นี้สามารถใช้งานผ่าน hosts ใดได้บ้าง | | validateAuthorizedHosts | `Boolean` | | ตั้งค่าว่า AppCredential นี้จะตรวจสอบการใช้งานผ่าน hosts | | authorizedRedirectUrls | `[AppCredentialAuthorizedRedirectUrlInput]` | | ตั้งค่าว่า AppCredential นี้ยอมรับการ RedirectUrl ใดบ้าง | | validateAuthorizedRedirectUrls | `Boolean` | | ตั้งค่าว่า AppCredential นี้จะตรวจสอบการ RedirectUrl | | minutesTimeLock | `Int` | | จำนวนนาที ที่ lock เมื่อ login ผิด | | numberOfFail | `Int` | | จำนวนครั้งที่ login ผิด แล้วจะ lock | | minutesTimeFail | `Int` | | จำนวนนาที ที่เมื่อ login ผิด แล้วจะไม่ให้ login ซ้ำ ก่อนจะ lock | | jwtAccessExpireTime | `Int` | | เวลาหมดอายุของ Access token (วินาที) ถ้าเป็น 0 คือไม่มีวันหมดอายุ | | jwtRefreshExpireTime | `Int` | | เวลาหมดอายุของ Refresh token (วินาที) ถ้าเป็น 0 คือไม่มีวันหมดอายุ | | expiryAt | `Date` | | เวลาหมดอายุของ AppCredential นี้(ถ้ามี) | | minimumAppVersion | `String` | | เวอร์ชันขั้นต่ำของ mobile app ที่อนุญาตให้ใช้งาน | | appUpdateUrlAndroid | `String` | | ลิงก์อัปเดตแอปสำหรับ Android | | appUpdateUrlIos | `String` | | ลิงก์อัปเดตแอปสำหรับ iOS | | forceUpdate | `Boolean` | | บังคับอัปเดตเมื่อเวอร์ชันต่ำกว่า minimumAppVersion | | isActive | `Boolean` | | สถานะการใช้งาน | argument อื่น: `appCredentialId: ID!` Response: `AppCredential!` --- #### removeAppCredential ลบ AppCredential นั้นออกจากระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `removeAppCredential` ```graphql mutation RemoveAppCredential($appCredentialId: ID!) { removeAppCredential(appCredentialId: $appCredentialId) { _id appKey clientId description authorizedHosts { _id hostName description isActive } validateAuthorizedHosts authorizedRedirectUrls { _id redirectUrl description isActive } validateAuthorizedRedirectUrls minutesTimeLock numberOfFail # ... } } ``` | argument | Type | | --- | --- | | appCredentialId | `ID!` | Response: `AppCredential!` --- #### loginWithAccessType login ด้วย username, email หรือเบอร์โทรศัพท์ + password แล้วได้ token กลับทันที · ถ้าใช้เบอร์โทรศัพท์ ควรส่ง `countryCode` (เช่น `+66`) หรือใส่เบอร์ในรูป `+66...` - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation LoginWithAccessType($loginWithAccessTypeInput: LoginWithAccessTypeInput, $sessionInfo: SessionInfoInput) { loginWithAccessType(loginWithAccessTypeInput: $loginWithAccessTypeInput, sessionInfo: $sessionInfo) { redirectURL token { accessToken refreshToken } } } ``` `loginWithAccessTypeInput`: `LoginWithAccessTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | username | `String!` | ใช่ | | | countryCode | `String` | | | | password | `String!` | ใช่ | | | redirectURL | `String` | | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginAccessType` --- #### loginWithGoogleOAuth login ด้วย Google (ส่ง `id_token` จาก Google) - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation LoginWithGoogleOAuth($loginWithGoogleOAuthInput: LoginWithGoogleOAuthInput, $sessionInfo: SessionInfoInput) { loginWithGoogleOAuth(loginWithGoogleOAuthInput: $loginWithGoogleOAuthInput, sessionInfo: $sessionInfo) { redirectURL token { accessToken refreshToken } } } ``` `loginWithGoogleOAuthInput`: `LoginWithGoogleOAuthInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | idToken | `String!` | ใช่ | | | redirectURL | `String` | | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginAccessType` --- #### registerWithGoogleOAuth สมัครบัญชีใหม่ด้วย Google (`id_token`) แล้วได้ token กลับ - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation RegisterWithGoogleOAuth($registerWithGoogleOAuthInput: RegisterWithGoogleOAuthInput, $sessionInfo: SessionInfoInput) { registerWithGoogleOAuth(registerWithGoogleOAuthInput: $registerWithGoogleOAuthInput, sessionInfo: $sessionInfo) { redirectURL token { accessToken refreshToken } } } ``` `registerWithGoogleOAuthInput`: `RegisterWithGoogleOAuthInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | idToken | `String!` | ใช่ | | | redirectURL | `String` | | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginAccessType` --- #### linkAccountWithGoogleOAuth ผูกบัญชี Google เข้ากับบัญชีที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation LinkAccountWithGoogleOAuth($linkAccountWithGoogleOAuthInput: LinkAccountWithGoogleOAuthInput) { linkAccountWithGoogleOAuth(linkAccountWithGoogleOAuthInput: $linkAccountWithGoogleOAuthInput) { status } } ``` `linkAccountWithGoogleOAuthInput`: `LinkAccountWithGoogleOAuthInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | idToken | `String!` | ใช่ | | Response: `LinkAccountWithGoogleOAuthStatus` --- #### unlinkAccountFromGoogleOAuth ยกเลิกการผูกบัญชี Google ออกจากบัญชีที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation UnlinkAccountFromGoogleOAuth($unlinkAccountFromGoogleOAuthInput: UnlinkAccountFromGoogleOAuthInput) { unlinkAccountFromGoogleOAuth(unlinkAccountFromGoogleOAuthInput: $unlinkAccountFromGoogleOAuthInput) { status } } ``` `unlinkAccountFromGoogleOAuthInput`: `UnlinkAccountFromGoogleOAuthInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | authId | `String` | | | Response: `UnlinkAccountFromGoogleOAuthStatus` --- #### loginWithFaceBookOAuth login ด้วย Facebook (ส่ง `access_token` จาก Facebook) - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation LoginWithFaceBookOAuth($loginWithFaceBookOAuthInput: LoginWithFaceBookOAuthInput, $sessionInfo: SessionInfoInput) { loginWithFaceBookOAuth(loginWithFaceBookOAuthInput: $loginWithFaceBookOAuthInput, sessionInfo: $sessionInfo) { redirectURL token { accessToken refreshToken } } } ``` `loginWithFaceBookOAuthInput`: `LoginWithFaceBookOAuthInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | idToken | `String!` | ใช่ | | | redirectURL | `String` | | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginAccessType` --- #### registerWithFaceBookOAuth สมัครบัญชีใหม่ด้วย Facebook (`access_token`) แล้วได้ token กลับ - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation RegisterWithFaceBookOAuth($registerWithFaceBookOAuthInput: RegisterWithFaceBookOAuthInput, $sessionInfo: SessionInfoInput) { registerWithFaceBookOAuth(registerWithFaceBookOAuthInput: $registerWithFaceBookOAuthInput, sessionInfo: $sessionInfo) { redirectURL token { accessToken refreshToken } } } ``` `registerWithFaceBookOAuthInput`: `RegisterWithFaceBookOAuthInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | idToken | `String!` | ใช่ | | | redirectURL | `String` | | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginAccessType` --- #### linkAccountWithFaceBookOAuth ผูกบัญชี Facebook เข้ากับบัญชีที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation LinkAccountWithFaceBookOAuth($linkAccountWithFaceBookOAuthInput: LinkAccountWithFaceBookOAuthInput) { linkAccountWithFaceBookOAuth(linkAccountWithFaceBookOAuthInput: $linkAccountWithFaceBookOAuthInput) { status } } ``` `linkAccountWithFaceBookOAuthInput`: `LinkAccountWithFaceBookOAuthInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | idToken | `String!` | ใช่ | | Response: `LinkAccountWithFaceBookOAuthStatus` --- #### unlinkAccountFromFaceBookOAuth ยกเลิกการผูกบัญชี Facebook ออกจากบัญชีที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation UnlinkAccountFromFaceBookOAuth($unlinkAccountFromFaceBookOAuthInput: UnlinkAccountFromFaceBookOAuthInput) { unlinkAccountFromFaceBookOAuth(unlinkAccountFromFaceBookOAuthInput: $unlinkAccountFromFaceBookOAuthInput) { status } } ``` `unlinkAccountFromFaceBookOAuthInput`: `UnlinkAccountFromFaceBookOAuthInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | authId | `String` | | | Response: `UnlinkAccountFromFaceBookOAuthStatus` --- #### loginWithAppleOAuth login ด้วย Apple (ส่ง `id_token` จาก Apple) - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation LoginWithAppleOAuth($loginWithAppleOAuthInput: LoginWithAppleOAuthInput, $sessionInfo: SessionInfoInput) { loginWithAppleOAuth(loginWithAppleOAuthInput: $loginWithAppleOAuthInput, sessionInfo: $sessionInfo) { redirectURL token { accessToken refreshToken } } } ``` `loginWithAppleOAuthInput`: `LoginWithAppleOAuthInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | idToken | `String!` | ใช่ | | | redirectURL | `String` | | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginAccessType` --- #### registerWithAppleOAuth สมัครบัญชีใหม่ด้วย Apple (`id_token`) แล้วได้ token กลับ - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation RegisterWithAppleOAuth($registerWithAppleOAuthInput: RegisterWithAppleOAuthInput, $sessionInfo: SessionInfoInput) { registerWithAppleOAuth(registerWithAppleOAuthInput: $registerWithAppleOAuthInput, sessionInfo: $sessionInfo) { redirectURL token { accessToken refreshToken } } } ``` `registerWithAppleOAuthInput`: `RegisterWithAppleOAuthInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | idToken | `String!` | ใช่ | | | redirectURL | `String` | | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginAccessType` --- #### linkAccountWithAppleOAuth ผูกบัญชี Apple เข้ากับบัญชีที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation LinkAccountWithAppleOAuth($linkAccountWithAppleOAuthInput: LinkAccountWithAppleOAuthInput) { linkAccountWithAppleOAuth(linkAccountWithAppleOAuthInput: $linkAccountWithAppleOAuthInput) { status } } ``` `linkAccountWithAppleOAuthInput`: `LinkAccountWithAppleOAuthInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | idToken | `String!` | ใช่ | | Response: `LinkAccountWithAppleOAuthStatus` --- #### unlinkAccountFromAppleOAuth ยกเลิกการผูกบัญชี Apple ออกจากบัญชีที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation UnlinkAccountFromAppleOAuth($unlinkAccountFromAppleOAuthInput: UnlinkAccountFromAppleOAuthInput) { unlinkAccountFromAppleOAuth(unlinkAccountFromAppleOAuthInput: $unlinkAccountFromAppleOAuthInput) { status } } ``` `unlinkAccountFromAppleOAuthInput`: `UnlinkAccountFromAppleOAuthInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | authId | `String` | | | Response: `UnlinkAccountFromAppleOAuthStatus` --- #### loginWithUserNameAccessType login ด้วย username + password แล้วได้ token กลับทันที - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation LoginWithUserNameAccessType($loginWithUsernameAccessTypeInput: LoginWithUsernameAccessTypeInput, $sessionInfo: SessionInfoInput) { loginWithUserNameAccessType(loginWithUsernameAccessTypeInput: $loginWithUsernameAccessTypeInput, sessionInfo: $sessionInfo) { redirectURL token { accessToken refreshToken } } } ``` `loginWithUsernameAccessTypeInput`: `LoginWithUsernameAccessTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | username | `String!` | ใช่ | | | password | `String!` | ใช่ | | | redirectURL | `String` | | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginAccessType` --- #### loginWithEmailAccessType login ด้วย email + password แล้วได้ token กลับทันที - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation LoginWithEmailAccessType($loginWithEmailAccessTypeInput: LoginWithEmailAccessTypeInput, $sessionInfo: SessionInfoInput) { loginWithEmailAccessType(loginWithEmailAccessTypeInput: $loginWithEmailAccessTypeInput, sessionInfo: $sessionInfo) { redirectURL token { accessToken refreshToken } } } ``` `loginWithEmailAccessTypeInput`: `LoginWithEmailAccessTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | email | `String!` | ใช่ | | | password | `String!` | ใช่ | | | redirectURL | `String` | | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginAccessType` --- #### loginWithPhoneNumberAccessType login ด้วยเบอร์โทรศัพท์ + password แล้วได้ token กลับทันที - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation LoginWithPhoneNumberAccessType($loginWithPhoneNumberAccessTypeInput: LoginWithPhoneNumberAccessTypeInput, $sessionInfo: SessionInfoInput) { loginWithPhoneNumberAccessType(loginWithPhoneNumberAccessTypeInput: $loginWithPhoneNumberAccessTypeInput, sessionInfo: $sessionInfo) { redirectURL token { accessToken refreshToken } } } ``` `loginWithPhoneNumberAccessTypeInput`: `LoginWithPhoneNumberAccessTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | countryCode | `String!` | ใช่ | | | phoneNumber | `String!` | ใช่ | | | password | `String!` | ใช่ | | | redirectURL | `String` | | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginAccessType` --- #### loginWithUserNameSocketType login ด้วย username + password แบบ WebSocket: token จะถูกส่งเข้า socket ที่ได้จาก `getLoginSocketId` - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation LoginWithUserNameSocketType($loginWithUsernameSocketTypeInput: LoginWithUsernameSocketTypeInput, $sessionInfo: SessionInfoInput) { loginWithUserNameSocketType(loginWithUsernameSocketTypeInput: $loginWithUsernameSocketTypeInput, sessionInfo: $sessionInfo) { status } } ``` `loginWithUsernameSocketTypeInput`: `LoginWithUsernameSocketTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | username | `String!` | ใช่ | | | password | `String!` | ใช่ | | | socketId | `String!` | ใช่ | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginSocketType!` --- #### loginWithEmailSocketType login ด้วย email + password แบบ WebSocket - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation LoginWithEmailSocketType($loginWithEmailSocketTypeInput: LoginWithEmailSocketTypeInput, $sessionInfo: SessionInfoInput) { loginWithEmailSocketType(loginWithEmailSocketTypeInput: $loginWithEmailSocketTypeInput, sessionInfo: $sessionInfo) { status } } ``` `loginWithEmailSocketTypeInput`: `LoginWithEmailSocketTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | email | `String!` | ใช่ | | | password | `String!` | ใช่ | | | socketId | `String!` | ใช่ | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginSocketType!` --- #### loginWithPhoneNumberSocketType login ด้วยเบอร์โทรศัพท์ + password แบบ WebSocket - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation LoginWithPhoneNumberSocketType($loginWithPhoneNumberSocketType: LoginWithPhoneNumberSocketTypeInput, $sessionInfo: SessionInfoInput) { loginWithPhoneNumberSocketType(loginWithPhoneNumberSocketType: $loginWithPhoneNumberSocketType, sessionInfo: $sessionInfo) { status } } ``` `loginWithPhoneNumberSocketType`: `LoginWithPhoneNumberSocketTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | countryCode | `String!` | ใช่ | | | phoneNumber | `String!` | ใช่ | | | password | `String!` | ใช่ | | | socketId | `String!` | ใช่ | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `LoginSocketType!` --- #### loginWithEmailOtpType ขอ OTP ทาง email เพื่อ login · ได้ `otpRef` กลับ แล้วนำไปยืนยันด้วย `verifierOtp` - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation LoginWithEmailOtpType($loginWithEmailOtpTypeInput: LoginWithEmailOtpTypeInput) { loginWithEmailOtpType(loginWithEmailOtpTypeInput: $loginWithEmailOtpTypeInput) { redirectURL otpRef } } ``` `loginWithEmailOtpTypeInput`: `LoginWithEmailOtpTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | email | `String!` | ใช่ | | | socketId | `String!` | ใช่ | | Response: `LoginOtpType!` --- #### loginWithPhoneNumberOtpType ขอ OTP ทาง SMS เพื่อ login · ได้ `otpRef` กลับ แล้วนำไปยืนยันด้วย `verifierOtp` - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation LoginWithPhoneNumberOtpType($loginWithPhoneNumberOtpTypeInput: LoginWithPhoneNumberOtpTypeInput) { loginWithPhoneNumberOtpType(loginWithPhoneNumberOtpTypeInput: $loginWithPhoneNumberOtpTypeInput) { redirectURL otpRef } } ``` `loginWithPhoneNumberOtpTypeInput`: `LoginWithPhoneNumberOtpTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | countryCode | `String!` | ใช่ | | | phoneNumber | `String!` | ใช่ | | | socketId | `String!` | ใช่ | | Response: `LoginOtpType!` --- #### logout ออกจากระบบ session ปัจจุบัน - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation Logout { logout { status } } ``` Response: `LogoutStatus` --- #### logoutAll ออกจากระบบทุก session ของผู้ใช้ที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation LogoutAll { logoutAll { status } } ``` Response: `LogoutStatus` --- #### logoutAllByAuthId ออกจากระบบทุก Session ตาม Auth ID - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `logoutAllByAuthId` ```graphql mutation LogoutAllByAuthId($authId: String!) { logoutAllByAuthId(authId: $authId) { status } } ``` | argument | Type | | --- | --- | | authId | `String!` | Response: `LogoutStatus` --- #### refreshToken ขอ accessToken / refreshToken ชุดใหม่ด้วย refreshToken (refreshToken เดิมใช้ซ้ำไม่ได้) - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation RefreshToken($refreshTokenInput: RefreshTokenInput) { refreshToken(refreshTokenInput: $refreshTokenInput) { accessToken refreshToken } } ``` `refreshTokenInput`: `RefreshTokenInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | refreshToken | `String!` | ใช่ | | Response: `Token` --- #### changePassword เปลี่ยนรหัสผ่านของผู้ใช้ที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation ChangePassword($changePasswordInput: ChangePasswordInput) { changePassword(changePasswordInput: $changePasswordInput) { status } } ``` `changePasswordInput`: `ChangePasswordInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | currentPassword | `String!` | ใช่ | | | newPassword | `String!` | ใช่ | | | confirmNewPassword | `String!` | ใช่ | | Response: `ChangePasswordStatus` --- #### resetPassword ตั้งรหัสผ่านใหม่ให้บัญชี (ผู้ดูแล) - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation ResetPassword($resetPasswordInput: ResetPasswordInput) { resetPassword(resetPasswordInput: $resetPasswordInput) { status } } ``` `resetPasswordInput`: `ResetPasswordInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | authId | `String!` | ใช่ | | | newPassword | `String!` | ใช่ | | | confirmNewPassword | `String!` | ใช่ | | Response: `ResetPasswordStatus` --- #### resetPasswordWithEmail ขอ OTP ทาง email เพื่อรีเซ็ตรหัสผ่าน - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation ResetPasswordWithEmail($resetPasswordWithEmailInput: ResetPasswordWithEmailInput) { resetPasswordWithEmail(resetPasswordWithEmailInput: $resetPasswordWithEmailInput) { otpRef } } ``` `resetPasswordWithEmailInput`: `ResetPasswordWithEmailInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | email | `String!` | ใช่ | | Response: `ResetPasswordOtp` --- #### resetPasswordWithPhoneNumber ขอ OTP ทาง SMS เพื่อรีเซ็ตรหัสผ่าน - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation ResetPasswordWithPhoneNumber($resetPasswordWithPhoneNumberInput: ResetPasswordWithPhoneNumberInput) { resetPasswordWithPhoneNumber(resetPasswordWithPhoneNumberInput: $resetPasswordWithPhoneNumberInput) { otpRef } } ``` `resetPasswordWithPhoneNumberInput`: `ResetPasswordWithPhoneNumberInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | countryCode | `String!` | ใช่ | | | phoneNumber | `String!` | ใช่ | | Response: `ResetPasswordOtp` --- #### revokeToken ยกเลิก token ของผู้ใช้ที่ระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation RevokeToken($revokeTokenInput: RevokeTokenInput) { revokeToken(revokeTokenInput: $revokeTokenInput) { status } } ``` `revokeTokenInput`: `RevokeTokenInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | authId | `String!` | ใช่ | | Response: `RevokeTokenStatus` --- #### unlockAccountByAuthId ปลดล็อกบัญชีที่ถูกล็อกตาม authId - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getLockAccountsByAppKey` ```graphql mutation UnlockAccountByAuthId($appKey: String!, $authId: String!) { unlockAccountByAuthId(appKey: $appKey, authId: $authId) { status } } ``` | argument | Type | | --- | --- | | appKey | `String!` | | authId | `String!` | Response: `DeleteAccountStatus` --- #### deleteAccount ลบบัญชี - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation DeleteAccount($deleteAccountInput: DeleteAccountInput) { deleteAccount(deleteAccountInput: $deleteAccountInput) { status } } ``` `deleteAccountInput`: `DeleteAccountInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | authId | `String!` | ใช่ | | Response: `DeleteAccountStatus` --- #### verifierOtp ยืนยัน OTP ที่ได้จาก `loginWith*OtpType` หรือ `resetPasswordWith*` · กรณี login ได้ token กลับ - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation VerifierOtp($verifierOtpInput: VerifierOtpInput, $sessionInfo: SessionInfoInput) { verifierOtp(verifierOtpInput: $verifierOtpInput, sessionInfo: $sessionInfo) { redirectURL token { accessToken refreshToken } otpVerifierRef status } } ``` `verifierOtpInput`: `VerifierOtpInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | otpRef | `String!` | ใช่ | | | otpCode | `String!` | ใช่ | | `sessionInfo`: `SessionInfoInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | host | `String` | | | | ipAddress | `String` | | | | userAgent | `String` | | | Response: `VerifierOtp` --- #### resetMyPasswordByOtpVerifierRef ตั้งรหัสผ่านใหม่หลังยืนยัน OTP สำเร็จ - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql mutation ResetMyPasswordByOtpVerifierRef($resetPasswordOtpInput: ResetPasswordOtpInput) { resetMyPasswordByOtpVerifierRef(resetPasswordOtpInput: $resetPasswordOtpInput) { status } } ``` `resetPasswordOtpInput`: `ResetPasswordOtpInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | otpVerifierRef | `String!` | ใช่ | | | newPassword | `String!` | ใช่ | | | confirmNewPassword | `String!` | ใช่ | | Response: `ResetPasswordStatus` --- #### syncMobileDevice ลงทะเบียน/อัปเดตอุปกรณ์ของผู้ใช้ (เรียกหลัง login) — upsert ตาม installationId + user ที่ login - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation SyncMobileDevice($input: SyncMobileDeviceInput!) { syncMobileDevice(input: $input) { _id appKey installationId userId platform deviceName appVersion pushProvider pushEnabled pushPermissionStatus # ... } } ``` `input`: `SyncMobileDeviceInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | installationId | `String!` | ใช่ | installationId ที่ mobile gen | | platform | `EnumMobilePlatform!` | ใช่ | แพลตฟอร์มของเครื่อง | | deviceName | `String` | | ชื่อเครื่อง | | appVersion | `String` | | เวอร์ชันแอป | Response: `UserMobileDevice!` --- #### registerPushToken ลงทะเบียน Expo push token ให้เครื่องนี้ (เรียกหลังได้ token จาก OS) — ย้าย token ออกจากเครื่องอื่นที่ถืออยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation RegisterPushToken($input: RegisterPushTokenInput!) { registerPushToken(input: $input) { _id appKey installationId userId platform deviceName appVersion pushProvider pushEnabled pushPermissionStatus # ... } } ``` `input`: `RegisterPushTokenInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | installationId | `String!` | ใช่ | installationId ที่ mobile gen | | pushToken | `String!` | ใช่ | Expo push token | | pushProvider | `EnumPushProvider` | | ผู้ให้บริการ push | | pushPermissionStatus | `EnumPushPermissionStatus` | | สถานะสิทธิ์การแจ้งเตือนจาก OS | | pushLanguage | `String` | | ภาษาของการแจ้งเตือน | Response: `UserMobileDevice!` --- #### updateMobileDeviceSetting อัปเดตการตั้งค่าการแจ้งเตือนของเครื่อง (เปิด/ปิด, ภาษา, ระดับความสำคัญ) - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation UpdateMobileDeviceSetting($input: UpdateMobileDeviceSettingInput!) { updateMobileDeviceSetting(input: $input) { _id appKey installationId userId platform deviceName appVersion pushProvider pushEnabled pushPermissionStatus # ... } } ``` `input`: `UpdateMobileDeviceSettingInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | installationId | `String!` | ใช่ | installationId ที่ mobile gen | | pushEnabled | `Boolean` | | เปิด/ปิดการแจ้งเตือน | | pushLanguage | `String` | | ภาษาของการแจ้งเตือน | | minimumSeverity | `EnumPushSeverity` | | ระดับความสำคัญขั้นต่ำที่จะรับการแจ้งเตือน | | pushPermissionStatus | `EnumPushPermissionStatus` | | สถานะสิทธิ์การแจ้งเตือนจาก OS (กรณีผู้ใช้เปลี่ยนใน setting ของเครื่อง) | Response: `UserMobileDevice!` --- #### deactivateMobileDevice ปิดการใช้งานเครื่องนี้ (เรียกตอน logout) — set inactive + ปิด push (ไม่ลบ document) - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation DeactivateMobileDevice($input: DeactivateMobileDeviceInput!) { deactivateMobileDevice(input: $input) { _id appKey installationId userId platform deviceName appVersion pushProvider pushEnabled pushPermissionStatus # ... } } ``` `input`: `DeactivateMobileDeviceInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | installationId | `String!` | ใช่ | installationId ที่ mobile gen | Response: `UserMobileDevice!` --- #### removeMyMobileDevice ลบอุปกรณ์ของตัวเองออกถาวร (hard delete) — สำหรับเครื่องที่ไม่ใช้แล้ว - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation RemoveMyMobileDevice($installationId: String!) { removeMyMobileDevice(installationId: $installationId) { _id appKey installationId userId platform deviceName appVersion pushProvider pushEnabled pushPermissionStatus # ... } } ``` | argument | Type | | --- | --- | | installationId | `String!` | Response: `UserMobileDevice!` ---

## Kafka consume Reference ทุกข้อความมี header `appKey` และ `serviceKey` (service ปลายทาง) · payload อยู่ในรูป `{ <ข้อมูล>: {...}, action: "ADD" | "REMOVE" | ... }` · ข้อความของแอปที่ service นี้ไม่มี AppCertificate จะถูกข้าม --- ### init-system ตั้งระบบจากศูนย์ (เฉพาะ core set) — สร้างแอป SYSTEM, AppCredential เริ่มต้น และบัญชีผู้ดูแลระบบ topic: init-system --- ### refresh-data core สั่งให้ส่งข้อมูลที่ตัวเองถืออยู่ขึ้น Kafka ใหม่ (ใช้ตอนมี service ใหม่เข้าแอป) และล้าง cache ของตัวเอง · รับเฉพาะข้อความที่ header `serviceKey` = `authentication` topic: refresh-data เมื่อได้รับจะส่ง `sync-app-credential` ของแอปนั้นให้ทุก service และส่ง `sync-permission` ของตัวเองให้ ACL --- ### sync-app-certificate รับ AppCertificate (กุญแจของ service ต่อแอป) จาก core — บอกว่า service ใดอยู่ในแอปใด topic: sync-app-certificate --- ### sync-service-setting / sync-app-service-setting รับค่าตั้งค่าเพิ่มเติมแบบ JSON ของ service นี้ ทั้งระบบ (`sync-service-setting`) และรายแอป (`sync-app-service-setting`) จาก core · ค่า OAuth ของ Google / Facebook / Apple ตั้งผ่าน `sync-app-service-setting` (ดู [วิธี login ที่มี](#login-methods)) topic: sync-service-setting topic: sync-app-service-setting --- ### sync-application รับข้อมูลแอปจาก Application Service topic: sync-application --- ### sync-user-policy รับ UserPolicy ที่ ACL คอมไพล์แล้ว เฉพาะ permission ของ `authentication` ใช้ตรวจสิทธิ์ตอนเรียก API topic: sync-user-policy | key | Type | คำอธิบาย | | --- | --- | --- | | userPolicy.userPolicyKey | string | `authentication:::::` หรือ `authentication:::::` | | userPolicy.permissionKey | string | permission | | userPolicy.authId | string | ผู้ใช้ | | userPolicy.organizationId | string | องค์กร (ถ้าเป็นสิทธิ์ระดับองค์กร) | | action | string | `ADD`, `REMOVE`, `REMOVE_APP`, `REMOVE_PERMISSION`, `REMOVE_USER`, `REMOVE_ORGANIZATION` | --- ### sync-organization รับข้อมูลองค์กรจาก [Unit Service](unitService.md) (ใช้กับ default organization ของบัญชี) topic: sync-organization --- ### sync-invite-code รับข้อมูล invite code จาก [ACL Service](aclService.md) ใช้ตอนสมัครด้วย `inviteCodeKey` หรือ `useInviteCode` topic: sync-invite-code | key | Type | คำอธิบาย | | --- | --- | --- | | inviteCode.id | string | id | | inviteCode.inviteCodeKey | string | รหัสเชิญ | | inviteCode.defaultOrganizationId / defaultOrganizationKey | string | องค์กรเริ่มต้นที่จะตั้งให้ผู้ใช้ | | inviteCode.maxUses / currentUses | number | จำนวนครั้งที่ใช้ได้ / ใช้ไปแล้ว | | inviteCode.validStartTime / validEndTime | Date | ช่วงเวลาที่ใช้ได้ | | inviteCode.appRoleIds / orgRoleIds | string[] | role ที่จะได้รับ | | inviteCode.isActive | boolean | เปิดใช้งาน | | action | string | `ADD`, `REMOVE` | --- ## Kafka Produce Reference --- ### sync-auth ส่งข้อมูลบัญชีเมื่อมีการสมัคร (รวมสมัครผ่าน OAuth), ตั้ง default organization หรือลบบัญชี — **กระจายถึงทุก service ในแอป** service ไหนต้องใช้ชื่อ / อีเมล / เบอร์โทรของผู้ใช้ (เช่น ส่ง SMS) รับ topic นี้แล้วเก็บสำเนาไว้ได้เลย · ผู้รับปัจจุบัน: Profile, ACL topic: sync-auth header: appKey (ไม่มี serviceKey = กระจายทั้งแอป · ผู้รับห้ามกรองด้วย serviceKey) | key | Type | คำอธิบาย | | --- | --- | --- | | account.appKey | string | appKey | | account.authId | string | id ของบัญชี | | account.username | string | username | | account.emails / phoneNumber | array | email / เบอร์โทรของบัญชี | | account.roleKey | string | `ADMIN`, `NONE` | | account.inviteCodeId / inviteCodeKey | string | invite code ที่ใช้สมัคร (ถ้ามี) | | account.userType | string | ประเภทผู้ใช้ | | account.defaultOrganizationId / defaultOrganizationKey | string | องค์กรเริ่มต้น | | account.firstName, middleName, lastName, displayName, gender, profileImage, electronicSignatureKey | string | ข้อมูลโปรไฟล์เริ่มต้น | | action | string | `ADD`, `REMOVE` | > ค่า message ของ topic นี้ถูกห่อเป็น `{ "value": "" }` ผู้รับต้อง `JSON.parse` ค่า `value` อีกชั้นเพื่อได้ object ข้างบน --- ### sync-app-credential กระจาย AppCredential ของแอปไปให้ทุก service ที่อยู่ในแอปนั้น (ส่งแยกทีละ service, header `serviceKey` = service ปลายทาง) เพื่อให้แต่ละ service ตรวจ token และ header ของผู้เรียกได้เอง · ข้อมูลลับในข้อความถูกเข้ารหัสด้วยกุญแจของ service ปลายทาง topic: sync-app-credential | key | Type | คำอธิบาย | | --- | --- | --- | | appCredential.clientId | string | clientId | | appCredential.appCredentialType | string | `USER`, `SYSTEM` | | appCredential.authorizedHosts / authorizedRedirectUrls | array | host / redirect URL ที่อนุญาต | | appCredential.jwtAccessExpireTime / jwtRefreshExpireTime | number | อายุ token (วินาที) | | appCredential.numberOfFail / minutesTimeFail / minutesTimeLock | number | กฎการล็อกบัญชี | | appCredential.expiryAt | Date | วันหมดอายุของ AppCredential | | appCredential.minimumAppVersion, appUpdateUrl*, forceUpdate | | การบังคับอัปเดตแอปมือถือ | | appCredential.(กุญแจตรวจ token / clientSecret) | string | เข้ารหัสถึง service ปลายทาง | | action | string | `ADD`, `REMOVE` | --- ### create-notification ส่งคำขอแจ้งเตือนไปที่ Notification Service เช่น OTP ทาง SMS หรือ email topic: create-notification | key | Type | คำอธิบาย | | --- | --- | --- | | notification.isSchedule | boolean | `false` = ส่งทันที | | notification.sms | object | `msisdn`, `message`, `sender`, `expire` | | notification.email | object | `to`, `subject`, `text` | --- ### sync-mobile-device ส่งข้อมูลอุปกรณ์มือถือ / push token ที่เปลี่ยนแปลงไปให้ Notification Service topic: sync-mobile-device | key | Type | คำอธิบาย | | --- | --- | --- | | mobileDevice.appKey | string | appKey | | mobileDevice.installationId | string | id ของการติดตั้งแอปบนเครื่อง | | mobileDevice.userId | string | ผู้ใช้ | | mobileDevice.platform | string | แพลตฟอร์ม | | mobileDevice.deviceName | string | ชื่อเครื่อง | | mobileDevice.appVersion | string | เวอร์ชันแอป | | mobileDevice.pushToken / pushProvider / pushEnabled / pushPermissionStatus | | push token และสถานะการแจ้งเตือน | | mobileDevice.pushLanguage / minimumSeverity | string | ภาษา / ระดับความสำคัญขั้นต่ำที่ต้องการรับ | | mobileDevice.status | string | สถานะเครื่อง | | action | string | `ADD`, `REMOVE` | --- ### sync-permission ส่ง permission ทั้งหมดของ `authentication` ให้ [ACL Service](aclService.md) (ส่งตอนได้ `refresh-data`) topic: sync-permission --- > อัปเดตจากโค้ด gumon-authentication-service@833b687 · 2026-10-05 --- # ACL Service Service สำหรับจัดการสิทธิ์ (Access Control) ของทั้งระบบ serviceKey: access-control หน้าที่หลัก - เป็นทะเบียน **Permission** ของทุก service (แต่ละ service ส่ง permission ของตัวเองมาทาง topic `sync-permission`) - ผูก Permission และ **Custom Menu** เข้ากับ role 3 ระดับ: `appRole` (ทั้งแอป), `organizationRole` (ต่อองค์กร) และ `defaultOrganizationRole` (แม่แบบ role ที่ทุกองค์กรใหม่จะได้) - ผูกผู้ใช้เข้ากับ role (`addAppRoleToUser`, `addOrganizationRoleToUser`) และจัดการ **Invite Code** (รหัสเชิญที่ให้ role / องค์กรเริ่มต้นอัตโนมัติตอนสมัคร) - คอมไพล์สิทธิ์ของผู้ใช้เป็น **UserPolicy** แล้วส่งให้ service เจ้าของ permission ทาง topic `sync-user-policy` ตัว role (สร้าง / แก้ / ลบ appRole, organizationRole, defaultOrganizationRole) นิยามอยู่ที่ [Unit Service](unitService.md) แล้ว ACL รับสำเนามาทาง Kafka เพื่อผูก permission / เมนู / ผู้ใช้
- [ลำดับการทำงานของสิทธิ์](#permission-flow) - [API Reference](#api-reference) - [kafka consume Reference](#kafka-consume-reference) - [Kafka Produce Reference](#kafka-produce-reference) --- ## ลำดับการทำงานของสิทธิ์ **1. service ประกาศ permission ของตัวเองให้ ACL** topic: sync-permission | key | Type | คำอธิบาย | | --- | --- | --- | | permission.serviceKey | string | serviceKey ของ service เจ้าของ permission | | permission.permissionKey | string | key ของ permission (ไม่ซ้ำภายใน service) | | permission.title / description | string | ชื่อและคำอธิบาย | | permission.isSystem | boolean | permission ระดับระบบ | | permission.isGenerateApplication | boolean | สร้าง UserPolicy ระดับแอป | | permission.isGenerateOrganization | boolean | สร้าง UserPolicy ระดับองค์กร | | permission.isActive | boolean | `false` = ปิด permission นี้ (ลบการผูกและ UserPolicy ที่เกี่ยวข้อง) | | action | string | `ADD`, `REMOVE` | permission เป็นของระบบ ไม่ผูกกับแอป (อ้างอิงด้วยคู่ `serviceKey` + `permissionKey`) · header `serviceKey` ของข้อความนี้ = `access-control` **2. ผูก permission / เมนู / ผู้ใช้ เข้ากับ role** ผู้ดูแลผูก permission และเมนูเข้ากับ role และผูกผู้ใช้เข้ากับ role ผ่าน API ด้านล่าง (หรือผ่าน invite code ตอนสมัคร / topic `set-user-role`) **3. ACL คำนวณ UserPolicy แล้วส่งให้ service เจ้าของ permission** ถ้า organizationRole เปิด `isChildOrganizationAccess` สิทธิ์จะแตกลงไปถึงองค์กรลูกด้วย · ส่งทาง `sync-user-policy` โดย header `serviceKey` = service ปลายทาง **4. service ปลายทางตรวจสิทธิ์เองจากข้อมูลที่ถืออยู่** ตอนรับ request service สร้าง key แล้วค้นในฐานข้อมูล / cache ของตัวเอง ::::: (ระดับแอป) ::::: (ระดับองค์กร) ถ้าข้อมูล UserPolicy ของแอปต้องคำนวณใหม่ทั้งหมด ใช้ mutation `reCalculateUserPolicy` ผู้ที่อยู่ใน appRole ที่เป็น admin ของแอป (`isAdmin`) จะได้ทุก permission และทุกเมนูของแอปโดยอัตโนมัติ ---

## API Reference การยืนยันตัวตนและ header ดูที่ [Authentication Service](authenticationService.md#app-credential) · "สิทธิ์" คือ permissionKey ของ service `access-control` --- ### Query เป็น API ที่ใช้สำหรับการ Query ข้อมูลออกมา ไม่มีการแก้ไข Data --- #### getAppRolesCustomMenus ดึงข้อมูล getAppRolesCustomMenus ระบุ appKey อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getAppRolesCustomMenus` ```graphql query GetAppRolesCustomMenus($input: GetAppRolesCustomMenuInput) { getAppRolesCustomMenus(input: $input) { appRolesCustomMenus { _id appKey customMenuId customMenuKey customMenuPath appRoleId } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetAppRolesCustomMenuInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetAppRolesCustomMenuFilterInput` | | | | search | `GetAppRolesCustomMenuSearchInput` | | | | sort | `GetAppRolesCustomMenuSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `AppRolesCustomMenuPagination` --- #### getAppRolesCustomMenuById ดึงข้อมูล getAppRolesCustomMenu ตาม ID ถ้าอยากค้นหาข้าม app อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getAppRolesCustomMenuById` ```graphql query GetAppRolesCustomMenuById($appRolesCustomMenuId: String!) { getAppRolesCustomMenuById(appRolesCustomMenuId: $appRolesCustomMenuId) { _id appKey customMenuId customMenuKey customMenuPath customMenu { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } priority # ... } } ``` | argument | Type | | --- | --- | | appRolesCustomMenuId | `String!` | Response: `AppRolesCustomMenu` --- #### getAppRolesPermissions ดึงข้อมูล getAppRolesPermissions ระบุ appKey อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getAppRolesPermissions` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetAppRolesPermissions($input: GetAppRolePermissionInput) { getAppRolesPermissions(input: $input) { appRolePermissions { _id appKey permissionId permissionKey appRoleId appRoleKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetAppRolePermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetAppRolePermissionFilterInput` | | | | search | `GetAppRolePermissionSearchInput` | | | | sort | `GetAppRolePermissionSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `AppRolePermissionPagination!` --- #### getAppRolesPermissionById ดึงข้อมูล getAppRolesPermissions ตาม ID ถ้าอยากค้นหาข้าม app อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getAppRolesPermissionByiD` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetAppRolesPermissionById($appRolesPermissionId: ID!) { getAppRolesPermissionById(appRolesPermissionId: $appRolesPermissionId) { _id appKey permissionId permissionKey permission { _id serviceKey permissionKey title description systemNote } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } systemNote isActive # ... } } ``` | argument | Type | | --- | --- | | appRolesPermissionId | `ID!` | Response: `AppRolePermission!` --- #### getCustomMenus ดึงข้อมูล customMenu ทั้งหมดที่มีในระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomMenus` ```graphql query GetCustomMenus($getInput: GetCustomMenuInput) { getCustomMenus(getInput: $getInput) { customMenus { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetCustomMenuInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetCustomMenuFilterInput` | | | | search | `GetCustomMenuSearchInput` | | | | sort | `GetCustomMenuSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `CustomMenuPagination` --- #### getCustomMenuById ดึงข้อมูล customMenu ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomMenuById` ```graphql query GetCustomMenuById($customMenuId: String!) { getCustomMenuById(customMenuId: $customMenuId) { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath level title multilingualTitle description # ... } } ``` | argument | Type | | --- | --- | | customMenuId | `String!` | Response: `CustomMenu` --- #### getCustomMenuByKey ดึงข้อมูล customMenu ตาม customMenuKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomMenuByKey` ```graphql query GetCustomMenuByKey($customMenuKey: String!) { getCustomMenuByKey(customMenuKey: $customMenuKey) { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath level title multilingualTitle description # ... } } ``` | argument | Type | | --- | --- | | customMenuKey | `String!` | Response: `CustomMenu` --- #### getDefaultOrganizationRoleCustomMenus ดึงข้อมูล DefaultOrganizationRoleCustomMenu ทั้งหมด สามารถระบุ appKey หรือ filter อื่น ๆ ได้ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getDefaultOrganizationRoleCustomMenus` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetDefaultOrganizationRoleCustomMenus($input: GetDefaultOrganizationRoleCustomMenuInput) { getDefaultOrganizationRoleCustomMenus(input: $input) { defaultOrganizationRoleCustomMenus { _id appKey customMenuId customMenuKey defaultOrganizationRoleId defaultOrganizationRoleKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetDefaultOrganizationRoleCustomMenuInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetDefaultOrganizationRoleCustomMenuFilterInput` | | | | search | `GetDefaultOrganizationRoleCustomMenuSearchInput` | | | | sort | `GetDefaultOrganizationRoleCustomMenuSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `DefaultOrganizationRoleCustomMenuPagination!` --- #### getDefaultOrganizationRoleCustomMenuById ดึงข้อมูล DefaultOrganizationRoleCustomMenu ตาม ID - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getDefaultOrganizationRoleCustomMenuById` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetDefaultOrganizationRoleCustomMenuById($defaultOrganizationRoleCustomMenuId: ID!) { getDefaultOrganizationRoleCustomMenuById(defaultOrganizationRoleCustomMenuId: $defaultOrganizationRoleCustomMenuId) { _id appKey customMenuId customMenuKey customMenu { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } defaultOrganizationRoleId defaultOrganizationRoleKey defaultOrganizationRole { _id appKey defaultOrganizationRoleKey title subTitle description } systemNote priority # ... } } ``` | argument | Type | | --- | --- | | defaultOrganizationRoleCustomMenuId | `ID!` | Response: `DefaultOrganizationRoleCustomMenu!` --- #### getDefaultOrganizationRolePermissions ดึงข้อมูล getDefaultOrganizationRolePermissions ระบุ appKey อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getDefaultOrganizationRolePermissions` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetDefaultOrganizationRolePermissions($input: GetDefaultOrgRolePermissionInput) { getDefaultOrganizationRolePermissions(input: $input) { defaultOrgRolePermissions { _id appKey permissionId permissionKey defaultOrganizationRoleId defaultOrganizationRoleKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetDefaultOrgRolePermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetDefaultOrgRolePermissionFilterInput` | | | | search | `GetDefaultOrgRolePermissionSearchInput` | | | | sort | `GetDefaultOrgRolePermissionSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `DefaultOrgRolePermissionPagination!` --- #### getDefaultOrganizationRolePermissionById ดึงข้อมูล getDefaultOrganizationRolePermissions ตาม ID ถ้าอยากค้นหาข้าม app อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getDefaultOrganizationRolePermissionById` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetDefaultOrganizationRolePermissionById($defaultOrgRolePermissionId: ID!) { getDefaultOrganizationRolePermissionById(defaultOrgRolePermissionId: $defaultOrgRolePermissionId) { _id appKey permissionId permissionKey permission { _id serviceKey permissionKey title description systemNote } defaultOrganizationRoleId defaultOrganizationRoleKey defaultOrganizationRole { _id appKey defaultOrganizationRoleKey title subTitle description } systemNote isActive # ... } } ``` | argument | Type | | --- | --- | | defaultOrgRolePermissionId | `ID!` | Response: `DefaultOrgRolePermission!` --- #### getInviteCodeAppRoles ดึงข้อมูล Invite Code App Role ทั้งหมดของ app - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeAppRoles` ```graphql query GetInviteCodeAppRoles($getInput: GetInviteCodeAppRoleInput) { getInviteCodeAppRoles(getInput: $getInput) { inviteCodeAppRoles { _id inviteCodeAppRoleKey inviteCodeId inviteCodeKey appRoleId appRoleKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetInviteCodeAppRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetInviteCodeAppRoleFilterInput` | | | | search | `GetInviteCodeAppRoleSearchInput` | | | | sort | `GetInviteCodeAppRoleSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `InviteCodeAppRolePagination` --- #### getInviteCodeAppRolesByInviteCodeKey ดึงข้อมูล Invite Code App Role โดย invite code key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeAppRolesByInviteCodeKey` ```graphql query GetInviteCodeAppRolesByInviteCodeKey($inviteCodeKey: String!, $getInput: GetInviteCodeAppRoleByInviteCodeKeyInput) { getInviteCodeAppRolesByInviteCodeKey(inviteCodeKey: $inviteCodeKey, getInput: $getInput) { inviteCodeAppRoles { _id inviteCodeAppRoleKey inviteCodeId inviteCodeKey appRoleId appRoleKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetInviteCodeAppRoleByInviteCodeKeyInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetInviteCodeAppRoleByInviteCodeKeyFilterInput` | | | | search | `GetInviteCodeAppRoleByInviteCodeKeySearchInput` | | | | sort | `GetInviteCodeAppRoleSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `inviteCodeKey: String!` Response: `InviteCodeAppRolePagination` --- #### getInviteCodeAppRolesByAppRoleKey ดึงข้อมูล Invite Code App Role โดย app role key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeAppRolesByAppRoleKey` ```graphql query GetInviteCodeAppRolesByAppRoleKey($appRoleKey: String!, $getInput: GetInviteCodeAppRoleByAppRoleKeyInput) { getInviteCodeAppRolesByAppRoleKey(appRoleKey: $appRoleKey, getInput: $getInput) { inviteCodeAppRoles { _id inviteCodeAppRoleKey inviteCodeId inviteCodeKey appRoleId appRoleKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetInviteCodeAppRoleByAppRoleKeyInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetInviteCodeAppRoleByAppRoleKeyFilterInput` | | | | search | `GetInviteCodeAppRoleByAppRoleKeySearchInput` | | | | sort | `GetInviteCodeAppRoleSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `appRoleKey: String!` Response: `InviteCodeAppRolePagination` --- #### getInviteCodeAppRoleById ดึงข้อมูล Invite Code App Role โดย _id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeAppRoleById` ```graphql query GetInviteCodeAppRoleById($id: String!) { getInviteCodeAppRoleById(id: $id) { _id inviteCodeAppRoleKey inviteCodeId inviteCodeKey inviteCode { _id inviteCodeKey title subTitle description systemNote } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } createdBy updatedBy # ... } } ``` | argument | Type | | --- | --- | | id | `String!` | Response: `InviteCodeAppRole` --- #### getInviteCodeAppRoleByKey ดึงข้อมูล Invite Code App Role โดย key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeAppRoleByKey` ```graphql query GetInviteCodeAppRoleByKey($inviteCodeAppRoleKey: String!) { getInviteCodeAppRoleByKey(inviteCodeAppRoleKey: $inviteCodeAppRoleKey) { _id inviteCodeAppRoleKey inviteCodeId inviteCodeKey inviteCode { _id inviteCodeKey title subTitle description systemNote } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } createdBy updatedBy # ... } } ``` | argument | Type | | --- | --- | | inviteCodeAppRoleKey | `String!` | Response: `InviteCodeAppRole` --- #### getInviteCodeOrgRoles ดึงข้อมูล Invite Code Org Role ทั้งหมดของ app - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeOrgRoles` ```graphql query GetInviteCodeOrgRoles($getInput: GetInviteCodeOrgRoleInput) { getInviteCodeOrgRoles(getInput: $getInput) { inviteCodeOrgRoles { _id inviteCodeOrgRoleKey inviteCodeId inviteCodeKey orgRoleId orgRoleKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetInviteCodeOrgRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetInviteCodeOrgRoleFilterInput` | | | | search | `GetInviteCodeOrgRoleSearchInput` | | | | sort | `GetInviteCodeOrgRoleSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `InviteCodeOrgRolePagination` --- #### getInviteCodeOrgRolesByInviteCodeKey ดึงข้อมูล Invite Code Org Role โดย invite code key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeOrgRolesByInviteCodeKey` ```graphql query GetInviteCodeOrgRolesByInviteCodeKey($inviteCodeKey: String!, $getInput: GetInviteCodeOrgRoleByInviteCodeKeyInput) { getInviteCodeOrgRolesByInviteCodeKey(inviteCodeKey: $inviteCodeKey, getInput: $getInput) { inviteCodeOrgRoles { _id inviteCodeOrgRoleKey inviteCodeId inviteCodeKey orgRoleId orgRoleKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetInviteCodeOrgRoleByInviteCodeKeyInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetInviteCodeOrgRoleByInviteCodeKeyFilterInput` | | | | search | `GetInviteCodeOrgRoleByInviteCodeKeySearchInput` | | | | sort | `GetInviteCodeOrgRoleSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `inviteCodeKey: String!` Response: `InviteCodeOrgRolePagination` --- #### getInviteCodeOrgRolesByOrgRoleKey ดึงข้อมูล Invite Code Org Role โดย org role key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeOrgRolesByOrgRoleKey` ```graphql query GetInviteCodeOrgRolesByOrgRoleKey($orgRoleKey: String!, $getInput: GetInviteCodeOrgRoleByOrgRoleKeyInput) { getInviteCodeOrgRolesByOrgRoleKey(orgRoleKey: $orgRoleKey, getInput: $getInput) { inviteCodeOrgRoles { _id inviteCodeOrgRoleKey inviteCodeId inviteCodeKey orgRoleId orgRoleKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetInviteCodeOrgRoleByOrgRoleKeyInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetInviteCodeOrgRoleBOrgRoleKeyFilterInput` | | | | search | `GetInviteCodeOrgRoleBOrgRoleKeySearchInput` | | | | sort | `GetInviteCodeOrgRoleSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `orgRoleKey: String!` Response: `InviteCodeOrgRolePagination` --- #### getInviteCodeOrgRoleById ดึงข้อมูล Invite Code Org Role โดย _id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeOrgRoleById` ```graphql query GetInviteCodeOrgRoleById($id: String!) { getInviteCodeOrgRoleById(id: $id) { _id inviteCodeOrgRoleKey inviteCodeId inviteCodeKey inviteCode { _id inviteCodeKey title subTitle description systemNote } orgRoleId orgRoleKey orgRole { _id organizationKey organizationRoleKey title subTitle description } createdBy updatedBy # ... } } ``` | argument | Type | | --- | --- | | id | `String!` | Response: `InviteCodeOrgRole` --- #### getInviteCodeOrgRoleByKey ดึงข้อมูล Invite Code Org Role โดย key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeOrgRoleByKey` ```graphql query GetInviteCodeOrgRoleByKey($inviteCodeOrgRoleKey: String!) { getInviteCodeOrgRoleByKey(inviteCodeOrgRoleKey: $inviteCodeOrgRoleKey) { _id inviteCodeOrgRoleKey inviteCodeId inviteCodeKey inviteCode { _id inviteCodeKey title subTitle description systemNote } orgRoleId orgRoleKey orgRole { _id organizationKey organizationRoleKey title subTitle description } createdBy updatedBy # ... } } ``` | argument | Type | | --- | --- | | inviteCodeOrgRoleKey | `String!` | Response: `InviteCodeOrgRole` --- #### getInviteCodeUsages ดึงข้อมูล Invite Code Usage ทั้งหมดของ app - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeUsages` ```graphql query GetInviteCodeUsages($getInput: GetInviteCodeUsageInput) { getInviteCodeUsages(getInput: $getInput) { inviteCodeUsages { _id authId inviteCodeId inviteCodeKey email countryCode } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetInviteCodeUsageInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetInviteCodeUsageFilterInput` | | | | search | `GetInviteCodeUsageSearchInput` | | | | sort | `GetInviteCodeUsageSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `InviteCodeUsagePagination` --- #### getInviteCodeUsagesByInviteCodeKey ดึงข้อมูล Invite Code Usage โดยใช้ invite code key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeUsagesByInviteCodeKey` ```graphql query GetInviteCodeUsagesByInviteCodeKey($inviteCodeKey: String!, $getInput: GetInviteCodeUsageByInviteCodeKeyInput) { getInviteCodeUsagesByInviteCodeKey(inviteCodeKey: $inviteCodeKey, getInput: $getInput) { inviteCodeUsages { _id authId inviteCodeId inviteCodeKey email countryCode } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetInviteCodeUsageByInviteCodeKeyInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetInviteCodeUsageByInviteCodeKeyFilterInput` | | | | search | `GetInviteCodeUsageByInviteCodeKeySearchInput` | | | | sort | `GetInviteCodeUsageSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `inviteCodeKey: String!` Response: `InviteCodeUsagePagination` --- #### getInviteCodeUsageById ดึงข้อมูล Invite Code Usage โดยใช้ id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeUsageById` ```graphql query GetInviteCodeUsageById($id: String!) { getInviteCodeUsageById(id: $id) { _id authId inviteCodeId inviteCodeKey inviteCode { _id inviteCodeKey title subTitle description systemNote } email countryCode phoneNumber username redirectUrl # ... } } ``` | argument | Type | | --- | --- | | id | `String!` | Response: `InviteCodeUsage` --- #### getInviteCodeUsageByAuthId ดึงข้อมูล Invite Code Usage โดยใช้ authId - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeUsageByAuthId` ```graphql query GetInviteCodeUsageByAuthId($authId: String!) { getInviteCodeUsageByAuthId(authId: $authId) { _id authId inviteCodeId inviteCodeKey inviteCode { _id inviteCodeKey title subTitle description systemNote } email countryCode phoneNumber username redirectUrl # ... } } ``` | argument | Type | | --- | --- | | authId | `String!` | Response: `InviteCodeUsage` --- #### getInviteCodes ดึงข้อมูล Invite Code ทั้งหมดของ app - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodes` ```graphql query GetInviteCodes($getInput: GetInviteCodeInput) { getInviteCodes(getInput: $getInput) { inviteCodes { _id inviteCodeKey title subTitle description systemNote } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetInviteCodeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetInviteCodeFilterInput` | | | | search | `GetInviteCodeSearchInput` | | | | sort | `GetInviteCodeSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `InviteCodePagination` --- #### getPublicInviteCodes ดึงข้อมูล Invite Code ของ app ที่เป็น public - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetPublicInviteCodes($getInput: GetPublicInviteCodeInput) { getPublicInviteCodes(getInput: $getInput) { inviteCodes { _id inviteCodeKey title subTitle description systemNote } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetPublicInviteCodeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetPublicInviteCodeFilterInput` | | | | search | `GetInviteCodeSearchInput` | | | | sort | `GetInviteCodeSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `InviteCodePagination` --- #### getInviteCodeById ดึงข้อมูล Invite Code โดยใช้ id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeById` ```graphql query GetInviteCodeById($id: String!) { getInviteCodeById(id: $id) { _id inviteCodeKey title subTitle description systemNote defaultOrganizationId defaultOrganizationKey maxUses currentUses # ... } } ``` | argument | Type | | --- | --- | | id | `String!` | Response: `InviteCode` --- #### getInviteCodeByKey ดึงข้อมูล Invite Code โดยใช้ key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getInviteCodeByKey` ```graphql query GetInviteCodeByKey($inviteCodeKey: String!) { getInviteCodeByKey(inviteCodeKey: $inviteCodeKey) { _id inviteCodeKey title subTitle description systemNote defaultOrganizationId defaultOrganizationKey maxUses currentUses # ... } } ``` | argument | Type | | --- | --- | | inviteCodeKey | `String!` | Response: `InviteCode` --- #### getPublicInviteCodeById ดึงข้อมูล Invite Code โดยใช้ id - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetPublicInviteCodeById($id: String!) { getPublicInviteCodeById(id: $id) { _id inviteCodeKey title subTitle description systemNote defaultOrganizationId defaultOrganizationKey maxUses currentUses # ... } } ``` | argument | Type | | --- | --- | | id | `String!` | Response: `InviteCode` --- #### getPublicInviteCodeByKey ดึงข้อมูล Invite Code โดยใช้ key - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetPublicInviteCodeByKey($inviteCodeKey: String!) { getPublicInviteCodeByKey(inviteCodeKey: $inviteCodeKey) { _id inviteCodeKey title subTitle description systemNote defaultOrganizationId defaultOrganizationKey maxUses currentUses # ... } } ``` | argument | Type | | --- | --- | | inviteCodeKey | `String!` | Response: `InviteCode` --- #### getOrganizationRolesCustomMenus ดึงข้อมูล getOrgRolesCustomMenus ระบุ appKey อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationRolesCustomMenus` ```graphql query GetOrganizationRolesCustomMenus($input: GetOrgRoleCustomMenuInput) { getOrganizationRolesCustomMenus(input: $input) { organizationRolesCustomMenus { _id appKey customMenuId customMenuKey customMenuPath organizationId } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrgRoleCustomMenuInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrgRoleCustomMenuFilterInput` | | | | search | `GetOrgRoleCustomMenuSearchInput` | | | | sort | `GetOrgRoleCustomMenuSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `OrgRoleCustomMenuPagination` --- #### getOrganizationRolesCustomMenuById ดึงข้อมูล getOrgRolesCustomMenu ตาม ID ถ้าอยากค้นหาข้าม app อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationRolesCustomMenuById` ```graphql query GetOrganizationRolesCustomMenuById($orgRolesCustomMenuId: String) { getOrganizationRolesCustomMenuById(orgRolesCustomMenuId: $orgRolesCustomMenuId) { _id appKey customMenuId customMenuKey customMenuPath customMenu { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } organizationId organizationKey organizationPath organizationRoleId # ... } } ``` | argument | Type | | --- | --- | | orgRolesCustomMenuId | `String` | Response: `OrgRolesCustomMenu` --- #### getOrganizationRolesPermissions ดึงข้อมูล getOrgRolesPermissions ระบุ appKey อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationRolesPermissions` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetOrganizationRolesPermissions($input: GetOrgRolePermissionInput) { getOrganizationRolesPermissions(input: $input) { orgRolePermissions { _id appKey permissionId permissionKey organizationId organizationKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrgRolePermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrgRolePermissionFilterInput` | | | | search | `GetOrgRolePermissionSearchInput` | | | | sort | `GetOrgRolePermissionSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `OrgRolePermissionPagination!` --- #### getOrganizationRolesPermissionById ดึงข้อมูล getOrgRolesPermissions ตาม ID ถ้าอยากค้นหาข้าม app อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationRolesPermissionById` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetOrganizationRolesPermissionById($orgRolesPermissionId: ID!) { getOrganizationRolesPermissionById(orgRolesPermissionId: $orgRolesPermissionId) { _id appKey permissionId permissionKey permission { _id serviceKey permissionKey title description systemNote } organizationId organizationKey organizationPath organizationRoleId organizationRoleKey # ... } } ``` | argument | Type | | --- | --- | | orgRolesPermissionId | `ID!` | Response: `OrgRolePermission!` --- #### getPermissions ดึงข้อมูล permissions ทั้งหมดที่มีในระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getPermissions` ```graphql query GetPermissions($input: GetPermissionInput) { getPermissions(input: $input) { permissions { _id serviceKey permissionKey title description systemNote } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetPermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetPermissionFilterInput` | | | | search | `GetPermissionSearchInput` | | | | sort | `GetPermissionSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `PermissionPagination!` --- #### getPermissionById ดึงข้อมูล permissions ตาม ID - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getPermissionById` ```graphql query GetPermissionById($permissionId: ID!) { getPermissionById(permissionId: $permissionId) { _id serviceKey permissionKey title description systemNote isSystem isGenerateApplication isGenerateOrganization appUserPolicyKeyPattern # ... } } ``` | argument | Type | | --- | --- | | permissionId | `ID!` | Response: `Permission!` --- #### getPermissionByKey ดึงข้อมูล permissions ตาม permissionKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getPermissionByKey` ```graphql query GetPermissionByKey($permissionKey: String!) { getPermissionByKey(permissionKey: $permissionKey) { _id serviceKey permissionKey title description systemNote isSystem isGenerateApplication isGenerateOrganization appUserPolicyKeyPattern # ... } } ``` | argument | Type | | --- | --- | | permissionKey | `String!` | Response: `Permission!` --- #### getPermissionsByAppKey ดึงข้อมูล permissions ที่อยู่ใน appKey ที่ระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getPermissions` ```graphql query GetPermissionsByAppKey($appKey: String!, $input: GetPermissionInput) { getPermissionsByAppKey(appKey: $appKey, input: $input) { permissions { _id serviceKey permissionKey title description systemNote } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetPermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetPermissionFilterInput` | | | | search | `GetPermissionSearchInput` | | | | sort | `GetPermissionSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `appKey: String!` Response: `PermissionPagination!` --- #### getPermissionsUnassignedToAppRole ดึงข้อมูล permissions ไม่อยู่ใน appRole ที่ระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getPermissions` ```graphql query GetPermissionsUnassignedToAppRole($appRoleId: ID!, $input: GetPermissionInput) { getPermissionsUnassignedToAppRole(appRoleId: $appRoleId, input: $input) { permissions { _id serviceKey permissionKey title description systemNote } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetPermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetPermissionFilterInput` | | | | search | `GetPermissionSearchInput` | | | | sort | `GetPermissionSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `appRoleId: ID!` Response: `PermissionPagination!` --- #### getPermissionsUnassignedToOrganizationRole ดึงข้อมูล permissions ไม่อยู่ใน organizationRole ที่ระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getPermissions` ```graphql query GetPermissionsUnassignedToOrganizationRole($organizationRoleId: ID!, $input: GetPermissionInput) { getPermissionsUnassignedToOrganizationRole(organizationRoleId: $organizationRoleId, input: $input) { permissions { _id serviceKey permissionKey title description systemNote } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetPermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetPermissionFilterInput` | | | | search | `GetPermissionSearchInput` | | | | sort | `GetPermissionSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `organizationRoleId: ID!` Response: `PermissionPagination!` --- #### getPermissionsUnassignedToDefaultOrgRole ดึงข้อมูล permissions ไม่อยู่ใน defaultOrgRole ที่ระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getPermissions` ```graphql query GetPermissionsUnassignedToDefaultOrgRole($defaultOrgRoleId: ID!, $input: GetPermissionInput) { getPermissionsUnassignedToDefaultOrgRole(defaultOrgRoleId: $defaultOrgRoleId, input: $input) { permissions { _id serviceKey permissionKey title description systemNote } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetPermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetPermissionFilterInput` | | | | search | `GetPermissionSearchInput` | | | | sort | `GetPermissionSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `defaultOrgRoleId: ID!` Response: `PermissionPagination!` --- #### getProfileWithAppRoles ดึงข้อมูล AppRoles ที่มีความเชื่อมโยงกับ user ที่เข้าใช้งาน - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getProfileWithAppRoles` ```graphql query GetProfileWithAppRoles { getProfileWithAppRoles { _id appKey firstName middleName lastName displayName gender authId username profileImage # ... } } ``` Response: `ProfileWithAppRoles` --- #### getProfileWithOrgRoles ดึงข้อมูล OrgRoles ที่มีความเชื่อมโยงกับ user ที่เข้าใช้งาน - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getProfileWithOrgRoles` ```graphql query GetProfileWithOrgRoles($organizationKey: String!) { getProfileWithOrgRoles(organizationKey: $organizationKey) { _id appKey firstName middleName lastName displayName gender authId username profileImage # ... } } ``` | argument | Type | | --- | --- | | organizationKey | `String!` | Response: `ProfileWithOrgRoles!` --- #### getUserAppRoles Get UserAppRoles ในการใช้งานระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserAppRoles` ```graphql query GetUserAppRoles($input: GetUserAppRolesInput) { getUserAppRoles(input: $input) { userAppRoles { _id authId appRoleId appRoleKey isActive createdAt } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetUserAppRolesInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetUserAppRolesFilterInput` | | | | search | `GetUserAppRolesSearchInput` | | | | sort | `GetUserAppRolesSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `UserAppRolesPagination!` --- #### getUserAppRoleById Get UserAppRole ในการใช้งานระบบ โดยใช้ UserAppRoleId ในการระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserAppRoleById` ```graphql query GetUserAppRoleById($userAppRoleId: ID!) { getUserAppRoleById(userAppRoleId: $userAppRoleId) { _id authId profile { _id appKey firstName middleName lastName displayName } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } isActive createdAt updatedAt } } ``` | argument | Type | | --- | --- | | userAppRoleId | `ID!` | Response: `UserAppRole!` --- #### getMyAppRoles Get MyAppRoles ในการใช้งานระบบ โดยดึงข้อมูลมาจาก token ผู้ใช้งาน - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetMyAppRoles { getMyAppRoles { _id authId profile { _id appKey firstName middleName lastName displayName } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } isActive createdAt updatedAt } } ``` Response: `[UserAppRole]!` --- #### getMyAppRoleByID Get MyAppRoles ในการใช้งานระบบ โดยดึงข้อมูลมาจาก token ผู้ใช้งาน และ ใช้ UserAppRoleId ในการระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetMyAppRoleByID($userAppRoleId: ID!) { getMyAppRoleByID(userAppRoleId: $userAppRoleId) { _id authId profile { _id appKey firstName middleName lastName displayName } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } isActive createdAt updatedAt } } ``` | argument | Type | | --- | --- | | userAppRoleId | `ID!` | Response: `UserAppRole!` --- #### getUserAppRolesByAppKey Get UserAppRoles ของ User ตาม AppKey ที่ระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetUserAppRolesByAppKey($appKey: String!, $input: GetUserAppRolesInput) { getUserAppRolesByAppKey(appKey: $appKey, input: $input) { userAppRoles { _id authId appRoleId appRoleKey isActive createdAt } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetUserAppRolesInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetUserAppRolesFilterInput` | | | | search | `GetUserAppRolesSearchInput` | | | | sort | `GetUserAppRolesSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `appKey: String!` Response: `UserAppRolesPagination!` --- #### getUserCustomMenus ดึงข้อมูล getUserCustomMenus ตามที่ค้นหา และสามารถ ระบุ appKey อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserCustomMenus` ```graphql query GetUserCustomMenus($input: GetUserCustomMenuInput) { getUserCustomMenus(input: $input) { userCustomMenus { _id appKey authId customMenuId customMenuKey customMenuPath } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetUserCustomMenuInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetUserCustomMenuFilterInput` | | | | search | `GetUserCustomMenuSearchInput` | | | | sort | `GetUserCustomMenuSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `UserCustomMenuPagination` --- #### getUserCustomMenuById ดึงข้อมูล getUserCustomMenuById ตาม ID ถ้าอยากค้นหาข้าม app อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserCustomMenuById` ```graphql query GetUserCustomMenuById($userCustomMenuId: ID!) { getUserCustomMenuById(userCustomMenuId: $userCustomMenuId) { _id appKey authId customMenuId customMenuKey customMenuPath customMenu { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } organizationId organizationKey organizationPath # ... } } ``` | argument | Type | | --- | --- | | userCustomMenuId | `ID!` | Response: `UserCustomMenu` --- #### getUserCustomMenusByAuthId ดึงข้อมูล getUserCustomMenusByAuthId ตาม authId ถ้าอยากค้นหาข้าม app อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserCustomMenusByAuthId` ```graphql query GetUserCustomMenusByAuthId($authId: String!, $input: GetUserCustomMenuInputByAuthId) { getUserCustomMenusByAuthId(authId: $authId, input: $input) { customMenus { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetUserCustomMenuInputByAuthId` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetUserCustomMenuByAuthIdFilterInput` | | | | search | `GetUserCustomMenuByAuthIdSearchInput` | | | | sort | `GetUserCustomMenuSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `authId: String!` Response: `CustomMenuPagination` --- #### getUserCustomMenusByLevel ดึงข้อมูล getUserCustomMenesByLevel ตาม level ถ้าอยากค้นหาข้าม app อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserCustomMenesByLevel` ```graphql query GetUserCustomMenusByLevel($level: EnumLevel!, $input: GetUserCustomMenuInputByLevel) { getUserCustomMenusByLevel(level: $level, input: $input) { userCustomMenus { _id appKey authId customMenuId customMenuKey customMenuPath } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetUserCustomMenuInputByLevel` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetUserCustomMenuByLevelFilterInput` | | | | search | `GetUserCustomMenuByLevelSearchInput` | | | | sort | `GetUserCustomMenuSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `level: EnumLevel!` Response: `UserCustomMenuPagination` --- #### getMyCustomMenus ดึงข้อมูล getMyCustomMenus ของ user ที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetMyCustomMenus($input: GetUserCustomMenuInputByAuthId) { getMyCustomMenus(input: $input) { customMenus { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetUserCustomMenuInputByAuthId` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetUserCustomMenuByAuthIdFilterInput` | | | | search | `GetUserCustomMenuByAuthIdSearchInput` | | | | sort | `GetUserCustomMenuSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `CustomMenuPagination` --- #### getUserOrgRoles Get UserOrgRoles ในการใช้งานระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserOrgRoles` ```graphql query GetUserOrgRoles($input: GetUserOrgRolesInput) { getUserOrgRoles(input: $input) { userOrgRoles { _id authId organizationRoleKey organizationKey organizationPath isActive } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetUserOrgRolesInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetUserOrgRolesFilterInput` | | | | search | `GetUserOrgRolesSearchInput` | | | | sort | `GetUserOrgRolesSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `UserOrgRolesPagination!` --- #### getUserOrgRoleById Get UserOrgRoles ในการใช้งานระบบ โดยใช้ UserOrgRoleId ในการระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserOrgRoleById` ```graphql query GetUserOrgRoleById($userOrgRoleId: ID!) { getUserOrgRoleById(userOrgRoleId: $userOrgRoleId) { _id authId profile { _id appKey firstName middleName lastName displayName } organizationRoleKey organizationRole { _id organizationKey organizationRoleKey title subTitle description } organizationKey organizationPath isActive createdAt updatedAt # ... } } ``` | argument | Type | | --- | --- | | userOrgRoleId | `ID!` | Response: `UserOrgRole!` --- #### getMyOrgRoles Get MyOrgRoles ในการใช้งานระบบ โดยดึงข้อมูลมาจาก token ผู้ใช้งาน - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetMyOrgRoles { getMyOrgRoles { _id authId profile { _id appKey firstName middleName lastName displayName } organizationRoleKey organizationRole { _id organizationKey organizationRoleKey title subTitle description } organizationKey organizationPath isActive createdAt updatedAt # ... } } ``` Response: `[UserOrgRole]!` --- #### getMyOrgRoleByID Get MyOrgRole ในการใช้งานระบบ โดยดึงข้อมูลมาจาก token ผู้ใช้งาน และ ใช้ UserOrgRoleId ในการระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetMyOrgRoleByID($userOrgRoleId: ID!) { getMyOrgRoleByID(userOrgRoleId: $userOrgRoleId) { _id authId profile { _id appKey firstName middleName lastName displayName } organizationRoleKey organizationRole { _id organizationKey organizationRoleKey title subTitle description } organizationKey organizationPath isActive createdAt updatedAt # ... } } ``` | argument | Type | | --- | --- | | userOrgRoleId | `ID!` | Response: `UserOrgRole!` --- #### getUserPermissions ดึงข้อมูล getUserPermissions ตามที่ค้นหา และสามารถ ระบุ appKey อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserPermissions` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetUserPermissions($input: GetUserPermissionInput) { getUserPermissions(input: $input) { userPermissions { _id appKey authId permissionId permissionKey isGenerateApplication } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetUserPermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetUserPermissionFilterInput` | | | | search | `GetUserPermissionSearchInput` | | | | sort | `GetUserPermissionSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `UserPermissionPagination!` --- #### getUserPermissionById ดึงข้อมูล getUserPermissionById ตาม ID ถ้าอยากค้นหาข้าม app อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserPermissionById` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetUserPermissionById($userPermissionId: ID!) { getUserPermissionById(userPermissionId: $userPermissionId) { _id appKey authId permissionId permissionKey isGenerateApplication isGenerateOrganization userPolicyKeyPattern organizationId organizationKey # ... } } ``` | argument | Type | | --- | --- | | userPermissionId | `ID!` | Response: `UserPermission!` --- #### getUserPolicys ดึงข้อมูล getUserPolicys ตามที่ค้นหา และสามารถ ระบุ appKey อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserPolicys` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetUserPolicys($input: GetUserPolicyInput) { getUserPolicys(input: $input) { userPolicys { _id appKey userPolicyKey authId permissionKey organizationId } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetUserPolicyInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetUserPolicyFilterInput` | | | | search | `GetUserPolicySearchInput` | | | | sort | `GetUserPolicySortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `UserPolicyPagination!` --- #### getUserPolicyById ดึงข้อมูล getUserPolicyById ตาม ID ถ้าอยากค้นหาข้าม app อื่นได้ แต่ต้องมีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserPolicyById` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetUserPolicyById($userPolicyId: ID!) { getUserPolicyById(userPolicyId: $userPolicyId) { _id appKey userPolicyKey authId permissionKey organizationId createdAt updatedAt } } ``` | argument | Type | | --- | --- | | userPolicyId | `ID!` | Response: `UserPolicy!` --- #### verifyUserPolicy ใช้สำหรับตรวจสอบ list ของ userPolicyKey ว่าแต่สิทธิ์ที่ส่งมามีสิทธิ์ใช้งานหรือไม่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query VerifyUserPolicy($userPolicyKeys: [String!]!) { verifyUserPolicy(userPolicyKeys: $userPolicyKeys) { userPolicyKey isHavePolicy } } ``` | argument | Type | | --- | --- | | userPolicyKeys | `[String!]!` | Response: `[ResultVerifyUserPolicy]!` --- ### Mutation เป็น API ที่ใช้สำหรับการแก้ไขข้อมูล --- #### addCustomMenuToAppRole เพิ่ม CustomMenu ลงใน AppRole ทำข้าม app ได้ โดยเช็กจาก appRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `addCustomMenuToAppRole` ```graphql mutation AddCustomMenuToAppRole($input: AddCustomMenuToAppRoleInput!) { addCustomMenuToAppRole(input: $input) } ``` `input`: `AddCustomMenuToAppRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | customMenuIds | `[String]` | | customMenuId ใช้เพื่อบอกว่า เป็น id ของ customMenu ไหน | | appRoleIds | `[String]` | | appRoleId ใช้เพื่อบอกว่า เป็น id ของ appRole ไหน | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | Response: `Boolean` --- #### removeCustomMenuFromAppRole ลบ CustomMenu จาก AppRole ทำข้าม app ได้ โดยเช็กจาก appRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `removeCustomMenuFromAppRole` ```graphql mutation RemoveCustomMenuFromAppRole($appRolesCustomMenuId: String!) { removeCustomMenuFromAppRole(appRolesCustomMenuId: $appRolesCustomMenuId) { _id appKey customMenuId customMenuKey customMenuPath customMenu { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } priority # ... } } ``` | argument | Type | | --- | --- | | appRolesCustomMenuId | `String!` | Response: `AppRolesCustomMenu` --- #### updateAppRolesCustomMenu แก้ไขข้อมูล AppRolesCustomMenu ตาม id ทำข้าม app ได้ โดยเช็กจาก appRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateAppRolesCustomMenu` ```graphql mutation UpdateAppRolesCustomMenu($appRolesCustomMenuId: String!, $input: UpdateAppRolesCustomMenuInput!) { updateAppRolesCustomMenu(appRolesCustomMenuId: $appRolesCustomMenuId, input: $input) { _id appKey customMenuId customMenuKey customMenuPath customMenu { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } priority # ... } } ``` `input`: `UpdateAppRolesCustomMenuInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | argument อื่น: `appRolesCustomMenuId: String!` Response: `AppRolesCustomMenu!` --- #### addPermissionToAppRole เพิ่ม Permission ลงใน AppRole ทำข้าม app ได้ โดยเช็กจาก appRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `addPermissionToAppRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation AddPermissionToAppRole($input: AddPermissionToAppRoleInput!) { addPermissionToAppRole(input: $input) } ``` `input`: `AddPermissionToAppRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | permissionId | `String` | | permissionId ใช้เพื่อบอกว่า เป็น id ของ permission ไหน | | appRoleId | `String` | | appRoleId ใช้เพื่อบอกว่า เป็น id ของ appRole ไหน | | permissionIds | `[String]` | | permissionIds สำหรับใช้ในการระบุ List Permission ที่ต้องการให้ AppRole ใช้งาน | | appRoleIds | `[String]` | | appRoleIds สำหรับใช้ในการระบุ List AppRole ที่ต้องการให้ใช้งาน permission | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | Response: `Boolean!` --- #### removePermissionFromAppRole ลบ Permission จาก AppRole ทำข้าม app ได้ โดยเช็กจาก appRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `removePermissionFromAppRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation RemovePermissionFromAppRole($appRolesPermissionId: ID!) { removePermissionFromAppRole(appRolesPermissionId: $appRolesPermissionId) { _id appKey permissionId permissionKey permission { _id serviceKey permissionKey title description systemNote } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } systemNote isActive # ... } } ``` | argument | Type | | --- | --- | | appRolesPermissionId | `ID!` | Response: `AppRolePermission!` --- #### updateAppRolePermission แก้ไขข้อมูล AppRolePermission ตาม id ทำข้าม app ได้ โดยเช็กจาก appRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateAppRolePermission` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation UpdateAppRolePermission($appRolesPermissionId: ID!, $input: UpdateAppRolePermissionInput!) { updateAppRolePermission(appRolesPermissionId: $appRolesPermissionId, input: $input) { _id appKey permissionId permissionKey permission { _id serviceKey permissionKey title description systemNote } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } systemNote isActive # ... } } ``` `input`: `UpdateAppRolePermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน เมื่อเป็น false จะลบ userPolicy, userAppRolePermission เมื่อแก้กลับเป็น true gen userPolicy,userAppRolePermission | argument อื่น: `appRolesPermissionId: ID!` Response: `AppRolePermission!` --- #### createCustomMenu สร้างข้อมูล CustomMenu - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createCustomMenu` ```graphql mutation CreateCustomMenu($createInput: CreateCustomMenuInput!) { createCustomMenu(createInput: $createInput) { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath level title multilingualTitle description # ... } } ``` `createInput`: `CreateCustomMenuInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | serviceKey | `String` | | serviceKey ใช้เพื่อบอกว่า เป็น ของ service ไหน ไม่ใส่ default เป็น service acl | | customMenuKey | `String` | | key ของ custom menu | | parentCustomMenuId | `String` | | id ของ parent menu | | level | `EnumLevel!` | ใช่ | ใช้เพื่อเอามาแบ่งตามสิทธิ | | title | `String!` | ใช่ | ชื่อที่แสดงบนเมนู | | multilingualTitle | `JSON` | | ชื่อที่แสดงบนเมนู (หลายภาษา) | | description | `String` | | คำอธิบายสำหรับผู้ดูแลระบบ | | icon | `String` | | ไอคอน (เช่นชื่อ material icon) | | type | `EnumCustomMenuType!` | ใช่ | type ของเมนู | | templatePath | `String` | | Path ที่มี dynamic param ได้ เช่น /org/{orgKey}/edit/{itemId} | | pathParams | `[String]` | | รายชื่อ param ใน path | | queryParams | `[String]` | | รายชื่อ query string param ที่ต้องแนบ (ถ้าต้องการ) | | url | `String` | | สำหรับ external-link หรือ iframe | | openInNewTab | `Boolean` | | สำหรับ external-link | | fileName | `String` | | สำหรับ external-download | | priority | `Int` | | ใช้ตัดสินใจเมื่อ route ตรงกับหลายอัน (ยิ่งน้อยยิ่งสำคัญกว่า) | | order | `Int` | | ลำดับแสดงผลบนหน้าจอ (UI เท่านั้น, ไม่มีผลกับ route) | | showBadgeCount | `Boolean` | | ใช้แสดง badge count | | badgeCountKey | `String` | | Key ใช้ดึงค่าจำนวน badge | | breadCrumbs | `[CreateBreadCrumbInput]` | | breadCrumbs รายชื่อ breadcrumb ที่จะแสดงบนเมนูนี้ | | isGroupMenu | `Boolean` | | เป็น group menu หรือไม่ | | … | | | (มีอีก 4 ฟิลด์ ดู schema) | Response: `CustomMenu` --- #### updateCustomMenu แก้ไขข้อมูล CustomMenu - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateCustomMenu` ```graphql mutation UpdateCustomMenu($customMenuId: String!, $updateInput: UpdateCustomMenuInput!) { updateCustomMenu(customMenuId: $customMenuId, updateInput: $updateInput) { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath level title multilingualTitle description # ... } } ``` `updateInput`: `UpdateCustomMenuInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | serviceKey | `String` | | serviceKey ใช้เพื่อบอกว่า เป็น ของ service ไหน | | title | `String` | | ชื่อที่แสดงบนเมนู | | multilingualTitle | `JSON` | | ชื่อที่แสดงบนเมนู (หลายภาษา) | | description | `String` | | คำอธิบายสำหรับผู้ดูแลระบบ | | icon | `String` | | ไอคอน (เช่นชื่อ material icon) | | type | `EnumCustomMenuType` | | type ของเมนู | | templatePath | `String` | | Path ที่มี dynamic param ได้ เช่น /org/{orgKey}/edit/{itemId} | | pathParams | `[String]` | | รายชื่อ param ใน path | | queryParams | `[String]` | | รายชื่อ query string param ที่ต้องแนบ (ถ้าต้องการ) | | url | `String` | | สำหรับ external-link หรือ iframe | | openInNewTab | `Boolean` | | สำหรับ external-link | | fileName | `String` | | สำหรับ external-download | | priority | `Int` | | ใช้ตัดสินใจเมื่อ route ตรงกับหลายอัน (ยิ่งน้อยยิ่งสำคัญกว่า) | | order | `Int` | | ลำดับแสดงผลบนหน้าจอ (UI เท่านั้น, ไม่มีผลกับ route) | | showBadgeCount | `Boolean` | | ใช้แสดง badge count | | badgeCountKey | `String` | | Key ใช้ดึงค่าจำนวน badge | | breadCrumbs | `[CreateBreadCrumbInput]` | | breadCrumbs รายชื่อ breadcrumb ที่จะแสดงบนเมนูนี้ | | isGroupMenu | `Boolean` | | เป็น group menu หรือไม่ | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | | isSystem | `Boolean` | | isSystem เมนูระบบ (ใช้กรองในหน้าแอดมินเท่านั้น) | argument อื่น: `customMenuId: String!` Response: `CustomMenu` --- #### deleteCustomMenu ลบข้อมูล CustomMenu - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteCustomMenu` ```graphql mutation DeleteCustomMenu($customMenuId: String!) { deleteCustomMenu(customMenuId: $customMenuId) { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath level title multilingualTitle description # ... } } ``` | argument | Type | | --- | --- | | customMenuId | `String!` | Response: `CustomMenu` --- #### addCustomMenuToDefaultOrganizationRole เพิ่ม CustomMenu ลงใน DefaultOrganizationRole - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `addCustomMenuToDefaultOrganizationRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation AddCustomMenuToDefaultOrganizationRole($input: AddCustomMenuToDefaultOrganizationRoleInput!) { addCustomMenuToDefaultOrganizationRole(input: $input) } ``` `input`: `AddCustomMenuToDefaultOrganizationRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | defaultOrganizationRoleIds | `[String!]!` | ใช่ | defaultOrganizationRoleIds ใช้เพื่อบอกว่า เป็น id ของ defaultOrganizationRole ไหน | | customMenuSelectType | `EnumSelect` | | ประเภทการเลือก ALL เลือกทั้งหมด, SELECT เลือกบางส่วน | | customMenuIds | `[String]` | | customMenuIds ใช้เพื่อบอกว่า เป็น id ของ customMenu ไหน | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | Response: `Boolean!` --- #### removeCustomMenuFromDefaultOrganizationRole ลบ CustomMenu จาก DefaultOrganizationRole - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `removeCustomMenuFromDefaultOrganizationRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation RemoveCustomMenuFromDefaultOrganizationRole($defaultOrganizationRoleCustomMenuId: ID!) { removeCustomMenuFromDefaultOrganizationRole(defaultOrganizationRoleCustomMenuId: $defaultOrganizationRoleCustomMenuId) { _id appKey customMenuId customMenuKey customMenu { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } defaultOrganizationRoleId defaultOrganizationRoleKey defaultOrganizationRole { _id appKey defaultOrganizationRoleKey title subTitle description } systemNote priority # ... } } ``` | argument | Type | | --- | --- | | defaultOrganizationRoleCustomMenuId | `ID!` | Response: `DefaultOrganizationRoleCustomMenu!` --- #### updateDefaultOrganizationRoleCustomMenu แก้ไขข้อมูล DefaultOrganizationRoleCustomMenu ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateDefaultOrganizationRoleCustomMenu` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation UpdateDefaultOrganizationRoleCustomMenu($defaultOrganizationRoleCustomMenuId: ID!, $input: UpdateDefaultOrganizationRoleCustomMenuInput!) { updateDefaultOrganizationRoleCustomMenu(defaultOrganizationRoleCustomMenuId: $defaultOrganizationRoleCustomMenuId, input: $input) { _id appKey customMenuId customMenuKey customMenu { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } defaultOrganizationRoleId defaultOrganizationRoleKey defaultOrganizationRole { _id appKey defaultOrganizationRoleKey title subTitle description } systemNote priority # ... } } ``` `input`: `UpdateDefaultOrganizationRoleCustomMenuInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | systemNote | `String` | | ไว้สำหรับ admin | | priority | `Int` | | ลำดับความสำคัญ (priority) | | order | `Int` | | ลำดับการแสดงผล (order) | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | argument อื่น: `defaultOrganizationRoleCustomMenuId: ID!` Response: `DefaultOrganizationRoleCustomMenu!` --- #### addPermissionToDefaultOrganizationRole เพิ่ม Permission ลงใน DefaultOrganizationRole ทำข้าม app ได้ โดยเช็กจาก defaultOrganizationRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `addPermissionToDefaultOrganizationRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation AddPermissionToDefaultOrganizationRole($input: AddPermissionToDefaultOrgRoleInput!) { addPermissionToDefaultOrganizationRole(input: $input) } ``` `input`: `AddPermissionToDefaultOrgRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | defaultOrganizationRoleIds | `[String!]!` | ใช่ | defaultOrganizationRoleId ใช้เพื่อบอกว่า เป็น id ของ defaultOrganizationRole ไหน | | permissionSelectType | `EnumSelect` | | ประเภทการเลือก ALL เลือกทั้งหมด, SELECT เลือกบางส่วน | | permissionIds | `[String]` | | permissionId ใช้เพื่อบอกว่า เป็น id ของ permission ไหน | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | Response: `Boolean!` --- #### removePermissionFromDefaultOrganizationRole ลบ Permission จาก DefaultOrganizationRole ทำข้าม app ได้ โดยเช็กจาก defaultOrganizationRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `removePermissionFromDefaultOrganizationRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation RemovePermissionFromDefaultOrganizationRole($defaultOrgRolePermissionId: ID!) { removePermissionFromDefaultOrganizationRole(defaultOrgRolePermissionId: $defaultOrgRolePermissionId) { _id appKey permissionId permissionKey permission { _id serviceKey permissionKey title description systemNote } defaultOrganizationRoleId defaultOrganizationRoleKey defaultOrganizationRole { _id appKey defaultOrganizationRoleKey title subTitle description } systemNote isActive # ... } } ``` | argument | Type | | --- | --- | | defaultOrgRolePermissionId | `ID!` | Response: `DefaultOrgRolePermission!` --- #### updateDefaultOrganizationRolePermission แก้ไขข้อมูล DefaultOrganizationRolePermission ตาม id ทำข้าม app ได้ โดยเช็กจาก defaultOrganizationRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateDefaultOrganizationRolePermission` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation UpdateDefaultOrganizationRolePermission($defaultOrgRolePermissionId: ID!, $input: UpdateDefaultOrgRolePermissionInput!) { updateDefaultOrganizationRolePermission(defaultOrgRolePermissionId: $defaultOrgRolePermissionId, input: $input) { _id appKey permissionId permissionKey permission { _id serviceKey permissionKey title description systemNote } defaultOrganizationRoleId defaultOrganizationRoleKey defaultOrganizationRole { _id appKey defaultOrganizationRoleKey title subTitle description } systemNote isActive # ... } } ``` `input`: `UpdateDefaultOrgRolePermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน เมื่อเป็น false จะลบ userPolicy, userDefaultOrgRolePermission เมื่อแก้กลับเป็น true gen userPolicy,userDefaultOrgRolePermission | argument อื่น: `defaultOrgRolePermissionId: ID!` Response: `DefaultOrgRolePermission!` --- #### createInviteCode สร้าง Invite Code - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createInviteCode` ```graphql mutation CreateInviteCode($createInput: CreateInviteCodeInput!) { createInviteCode(createInput: $createInput) { _id inviteCodeKey title subTitle description systemNote defaultOrganizationId defaultOrganizationKey maxUses currentUses # ... } } ``` `createInput`: `CreateInviteCodeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | inviteCodeKey | `String` | | key ของ invite code | | title | `String!` | ใช่ | ชื่อ | | subTitle | `String` | | ชื่อรอง | | description | `String` | | รายล่ะเอียด | | systemNote | `String` | | system note | | defaultOrganizationKey | `String` | | default organization key | | maxUses | `Int` | | จำนวนการใช้งานสูงสุด null หมายถึงไม่จำกัด | | isPublic | `Boolean` | | เป็น public หรือไม่ | | validStartTime | `Date` | | วันที่ invite code มีผล | | validEndTime | `Date` | | วันที่ invite code หมดอายุ | | url | `String` | | url สำหรับ QR code | | appRoleIds | `[String]` | | app role id ที่จะเอามาใช้กับ invite code | | orgRoleIds | `[String]` | | org role id ที่จะเอามาใช้กับ invite code | | isDefault | `Boolean` | | เป็นค่าเริ่มต้นหรือไม่ | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน ถ้าไม่ใส่ valid start time มา และ isActive เป็น true จะเป็น new Date() | Response: `InviteCode!` --- #### updateInviteCode แก้ไข Invite Code - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateInviteCode` ```graphql mutation UpdateInviteCode($inviteCodeId: String!, $updateInput: UpdateInviteCodeInput!) { updateInviteCode(inviteCodeId: $inviteCodeId, updateInput: $updateInput) { _id inviteCodeKey title subTitle description systemNote defaultOrganizationId defaultOrganizationKey maxUses currentUses # ... } } ``` `updateInput`: `UpdateInviteCodeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | title | `String` | | ชื่อ | | subTitle | `String` | | ชื่อรอง | | description | `String` | | รายล่ะเอียด | | systemNote | `String` | | system note | | defaultOrganizationKey | `String` | | default organization key | | maxUses | `Int` | | จำนวนการใช้งานสูงสุด null หมายถึงไม่จำกัด | | isPublic | `Boolean` | | เป็น public หรือไม่ | | validStartTime | `Date` | | วันที่ invite code มีผล | | validEndTime | `Date` | | วันที่ invite code หมดอายุ | | url | `String` | | url สำหรับ QR code | | appRoleIds | `[String]` | | app role id ที่จะเอามาใช้กับ invite code **ที่จะเพิ่ม ถ้าจะไม่เพิ่มส่ง [] มา | | orgRoleIds | `[String]` | | org role id ที่จะเอามาใช้กับ invite code **ที่จะเพิ่ม ถ้าจะไม่เพิ่มส่ง [] มา | | isDefault | `Boolean` | | เป็นค่าเริ่มต้นหรือไม่ | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน ถ้าเป็น true valid start time จะเป็น new Date() | argument อื่น: `inviteCodeId: String!` Response: `InviteCode!` --- #### deleteInviteCode ลบ Invite Code - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteInviteCode` ```graphql mutation DeleteInviteCode($inviteCodeId: String!) { deleteInviteCode(inviteCodeId: $inviteCodeId) { _id inviteCodeKey title subTitle description systemNote defaultOrganizationId defaultOrganizationKey maxUses currentUses # ... } } ``` | argument | Type | | --- | --- | | inviteCodeId | `String!` | Response: `InviteCode!` --- #### addCustomMenuToOrganizationRole เพิ่ม CustomMenu ลงใน OrgRole ทำข้าม app ได้ โดยเช็กจาก orgRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `addCustomMenuToOrganizationRole` ```graphql mutation AddCustomMenuToOrganizationRole($input: AddCustomMenuToOrgRoleInput) { addCustomMenuToOrganizationRole(input: $input) } ``` `input`: `AddCustomMenuToOrgRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | customMenuIds | `[String]` | | customMenuId ใช้เพื่อบอกว่า เป็น id ของ customMenu ไหน | | organizationRoleIds | `[String]` | | organizationRoleId ใช้เพื่อบอกว่า เป็น id ของ orgRole ไหน | | systemNote | `String` | | ไว้สำหรับ admin | | isChildOrganizationAccess | `Boolean` | | isChildOrganizationAccess ใช้บอกว่าสามารถเข้าถึงข้อมูลของ child organization ได้หรือไม่ | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | Response: `Boolean` --- #### removeCustomMenuFromOrganizationRole ลบ CustomMenu จาก OrgRole ทำข้าม app ได้ โดยเช็กจาก orgRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `removeCustomMenuFromOrganizationRole` ```graphql mutation RemoveCustomMenuFromOrganizationRole($orgRolesCustomMenuId: String) { removeCustomMenuFromOrganizationRole(orgRolesCustomMenuId: $orgRolesCustomMenuId) { _id appKey customMenuId customMenuKey customMenuPath customMenu { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } organizationId organizationKey organizationPath organizationRoleId # ... } } ``` | argument | Type | | --- | --- | | orgRolesCustomMenuId | `String` | Response: `OrgRolesCustomMenu` --- #### updateOrganizationRoleCustomMenu แก้ไขข้อมูล OrgRolesCustomMenu ตาม id ทำข้าม app ได้ โดยเช็กจาก orgRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateOrganizationRoleCustomMenu` ```graphql mutation UpdateOrganizationRoleCustomMenu($orgRolesCustomMenuId: String, $input: UpdateOrgRoleCustomMenuInput) { updateOrganizationRoleCustomMenu(orgRolesCustomMenuId: $orgRolesCustomMenuId, input: $input) { _id appKey customMenuId customMenuKey customMenuPath customMenu { _id serviceKey customMenuKey parentCustomMenuId parentCustomMenuKey customMenuPath } organizationId organizationKey organizationPath organizationRoleId # ... } } ``` `input`: `UpdateOrgRoleCustomMenuInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | systemNote | `String` | | ไว้สำหรับ admin | | isChildOrganizationAccess | `Boolean` | | isChildOrganizationAccess ใช้บอกว่าสามารถเข้าถึงข้อมูลของ child organization ได้หรือไม่ | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | argument อื่น: `orgRolesCustomMenuId: String` Response: `OrgRolesCustomMenu` --- #### addPermissionToOrganizationRole เพิ่ม Permission ลงใน OrgRole ทำข้าม app ได้ โดยเช็กจาก orgRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `addPermissionToOrganizationRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation AddPermissionToOrganizationRole($input: AddPermissionToOrgRoleInput!) { addPermissionToOrganizationRole(input: $input) } ``` `input`: `AddPermissionToOrgRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | permissionId | `String` | | permissionId ใช้เพื่อบอกว่า เป็น id ของ permission ไหน | | organizationRoleId | `String` | | organizationRoleId ใช้เพื่อบอกว่า เป็น id ของ orgRole ไหน | | permissionIds | `[String]` | | permissionIds สำหรับใช้ในการระบุ List Permission ที่ต้องการให้ OrganizationRole ใช้งาน | | organizationRoleIds | `[String]` | | organizationRoleIds สำหรับใช้ในการระบุ List AppRole ที่ต้องการให้ใช้งาน permission | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | | isChildOrganizationAccess | `Boolean` | | สามารถเข้าถึงข้อมูลของ child organization ได้หรือไม่ | Response: `Boolean!` --- #### removePermissionFromOrganizationRole ลบ Permission จาก OrgRole ทำข้าม app ได้ โดยเช็กจาก orgRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `removePermissionFromOrganizationRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation RemovePermissionFromOrganizationRole($orgRolesPermissionId: ID!) { removePermissionFromOrganizationRole(orgRolesPermissionId: $orgRolesPermissionId) { _id appKey permissionId permissionKey permission { _id serviceKey permissionKey title description systemNote } organizationId organizationKey organizationPath organizationRoleId organizationRoleKey # ... } } ``` | argument | Type | | --- | --- | | orgRolesPermissionId | `ID!` | Response: `OrgRolePermission!` --- #### updateOrganizationRolePermission แก้ไขข้อมูล OrgRolePermission ตาม id ทำข้าม app ได้ โดยเช็กจาก orgRoleId ถ้ามีสิทธิ์ `systemApp` - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateOrganizationRolePermission` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation UpdateOrganizationRolePermission($orgRolesPermissionId: ID!, $input: UpdateOrgRolePermissionInput!) { updateOrganizationRolePermission(orgRolesPermissionId: $orgRolesPermissionId, input: $input) { _id appKey permissionId permissionKey permission { _id serviceKey permissionKey title description systemNote } organizationId organizationKey organizationPath organizationRoleId organizationRoleKey # ... } } ``` `input`: `UpdateOrgRolePermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | systemNote | `String` | | ไว้สำหรับ admin | | isChildOrganizationAccess | `Boolean` | | สามารถเข้าถึงข้อมูลของ child organization ได้หรือไม่ | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน เมื่อเป็น false จะลบ userPolicy, userOrgRolePermission เมื่อแก้กลับเป็น true gen userPolicy,userOrgRolePermission | argument อื่น: `orgRolesPermissionId: ID!` Response: `OrgRolePermission!` --- #### createPermission สร้างข้อมูล Permission - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createPermission` ```graphql mutation CreatePermission($createInput: CreatePermissionInput!) { createPermission(createInput: $createInput) { _id serviceKey permissionKey title description systemNote isSystem isGenerateApplication isGenerateOrganization appUserPolicyKeyPattern # ... } } ``` `createInput`: `CreatePermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | serviceKey | `String!` | ใช่ | serviceKey ใช้เพื่อบอกว่า เป็น ของ service ไหน | | permissionKey | `String!` | ใช่ | permissionKey ใช้เป็น key ในการระบุ userPolicyKey | | title | `String` | | ชื่อ | | description | `String` | | รายล่ะเอียด | | systemNote | `String` | | ไว้สำหรับ admin | | isSystem | `Boolean` | | isSystem | | isGenerateApplication | `Boolean` | | ถ้าเป็น true ถึงจะสามารถผูกได้กับ appRoles (ไว้เช็กสิทธิ lv app) | | isGenerateOrganization | `Boolean` | | ถ้าเป็น true ถึงจะสามารถผูกได้กับ organizationRoles (ไว้เช็กสิทธิ lv org) | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | Response: `Permission!` --- #### updatePermission แก้ไขข้อมูล Permission - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updatePermission` ```graphql mutation UpdatePermission($permissionId: ID!, $updateInput: UpdatePermissionInput!) { updatePermission(permissionId: $permissionId, updateInput: $updateInput) { _id serviceKey permissionKey title description systemNote isSystem isGenerateApplication isGenerateOrganization appUserPolicyKeyPattern # ... } } ``` `updateInput`: `UpdatePermissionInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | title | `String` | | ชื่อ | | description | `String` | | รายล่ะเอียด | | systemNote | `String` | | ไว้สำหรับ admin | | isSystem | `Boolean` | | isSystem | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน เมื่อเป็น false จะทำการปิดการใช้งาน appRolesPermission,organizationRolesPermission และ ลบ userPolicy, userPermission เมื่อแก้กลับเป็น true ต้องไปแก้ใน appRolesPermission,organizationRolesPermission ซ้ำเพื่อเปิดให้เจน userPolicy,userPermission เนื่องจาก ป้องการการพลาดให้สิทธิที่ไม่ควรให้กับ user | argument อื่น: `permissionId: ID!` Response: `Permission!` --- #### deletePermission ลบข้อมูล Permission - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deletePermission` ```graphql mutation DeletePermission($permissionId: ID!) { deletePermission(permissionId: $permissionId) { _id serviceKey permissionKey title description systemNote isSystem isGenerateApplication isGenerateOrganization appUserPolicyKeyPattern # ... } } ``` | argument | Type | | --- | --- | | permissionId | `ID!` | Response: `Permission!` --- #### addAppRoleToUser เพิ่ม AppRole ให้กับ User ในการใช้งานระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `addAppRoleToUser` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation AddAppRoleToUser($addAppRoleToUserInput: AddAppRoleToUserInput!) { addAppRoleToUser(addAppRoleToUserInput: $addAppRoleToUserInput) } ``` `addAppRoleToUserInput`: `AddAppRoleToUserInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String!` | ใช่ | ข้อมูล AppKey สำหรับใช้ในการระบุ App ที่ต้องการให้ User ใช้งาน | | authId | `String` | | ข้อมูล AuthId สำหรับใช้ในการระบุ User ที่ต้องการให้ใช้งาน AppRole | | appRoleId | `String` | | ข้อมูล AppRoleId สำหรับใช้ในการระบุ AppRole ที่ต้องการให้ User ใช้งาน | | authIds | `[String]` | | authIds สำหรับใช้ในการระบุ List User ที่ต้องการให้ใช้งาน AppRole | | appRoleIds | `[String]` | | appRoleIds สำหรับใช้ในการระบุ List AppRole ที่ต้องการให้ User ใช้งาน | | isActive | `Boolean` | | ระบุสถานะของการใช้งานของ AppRole นี้ | Response: `Boolean!` --- #### removeAppRoleFromUser ลบ AppRole ให้กับ User ในการใช้งานระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `removeAppRoleFromUser` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation RemoveAppRoleFromUser($userAppRoleId: ID!, $appKey: String) { removeAppRoleFromUser(userAppRoleId: $userAppRoleId, appKey: $appKey) { _id authId profile { _id appKey firstName middleName lastName displayName } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } isActive createdAt updatedAt } } ``` | argument | Type | | --- | --- | | userAppRoleId | `ID!` | | appKey | `String` | Response: `UserAppRole!` --- #### updateUserAppRole อัปเดต AppRole ให้กับ User ในการใช้งานระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateUserAppRole` ```graphql mutation UpdateUserAppRole($updateUserAppRoleInput: [UpdateUserAppRoleInput]!) { updateUserAppRole(updateUserAppRoleInput: $updateUserAppRoleInput) { _id authId profile { _id appKey firstName middleName lastName displayName } appRoleId appRoleKey appRole { _id appRoleKey title subTitle description systemNote } isActive createdAt updatedAt } } ``` `updateUserAppRoleInput`: `UpdateUserAppRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String!` | ใช่ | ข้อมูล AppKey สำหรับใช้ในการระบุ App ที่ต้องการให้ User ใช้งาน | | authId | `String!` | ใช่ | ข้อมูล AuthId สำหรับใช้ในการระบุ User ที่ต้องการให้ใช้งาน AppRole | | appRoleId | `String!` | ใช่ | ข้อมูล AppRoleId สำหรับใช้ในการระบุ AppRole ที่ต้องการให้ User ใช้งาน | | isActive | `Boolean!` | ใช่ | ระบุสถานะของการใช้งานของ AppRole นี้ | Response: `[UserAppRole]!` --- #### addOrganizationRoleToUser เพิ่ม OrganizationRole ให้กับ User ในการใช้งานระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `addOrganizationRoleToUser` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation AddOrganizationRoleToUser($addOrganizationRoleToUserInput: AddOrganizationRoleToUserInput!) { addOrganizationRoleToUser(addOrganizationRoleToUserInput: $addOrganizationRoleToUserInput) } ``` `addOrganizationRoleToUserInput`: `AddOrganizationRoleToUserInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String!` | ใช่ | ข้อมูล AppKey สำหรับใช้ในการระบุ App ที่ต้องการให้ User ใช้งาน | | authId | `String` | | ข้อมูล AuthId สำหรับใช้ในการระบุ User ที่ต้องการให้ใช้งาน OrgRole | | organizationRoleId | `String` | | ข้อมูล OrganizationRoleId สำหรับใช้ในการระบุ OrgRole ที่ต้องการให้ User ใช้งาน | | authIds | `[String]` | | authIds สำหรับใช้ในการระบุ List User ที่ต้องการให้ใช้งาน AppRole | | organizationRoleIds | `[String]` | | OrganizationRoleIds สำหรับใช้ในการระบุ List OrganizationRole ที่ต้องการให้ User ใช้งาน | | isActive | `Boolean` | | ระบุสถานะของการใช้งานของ OrgRole นี้ | Response: `Boolean!` --- #### removeOrgRoleFromUser ลบ OrgRole ให้กับ User ในการใช้งานระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `removeOrgRoleFromUser` ```graphql mutation RemoveOrgRoleFromUser($userOrgRoleId: ID!) { removeOrgRoleFromUser(userOrgRoleId: $userOrgRoleId) { _id authId profile { _id appKey firstName middleName lastName displayName } organizationRoleKey organizationRole { _id organizationKey organizationRoleKey title subTitle description } organizationKey organizationPath isActive createdAt updatedAt # ... } } ``` | argument | Type | | --- | --- | | userOrgRoleId | `ID!` | Response: `UserOrgRole!` --- #### updateUserOrgRole อัปเดต OrgRole ให้กับ User ในการใช้งานระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateUserOrgRole` ```graphql mutation UpdateUserOrgRole($updateUserOrgRoleInput: [UpdateUserOrgRoleInput]!) { updateUserOrgRole(updateUserOrgRoleInput: $updateUserOrgRoleInput) { _id authId profile { _id appKey firstName middleName lastName displayName } organizationRoleKey organizationRole { _id organizationKey organizationRoleKey title subTitle description } organizationKey organizationPath isActive createdAt updatedAt # ... } } ``` `updateUserOrgRoleInput`: `UpdateUserOrgRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String!` | ใช่ | ข้อมูล AppKey สำหรับใช้ในการระบุ App ที่ต้องการให้ User ใช้งาน | | authId | `String!` | ใช่ | ข้อมูล AuthId สำหรับใช้ในการระบุ User ที่ต้องการให้ใช้งาน OrgRole | | organizationRoleId | `String!` | ใช่ | ข้อมูล organizationRoleId สำหรับใช้ในการระบุ OrgRole ที่ต้องการให้ User ใช้งาน | | isActive | `Boolean!` | ใช่ | ระบุสถานะของการใช้งานของ OrgRole นี้ | Response: `[UserOrgRole]!` --- #### reCalculateUserPolicy ทำการคำนวณ UserPolicy ใหม่ โดยจะคำนวณจาก UserPermission ทั้งหมด และสร้าง UserPolicy ใหม่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `reCalculateUserPolicy` ```graphql mutation ReCalculateUserPolicy($appKey: String!) { reCalculateUserPolicy(appKey: $appKey) } ``` | argument | Type | | --- | --- | | appKey | `String!` | Response: `Boolean!` ---

## Kafka consume Reference ทุกข้อความมี header `appKey` และ `serviceKey` (service ปลายทาง) · payload อยู่ในรูป `{ <ข้อมูล>: {...}, action: "ADD" | "REMOVE" | ... }` · ข้อความของแอปที่ service นี้ไม่มี AppCertificate จะถูกข้าม --- ### topic มาตรฐาน | topic | ใช้ทำอะไร | | --- | --- | | `init-system` | ตั้งระบบจากศูนย์ (เฉพาะ core set) | | `refresh-data` | core สั่งให้ส่งข้อมูลที่ถืออยู่ขึ้นไปใหม่ + ล้าง cache · ACL จะส่ง `sync-permission` และ `sync-invite-code` และดึงเมนูของหน้าบ้านใหม่ | | `sync-app-certificate` | รับ AppCertificate ของ service ต่อแอป จาก core | | `sync-app-credential` | รับ AppCredential ของแอป จาก Authentication Service ใช้ตรวจ token / header | | `sync-service-setting` / `sync-app-service-setting` | รับค่าตั้งค่าเพิ่มเติมแบบ JSON ทั้งระบบ / รายแอป | | `sync-application` | รับข้อมูลแอป | --- ### sync-permission รับ permission จากทุก service (รายละเอียด payload ดู [ลำดับการทำงานของสิทธิ์](#permission-flow)) topic: sync-permission --- ### sync-service รับทะเบียน service จาก core · ถ้า service เป็นหน้าบ้าน ACL จะดึงรายการเมนูจาก `urlGetMetaData` มาสร้าง Custom Menu topic: sync-service | key | Type | คำอธิบาย | | --- | --- | --- | | service.serviceKey | string | serviceKey | | service.name / description / version | string | ข้อมูล service | | service.isCoreSet | boolean | เป็น service ใน core set | | service.isActive | boolean | เปิดใช้งาน | | service.type | string | `BACKEND`, `MAIN_FRONTEND`, `MICRO_FRONTEND`, `OTHER` | | service.urlFrontend | string | URL ของหน้าบ้าน | | service.urlGetMetaData | string | URL ที่คืนรายการเมนูของหน้าบ้าน | --- ### sync-app-role / sync-organization-role / sync-default-organization-role รับนิยาม role จาก [Unit Service](unitService.md) เพื่อใช้ผูก permission / เมนู / ผู้ใช้ · เมื่อได้ organizationRole ที่สร้างจาก defaultOrganizationRole ACL จะคัดลอก permission และเมนูของแม่แบบให้อัตโนมัติ topic: sync-app-role topic: sync-organization-role topic: sync-default-organization-role | key | คำอธิบาย | | --- | --- | | appRole | `id`, `appKey`, `appRoleKey`, `title`, `subTitle`, `description`, `isInvite`, `isActive`, ... (`isAdmin: true` สำหรับ role admin ที่สร้างตอนเพิ่มแอป) | | organizationRole | `id`, `appKey`, `organizationId`, `organizationKey`, `organizationPath`, `organizationRoleKey`, `title`, `isInvite`, `isActive`, ... (`defaultOrganizationRoleId` / `defaultOrganizationRoleKey` เมื่อสร้างจากแม่แบบ) | | defaultOrganizationRole | `id`, `appKey`, `defaultOrganizationRoleKey`, `title`, `isActive`, ... | | action | `ADD`, `REMOVE` | --- ### sync-organization รับข้อมูลองค์กรจาก [Unit Service](unitService.md) (ใช้โครงต้นไม้ `organizationPath` ในการแตกสิทธิ์ลงองค์กรลูก) topic: sync-organization --- ### sync-auth รับข้อมูลบัญชีใหม่จาก [Authentication Service](authenticationService.md) · ถ้าสมัครด้วย invite code จะผูก appRole / organizationRole ตาม invite code ให้ผู้ใช้อัตโนมัติ topic: sync-auth --- ### sync-profile รับข้อมูลโปรไฟล์จาก [Profile Service](userService.md) ใช้แสดงคู่กับ role (`getProfileWithAppRoles`, `getProfileWithOrgRoles`) topic: sync-profile --- ### set-user-role ให้ service อื่นกำหนด role ให้ผู้ใช้ผ่าน Kafka topic: set-user-role | key | Type | คำอธิบาย | | --- | --- | --- | | userRole.authId | string | ผู้ใช้ | | userRole.appKey | string | appKey | | userRole.appRoleIdList | string[] | appRole ที่ต้องการให้ | | userRole.orgRoleIdList | string[] | organizationRole ที่ต้องการให้ | | action | string | `ADD` | --- ### schedule-alarm รับการแจ้งเตือนตามเวลาจาก Schedule Service เพื่อเปิด / ปิด invite code ตาม `validStartTime` / `validEndTime` แล้วตอบผลทาง `schedule-alarm-result` topic: schedule-alarm --- ### sync-generate-user-policy / sync-generate-user-custom-menu คิวงานภายในของ ACL เอง (ACL ส่งให้ตัวเอง) ใช้คำนวณ UserPolicy และเมนูของผู้ใช้หลังมีการเปลี่ยน role / permission / เมนู topic: sync-generate-user-policy topic: sync-generate-user-custom-menu ---

## Kafka Produce Reference --- ### sync-user-policy ส่ง UserPolicy ให้ service เจ้าของ permission · header `serviceKey` = service ปลายทาง (service ต้องรับเฉพาะข้อความที่ `serviceKey` ตรงกับตัวเอง) topic: sync-user-policy | key | Type | คำอธิบาย | | --- | --- | --- | | userPolicy._id | string | id | | userPolicy.appKey | string | appKey | | userPolicy.userPolicyKey | string | key ตามรูปแบบใน [ลำดับการทำงานของสิทธิ์](#permission-flow) | | userPolicy.serviceKey | string | service เจ้าของ permission | | userPolicy.permissionKey | string | permission | | userPolicy.authId | string | ผู้ใช้ | | userPolicy.organizationId | string | องค์กร (เฉพาะระดับองค์กร) | | action | string | ดูตารางด้านล่าง | | action | ข้อมูลที่ส่งมา | ความหมาย | | --- | --- | --- | | `ADD` | `userPolicy` | เพิ่ม UserPolicy | | `REMOVE` | `userPolicy` | ลบ UserPolicy ตัวนั้น | | `REMOVE_APP` | `appKey` | ลบ UserPolicy ทั้งหมดของแอป | | `REMOVE_PERMISSION` | `permissionKey` | ลบ UserPolicy ทั้งหมดของ permission นั้น | | `REMOVE_USER` | `authId`, `appKey` | ลบ UserPolicy ทั้งหมดของผู้ใช้ในแอป | | `REMOVE_ORGANIZATION` | `oranizationId` (สะกดตามโค้ด), `appKey` | ลบ UserPolicy ทั้งหมดขององค์กร | --- ### sync-invite-code ส่งข้อมูล invite code ให้ [Authentication Service](authenticationService.md) ใช้ตอนสมัคร topic: sync-invite-code | key | Type | คำอธิบาย | | --- | --- | --- | | inviteCode.id / inviteCodeKey | string | id และรหัสเชิญ | | inviteCode.title / subTitle / description | string | ข้อมูลแสดงผล | | inviteCode.defaultOrganizationId / defaultOrganizationKey | string | องค์กรเริ่มต้น | | inviteCode.maxUses / currentUses | number | จำนวนครั้งที่ใช้ได้ / ใช้ไปแล้ว | | inviteCode.isPublic / isDefault / isActive | boolean | สถานะ | | inviteCode.validStartTime / validEndTime | Date | ช่วงเวลาที่ใช้ได้ | | inviteCode.appRoleIds / orgRoleIds | string[] | role ที่จะได้รับ | | action | string | `ADD`, `REMOVE` | --- ### set-schedule / schedule-alarm-result ลงทะเบียนนัดกับ Schedule Service สำหรับเวลาเริ่ม / หมดอายุของ invite code (`schedule.scheduleRefKey` = `:valid` หรือ `:expired`) และตอบผลหลังได้ `schedule-alarm` topic: set-schedule topic: schedule-alarm-result --- ### create-notification ส่งคำขอแจ้งเตือนไปที่ Notification Service topic: create-notification --- ### sync-permission ส่ง permission ของ `access-control` เอง (ตอนได้ `refresh-data`) topic: sync-permission --- ### sync-app-role-permission / sync-organization-role-permission ประกาศการผูก permission กับ appRole / organizationRole เมื่อมีการเปลี่ยนแปลง (สำหรับ service ที่ต้องการติดตาม) topic: sync-app-role-permission topic: sync-organization-role-permission --- ### sync-generate-user-policy / sync-generate-user-custom-menu คิวงานภายใน (ดู consume ด้านบน) --- > อัปเดตจากโค้ด gumon-access-control-service@2bca76f · 2026-10-05 --- # Unit Service Service สำหรับจัดการโครงสร้างองค์กรและ role ของแต่ละแอป serviceKey: unit หน้าที่หลัก - **Organization** — องค์กร / หน่วยงาน เป็นโครงต้นไม้ (`parentOrganizationId`, `organizationPath`) มีองค์กรสาธารณะ (`isPublic`) และการอนุมัติองค์กร - **Organization Type / Organization Tag** — ประเภทและแท็กขององค์กร (เป็นต้นไม้เช่นกัน) - **Organization Approve** — ใบอนุมัติองค์กรและรายละเอียดประกอบ สถานะ `INPROGRESS`, `APPROVED`, `REJECTED`, `NEED_MORE_INFORMATION` - **Contact** — ผู้ติดต่อขององค์กร (`organizationContact`), บุคคลติดต่อ (`organizationContactPeople`), ความสัมพันธ์ (`organizationContactRelation`) และประเภทลูกค้า (`customerType`) - **Role** — นิยาม `appRole` (ทั้งแอป), `organizationRole` (ต่อองค์กร) และ `defaultOrganizationRole` (แม่แบบ role ที่ทุกองค์กรใหม่จะได้อัตโนมัติ) · การผูก permission / เมนู / ผู้ใช้เข้ากับ role ทำที่ [ACL Service](aclService.md) - **Custom Running Number** — เลขรันนิ่ง (เช่น เลขเอกสาร) ระดับแอปหรือองค์กร ตาม pattern / รอบ ปี-เดือน-วัน หน้าที่ของ Label Service เดิม (label ขององค์กร หน่วยงาน ตำแหน่ง role) ถูกรวมมาไว้ที่ service นี้แล้ว ในรูปของ organization, organization type / tag และ role ข้างบน · ดู [Label Service (เลิกใช้)](labelService.md) การสร้างองค์กรใหม่ จะสร้าง organizationRole จาก defaultOrganizationRole ทุกตัวให้อัตโนมัติ และสร้างใบอนุมัติองค์กร
- [API Reference](#api-reference) - [kafka consume Reference](#kafka-consume-reference) - [Kafka Produce Reference](#kafka-produce-reference) ---

## API Reference การยืนยันตัวตนและ header ดูที่ [Authentication Service](authenticationService.md#app-credential) · "สิทธิ์" คือ permissionKey ของ service `unit` ที่ได้รับผ่าน role ใน [ACL Service](aclService.md) · "ระดับองค์กร" หมายถึงสิทธิ์ที่ให้แยกตามองค์กร นอกจาก GraphQL มี REST สำหรับนำเข้าผู้ติดต่อจากไฟล์ CSV: `POST /organization-contact/import` (multipart: `file` เป็น CSV, `organizationKey`) · ต้อง login · สิทธิ์ `importOrganizationContact` --- ### Query เป็น API ที่ใช้สำหรับการ Query ข้อมูลออกมา ไม่มีการแก้ไข Data --- #### getAppRoles ดึงข้อมูล appRoles ทั้งหมดที่มีในระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getAppRoles` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetAppRoles($input: GetAppRoleInput) { getAppRoles(input: $input) { appRoles { _id appRoleKey title subTitle description systemNote } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetAppRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetAppRoleFilterInput` | | | | search | `GetAppRoleSearchInput` | | | | sort | `GetAppRoleSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `AppRolePagination!` --- #### getAppRoleById ดึงข้อมูล appRoles ตาม ID ถ้าอยากค้นหาข้าม appKey จำเป็นต้องระบุ input.appKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getAppRoleById` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetAppRoleById($appRoleId: ID!, $appKey: String) { getAppRoleById(appRoleId: $appRoleId, appKey: $appKey) { _id appRoleKey title subTitle description systemNote isInvite isActive createdBy updatedBy # ... } } ``` | argument | Type | | --- | --- | | appRoleId | `ID!` | | appKey | `String` | Response: `AppRole!` --- #### getAppRoleByKey ดึงข้อมูล appRoles ตาม appRoleKey ถ้าอยากค้นหาข้าม appKey จำเป็นต้องระบุ input.appKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getAppRoleByKey` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetAppRoleByKey($appRoleKey: String!, $appKey: String) { getAppRoleByKey(appRoleKey: $appRoleKey, appKey: $appKey) { _id appRoleKey title subTitle description systemNote isInvite isActive createdBy updatedBy # ... } } ``` | argument | Type | | --- | --- | | appRoleKey | `String!` | | appKey | `String` | Response: `AppRole!` --- #### getCustomRunningNumberLogs ดึงข้อมูล custom running number log ทั้งหมด - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomRunningNumberLogs` ```graphql query GetCustomRunningNumberLogs($getInput: GetCustomRunningNumberLogInput) { getCustomRunningNumberLogs(getInput: $getInput) { customRunningNumberLogs { _id organizationId organizationKey customRunningNumberId customRunningNumberKey generatedCode } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetCustomRunningNumberLogInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetCustomRunningNumberLogFilterInput` | | | | search | `GetCustomRunningNumberLogSearchInput` | | | | sort | `GetCustomRunningNumberLogSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `CustomRunningNumberLogPagination` --- #### getCustomRunningNumberLogByOrganizationKey ดึงข้อมูل custom running number log ตาม organization key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomRunningNumberLogByOrganizationKey` (ระดับองค์กร) ```graphql query GetCustomRunningNumberLogByOrganizationKey($organizationKey: String!, $getInput: GetCustomRunningNumberLogByOrganizationKeyInput) { getCustomRunningNumberLogByOrganizationKey(organizationKey: $organizationKey, getInput: $getInput) { customRunningNumberLogs { _id organizationId organizationKey customRunningNumberId customRunningNumberKey generatedCode } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetCustomRunningNumberLogByOrganizationKeyInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetCustomRunningNumberLogByOrganizationKeyFilterInput` | | | | search | `GetCustomRunningNumberLogByOrganizationKeySearchInput` | | | | sort | `GetCustomRunningNumberLogSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `organizationKey: String!` Response: `CustomRunningNumberLogPagination` --- #### getCustomRunningNumberLogById ดึงข้อมูล custom running number log ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomRunningNumberLogById` (ระดับองค์กร) ```graphql query GetCustomRunningNumberLogById($customRunningNumberLogId: String!) { getCustomRunningNumberLogById(customRunningNumberLogId: $customRunningNumberLogId) { _id organizationId organizationKey customRunningNumberId customRunningNumberKey generatedCode createdBy updatedBy createdAt updatedAt # ... } } ``` | argument | Type | | --- | --- | | customRunningNumberLogId | `String!` | Response: `CustomRunningNumberLog` --- #### getCustomRunningNumberLogByCustomRunningNumberId ดึงข้อมูล custom running number log ตาม custom running number id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomRunningNumberLogByCustomRunningNumberId` (ระดับองค์กร) ```graphql query GetCustomRunningNumberLogByCustomRunningNumberId($customRunningNumberId: String!, $getInput: GetCustomRunningNumberLogByCustomRunningNumberIdInput) { getCustomRunningNumberLogByCustomRunningNumberId(customRunningNumberId: $customRunningNumberId, getInput: $getInput) { customRunningNumberLogs { _id organizationId organizationKey customRunningNumberId customRunningNumberKey generatedCode } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetCustomRunningNumberLogByCustomRunningNumberIdInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetCustomRunningNumberLogByCustomRunningNumberIdFilterInput` | | | | search | `GetCustomRunningNumberLogByCustomRunningNumberIdSearchInput` | | | | sort | `GetCustomRunningNumberLogSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `customRunningNumberId: String!` Response: `CustomRunningNumberLogPagination` --- #### getCustomRunningNumbers ดึงข้อมูล custom running number ทั้งหมด - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomRunningNumbers` ```graphql query GetCustomRunningNumbers($getInput: GetCustomRunningNumberInput) { getCustomRunningNumbers(getInput: $getInput) { customRunningNumbers { _id level organizationId organizationKey customRunningNumberKey refKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetCustomRunningNumberInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetCustomRunningNumberFilterInput` | | | | search | `GetCustomRunningNumberSearchInput` | | | | sort | `GetCustomRunningNumberSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `CustomRunningNumberPagination` --- #### getCustomRunningNumberByOrganizationKey ดึงข้อมูล custom running number ตาม organization key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomRunningNumberByOrganizationKey` (ระดับองค์กร) ```graphql query GetCustomRunningNumberByOrganizationKey($organizationKey: String!, $getInput: GetCustomRunningNumberByOrganizationKeyInput) { getCustomRunningNumberByOrganizationKey(organizationKey: $organizationKey, getInput: $getInput) { customRunningNumbers { _id level organizationId organizationKey customRunningNumberKey refKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetCustomRunningNumberByOrganizationKeyInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetCustomRunningNumberByOrganizationKeyFilterInput` | | | | search | `GetCustomRunningNumberByOrganizationKeySearchInput` | | | | sort | `GetCustomRunningNumberSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `organizationKey: String!` Response: `CustomRunningNumberPagination` --- #### getCustomRunningNumberById ดึงข้อมูล custom running number ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomRunningNumberById` (ระดับองค์กร) ```graphql query GetCustomRunningNumberById($customRunningNumberId: String!) { getCustomRunningNumberById(customRunningNumberId: $customRunningNumberId) { _id level organizationId organizationKey customRunningNumberKey refKey order title description serial # ... } } ``` | argument | Type | | --- | --- | | customRunningNumberId | `String!` | Response: `CustomRunningNumber` --- #### getCustomRunningNumberByKey ดึงข้อมูล custom running number ตาม key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomRunningNumberByKey` (ระดับองค์กร) ```graphql query GetCustomRunningNumberByKey($customRunningNumberKey: String!) { getCustomRunningNumberByKey(customRunningNumberKey: $customRunningNumberKey) { _id level organizationId organizationKey customRunningNumberKey refKey order title description serial # ... } } ``` | argument | Type | | --- | --- | | customRunningNumberKey | `String!` | Response: `CustomRunningNumber` --- #### getCustomRunningNumberByRefKey ดึงข้อมูล custom running number ตาม ref key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomRunningNumberByRefKey` (ระดับองค์กร) ```graphql query GetCustomRunningNumberByRefKey($refKey: String!, $getInput: GetCustomRunningNumberByRefKeyInput) { getCustomRunningNumberByRefKey(refKey: $refKey, getInput: $getInput) { customRunningNumbers { _id level organizationId organizationKey customRunningNumberKey refKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetCustomRunningNumberByRefKeyInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetCustomRunningNumberByRefKeyFilterInput` | | | | search | `GetCustomRunningNumberByRefKeySearchInput` | | | | sort | `GetCustomRunningNumberSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `refKey: String!` Response: `CustomRunningNumberPagination` --- #### getNextRunningNumber ขอ running number ถัดไป ตาม custom running number id ดูเลขถัดไปโดยยังไม่บันทึกการใช้ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getNextRunningNumber` (ระดับองค์กร) ```graphql query GetNextRunningNumber($customRunningNumberId: String!) { getNextRunningNumber(customRunningNumberId: $customRunningNumberId) } ``` | argument | Type | | --- | --- | | customRunningNumberId | `String!` | Response: `String` --- #### getCustomerTypes ดึงข้อมูล customer type ทั้งหมด - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomerTypes` ```graphql query GetCustomerTypes($getInput: GetCustomerTypeInput) { getCustomerTypes(getInput: $getInput) { customerTypes { _id organizationId organizationKey customerTypeKey customerTypeCode title } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetCustomerTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetCustomerTypeFilterInput` | | | | search | `GetCustomerTypeSearchInput` | | | | sort | `GetCustomerTypeSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `CustomerTypePagination` --- #### getCustomerTypeByOrganizationKey ดึงข้อมูล customer type ตาม organization key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomerTypeByOrganizationKey` (ระดับองค์กร) ```graphql query GetCustomerTypeByOrganizationKey($organizationKey: String!, $getInput: GetCustomerTypeByOrganizationKeyInput) { getCustomerTypeByOrganizationKey(organizationKey: $organizationKey, getInput: $getInput) { customerTypes { _id organizationId organizationKey customerTypeKey customerTypeCode title } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetCustomerTypeByOrganizationKeyInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetCustomerTypeByOrganizationKeyFilterInput` | | | | search | `GetCustomerTypeByOrganizationKeySearchInput` | | | | sort | `GetCustomerTypeSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `organizationKey: String!` Response: `CustomerTypePagination` --- #### getCustomerTypeById ดึงข้อมูล customer type ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomerTypeById` (ระดับองค์กร) ```graphql query GetCustomerTypeById($customerTypeId: String!) { getCustomerTypeById(customerTypeId: $customerTypeId) { _id organizationId organizationKey customerTypeKey customerTypeCode title subTitle description isActive createdBy # ... } } ``` | argument | Type | | --- | --- | | customerTypeId | `String!` | Response: `CustomerType` --- #### getCustomerTypeByKey ดึงข้อมูล customer type ตาม key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getCustomerTypeByKey` (ระดับองค์กร) ```graphql query GetCustomerTypeByKey($customerTypeKey: String!) { getCustomerTypeByKey(customerTypeKey: $customerTypeKey) { _id organizationId organizationKey customerTypeKey customerTypeCode title subTitle description isActive createdBy # ... } } ``` | argument | Type | | --- | --- | | customerTypeKey | `String!` | Response: `CustomerType` --- #### getDefaultOrganizationRoles ดึงข้อมูล defaultOrganizationRoles ทั้งหมดที่มีในระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getDefaultOrganizationRoles` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetDefaultOrganizationRoles($getInput: GetDefaultOrganizationRoleInput, $appKey: String) { getDefaultOrganizationRoles(getInput: $getInput, appKey: $appKey) { defaultOrganizationRoles { _id defaultOrganizationRoleKey title subTitle description systemNote } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetDefaultOrganizationRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetDefaultOrganizationRoleFilterInput` | | | | search | `GetDefaultOrganizationRoleSearchInput` | | | | sort | `GetDefaultOrganizationRoleSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `appKey: String` Response: `DefaultOrganizationRolePagination!` --- #### getDefaultOrganizationRoleById ดึงข้อมูล defaultOrganizationRole ตาม ID - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getDefaultOrganizationRoleById` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetDefaultOrganizationRoleById($defaultOrganizationRoleId: ID!, $appKey: String) { getDefaultOrganizationRoleById(defaultOrganizationRoleId: $defaultOrganizationRoleId, appKey: $appKey) { _id defaultOrganizationRoleKey title subTitle description systemNote isActive createdBy updatedBy createdAt # ... } } ``` | argument | Type | | --- | --- | | defaultOrganizationRoleId | `ID!` | | appKey | `String` | Response: `DefaultOrganizationRole!` --- #### getOrganizationApproveDetails ดึงข้อมูล Details ของใบอนุมัติ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationApproveDetails` ```graphql query GetOrganizationApproveDetails($organizationKey: String!, $input: GetOrganizationApproveDetailInput) { getOrganizationApproveDetails(organizationKey: $organizationKey, input: $input) { organizationApproveDetails { _id organizationId organizationKey organizationApproveId timeStamp isAuto } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrganizationApproveDetailInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrganizationApproveDetailFilterInput` | | | | search | `GetOrganizationApproveDetailSearchInput` | | | | sort | `GetOrganizationApproveDetailSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `organizationKey: String!` Response: `OrganizationApproveDetailPagination!` --- #### getOrganizationApproveDetailById ดึงข้อมูล Details ของใบอนุมัติตาม ID - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationApproveDetailById` (ระดับองค์กร) ```graphql query GetOrganizationApproveDetailById($organizationApproveDetailId: ID!) { getOrganizationApproveDetailById(organizationApproveDetailId: $organizationApproveDetailId) { _id organizationId organizationKey organizationApproveId timeStamp isAuto isActive text files images # ... } } ``` | argument | Type | | --- | --- | | organizationApproveDetailId | `ID!` | Response: `OrganizationApproveDetail!` --- #### getOrganizationApproves ดึงข้อมูล organizationApprove ทั้งหมดที่มีในระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationApprove` ```graphql query GetOrganizationApproves($input: GetOrganizationApproveInput) { getOrganizationApproves(input: $input) { organizationApproves { _id organizationId organizationKey approveStatus createdBy updatedBy } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrganizationApproveInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrganizationApproveFilterInput` | | | | search | `GetOrganizationApproveSearchInput` | | | | sort | `GetOrganizationApproveSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `OrganizationApprovePagination!` --- #### getOrganizationApproveById ดึงข้อมูล organizationApprove ตาม ID - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationApproveById` (ระดับองค์กร) ```graphql query GetOrganizationApproveById($organizationApproveId: ID!) { getOrganizationApproveById(organizationApproveId: $organizationApproveId) { _id organizationId organizationKey organization { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey } approveStatus createdBy updatedBy createdAt updatedAt } } ``` | argument | Type | | --- | --- | | organizationApproveId | `ID!` | Response: `OrganizationApprove!` --- #### getOrganizationApproveByKey ดึงข้อมูล organizationApprove ตาม organizationKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationApproveByKey` (ระดับองค์กร) ```graphql query GetOrganizationApproveByKey($organizationKey: String!) { getOrganizationApproveByKey(organizationKey: $organizationKey) { _id organizationId organizationKey organization { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey } approveStatus createdBy updatedBy createdAt updatedAt } } ``` | argument | Type | | --- | --- | | organizationKey | `String!` | Response: `OrganizationApprove!` --- #### getOrganizationContactPeoples ดึงข้อมูล organizationContactPeoples ทั้งหมดที่มีในระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationContactPeoples` (ระดับองค์กร) ```graphql query GetOrganizationContactPeoples($input: GetOrganizationContactPeopleInput) { getOrganizationContactPeoples(input: $input) { organizationContactPeoples { _id organizationId organizationKey organizationPath organizationContactPeopleKey name } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrganizationContactPeopleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrganizationContactPeopleFilterInput` | | | | search | `GetOrganizationContactPeopleSearchInput` | | | | sort | `GetOrganizationContactPeopleSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `OrganizationContactPeoplePagination!` --- #### getOrganizationContactPeopleById ดึงข้อมูล organizationContactPeoples ตาม ID - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationContactPeopleById` (ระดับองค์กร) ```graphql query GetOrganizationContactPeopleById($organizationContactPeopleId: ID!) { getOrganizationContactPeopleById(organizationContactPeopleId: $organizationContactPeopleId) { _id organizationId organizationKey organizationPath organizationContactPeopleKey name description address { address1 address2 address3 city subdivision postalCode } phoneNumber position # ... } } ``` | argument | Type | | --- | --- | | organizationContactPeopleId | `ID!` | Response: `OrganizationContactPeople!` --- #### getOrganizationContactPeopleByKey ดึงข้อมูล organizationContactPeoples ตาม organizationContactPeopleKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationContactPeopleByKey` (ระดับองค์กร) ```graphql query GetOrganizationContactPeopleByKey($organizationContactPeopleKey: String!) { getOrganizationContactPeopleByKey(organizationContactPeopleKey: $organizationContactPeopleKey) { _id organizationId organizationKey organizationPath organizationContactPeopleKey name description address { address1 address2 address3 city subdivision postalCode } phoneNumber position # ... } } ``` | argument | Type | | --- | --- | | organizationContactPeopleKey | `String!` | Response: `OrganizationContactPeople!` --- #### getOrganizationContactRelations ดึงข้อมูล organizationContactRelations ทั้งหมดที่มีในระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationContactRelations` ```graphql query GetOrganizationContactRelations($input: GetOrganizationContactRelationInput) { getOrganizationContactRelations(input: $input) { organizationContactRelations { _id organizationId organizationKey organizationPath organizationContactRelationKey organizationContactId } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrganizationContactRelationInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrganizationContactRelationFilterInput` | | | | search | `GetOrganizationContactRelationSearchInput` | | | | sort | `GetOrganizationContactRelationSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `OrganizationContactRelationPagination` --- #### getOrganizationContactRelationById ดึงข้อมูล organizationContactRelation ตาม ID - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationContactRelationById` (ระดับองค์กร) ```graphql query GetOrganizationContactRelationById($relationId: ID!) { getOrganizationContactRelationById(relationId: $relationId) { _id organizationId organizationKey organizationPath organizationContactRelationKey organizationContactId organizationContactKey organizationContact { _id organizationId organizationKey organizationPath organizationContactKey organizationContactCode } organizationContactPeopleId organizationContactPeopleKey # ... } } ``` | argument | Type | | --- | --- | | relationId | `ID!` | Response: `OrganizationContactRelation` --- #### getOrganizationContactRelationByKey ดึงข้อมูล organizationContactRelation ตาม organizationContactRelationKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationContactRelationByKey` (ระดับองค์กร) ```graphql query GetOrganizationContactRelationByKey($relationKey: String!) { getOrganizationContactRelationByKey(relationKey: $relationKey) { _id organizationId organizationKey organizationPath organizationContactRelationKey organizationContactId organizationContactKey organizationContact { _id organizationId organizationKey organizationPath organizationContactKey organizationContactCode } organizationContactPeopleId organizationContactPeopleKey # ... } } ``` | argument | Type | | --- | --- | | relationKey | `String!` | Response: `OrganizationContactRelation` --- #### getOrganizationContacts ดึงข้อมูล organizationContacts ทั้งหมดที่มีในระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationContacts` ```graphql query GetOrganizationContacts($input: GetOrganizationContactInput) { getOrganizationContacts(input: $input) { organizationContacts { _id organizationId organizationKey organizationPath organizationContactKey organizationContactCode } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrganizationContactInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrganizationContactFilterInput` | | | | search | `GetOrganizationContactSearchInput` | | | | sort | `GetOrganizationContactSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `OrganizationContactPagination!` --- #### getOrganizationContactById ดึงข้อมูล organizationContacts ตาม ID - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationContactById` (ระดับองค์กร) ```graphql query GetOrganizationContactById($organizationContactId: ID!) { getOrganizationContactById(organizationContactId: $organizationContactId) { _id organizationId organizationKey organizationPath organizationContactKey organizationContactCode name name2 name3 description # ... } } ``` | argument | Type | | --- | --- | | organizationContactId | `ID!` | Response: `OrganizationContact!` --- #### getOrganizationContactByKey ดึงข้อมูล organizationContacts ตาม organizationContactKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationContactByKey` (ระดับองค์กร) ```graphql query GetOrganizationContactByKey($organizationContactKey: String!) { getOrganizationContactByKey(organizationContactKey: $organizationContactKey) { _id organizationId organizationKey organizationPath organizationContactKey organizationContactCode name name2 name3 description # ... } } ``` | argument | Type | | --- | --- | | organizationContactKey | `String!` | Response: `OrganizationContact!` --- #### getOrganizationContactByTaxID ดึงข้อมูล organizationContacts ตาม taxIdentificationNumber - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetOrganizationContactByTaxID($organizationKey: String!, $taxIdentificationNumber: String!) { getOrganizationContactByTaxID(organizationKey: $organizationKey, taxIdentificationNumber: $taxIdentificationNumber) { _id organizationId organizationKey organizationPath organizationContactKey organizationContactCode name name2 name3 description # ... } } ``` | argument | Type | | --- | --- | | organizationKey | `String!` | | taxIdentificationNumber | `String!` | Response: `OrganizationContact` --- #### getOrganizationRoles ดึงข้อมูล organizationRoles ทั้งหมดที่มีในระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationRoles` ```graphql query GetOrganizationRoles($input: GetOrganizationRoleInput) { getOrganizationRoles(input: $input) { organizationRoles { _id organizationId organizationKey organizationRoleKey title subTitle } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrganizationRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrganizationRoleFilterInput` | | | | search | `GetOrganizationRoleSearchInput` | | | | sort | `GetOrganizationRoleSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `OrganizationRolePagination!` --- #### getOrganizationRolesByOrganizationKey ดึงข้อมูล organizationRoles ตาม organizationKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationRolesByOrganizationKey` (ระดับองค์กร) ```graphql query GetOrganizationRolesByOrganizationKey($organizationKey: String!, $input: GetOrganizationRoleInput) { getOrganizationRolesByOrganizationKey(organizationKey: $organizationKey, input: $input) { organizationRoles { _id organizationId organizationKey organizationRoleKey title subTitle } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrganizationRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrganizationRoleFilterInput` | | | | search | `GetOrganizationRoleSearchInput` | | | | sort | `GetOrganizationRoleSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `organizationKey: String!` Response: `OrganizationRolePagination!` --- #### getOrganizationRoleById ดึงข้อมูล organizationRoles ตาม ID - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationRoleById` (ระดับองค์กร) ```graphql query GetOrganizationRoleById($organizationRoleId: ID!) { getOrganizationRoleById(organizationRoleId: $organizationRoleId) { _id organizationId organizationKey organizationRoleKey title subTitle description systemNote isInvite isActive # ... } } ``` | argument | Type | | --- | --- | | organizationRoleId | `ID!` | Response: `OrganizationRole!` --- #### getOrganizationRoleByKey ดึงข้อมูล organizationRoles ตาม organizationRoleKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationRoleByKey` (ระดับองค์กร) ```graphql query GetOrganizationRoleByKey($organizationRoleKey: String!, $organizationKey: String!) { getOrganizationRoleByKey(organizationRoleKey: $organizationRoleKey, organizationKey: $organizationKey) { _id organizationId organizationKey organizationRoleKey title subTitle description systemNote isInvite isActive # ... } } ``` | argument | Type | | --- | --- | | organizationRoleKey | `String!` | | organizationKey | `String!` | Response: `OrganizationRole!` --- #### getOrganizationTags ดึงข้อมูล organizationTags ทั้งหมดที่มีในระบบ - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetOrganizationTags($input: GetOrganizationTagInput) { getOrganizationTags(input: $input) { organizationTags { _id organizationTagKey organizationTagPath parentOrganizationTagId title subTitle } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrganizationTagInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrganizationTagFilterInput` | | | | search | `GetOrganizationTagSearchInput` | | | | sort | `GetOrganizationTagSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `OrganizationTagPagination!` --- #### getOrganizationTagById ดึงข้อมูล organizationTags ตาม ID - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetOrganizationTagById($organizationTagId: ID!) { getOrganizationTagById(organizationTagId: $organizationTagId) { _id organizationTagKey organizationTagPath parentOrganizationTagId title subTitle description icon systemNote isActive # ... } } ``` | argument | Type | | --- | --- | | organizationTagId | `ID!` | Response: `OrganizationTag!` --- #### getOrganizationTagByKey ดึงข้อมูล organizationTags ตาม - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetOrganizationTagByKey($organizationTagKey: String!) { getOrganizationTagByKey(organizationTagKey: $organizationTagKey) { _id organizationTagKey organizationTagPath parentOrganizationTagId title subTitle description icon systemNote isActive # ... } } ``` | argument | Type | | --- | --- | | organizationTagKey | `String!` | Response: `OrganizationTag!` --- #### getOrganizationTypes ดึงข้อมูล organizationTypes ทั้งหมดที่มีในระบบ - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetOrganizationTypes($input: GetOrganizationTypeInput) { getOrganizationTypes(input: $input) { organizationTypes { _id organizationTypeKey organizationTypePath parentOrganizationTypeId title subTitle } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrganizationTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrganizationTypeFilterInput` | | | | search | `GetOrganizationTypeSearchInput` | | | | sort | `GetOrganizationTypeSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `OrganizationTypePagination!` --- #### getOrganizationTypeById ดึงข้อมูล organizationTypes ตาม ID - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetOrganizationTypeById($organizationTypeId: ID!) { getOrganizationTypeById(organizationTypeId: $organizationTypeId) { _id organizationTypeKey organizationTypePath parentOrganizationTypeId title subTitle description icon systemNote isActive # ... } } ``` | argument | Type | | --- | --- | | organizationTypeId | `ID!` | Response: `OrganizationType!` --- #### getOrganizationTypeByKey ดึงข้อมูล organizationTypes ตาม - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetOrganizationTypeByKey($organizationTypeKey: String!) { getOrganizationTypeByKey(organizationTypeKey: $organizationTypeKey) { _id organizationTypeKey organizationTypePath parentOrganizationTypeId title subTitle description icon systemNote isActive # ... } } ``` | argument | Type | | --- | --- | | organizationTypeKey | `String!` | Response: `OrganizationType!` --- #### getOrganizations ดึงข้อมูล organizations ปกติเห็นเฉพาะองค์กรที่ผู้ใช้มีสิทธิ์ · ผู้ที่มีสิทธิ์ `queryAllOrganizations` เห็นทุกองค์กรในแอป - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizations` (ระดับองค์กร) ```graphql query GetOrganizations($input: GetOrganizationInput) { getOrganizations(input: $input) { organizations { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrganizationInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrganizationFilterInput` | | | | search | `GetOrganizationSearchInput` | | | | sort | `GetOrganizationSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `OrganizationPagination!` --- #### getOrganizationById ดึงข้อมูล organizations ตาม ID - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationById` (ระดับองค์กร) ```graphql query GetOrganizationById($organizationId: ID!) { getOrganizationById(organizationId: $organizationId) { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey title subTitle description title2 # ... } } ``` | argument | Type | | --- | --- | | organizationId | `ID!` | Response: `Organization!` --- #### getOrganizationByKey ดึงข้อมูล organizations ตาม organizationKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getOrganizationByKey` (ระดับองค์กร) ```graphql query GetOrganizationByKey($organizationKey: String!) { getOrganizationByKey(organizationKey: $organizationKey) { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey title subTitle description title2 # ... } } ``` | argument | Type | | --- | --- | | organizationKey | `String!` | Response: `Organization!` --- #### getPublicOrganizations ดึงข้อมูล organizations ที่ isPublic = true AND isApproved = true AND isActive = true - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetPublicOrganizations($input: GetOrganizationInput) { getPublicOrganizations(input: $input) { organizations { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetOrganizationInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetOrganizationFilterInput` | | | | search | `GetOrganizationSearchInput` | | | | sort | `GetOrganizationSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `OrganizationPagination!` --- #### getPublicOrganizationById ดึงข้อมูล organizations ตาม ID ที่ isPublic = true AND isApproved = true AND isActive = true - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetPublicOrganizationById($organizationId: ID!) { getPublicOrganizationById(organizationId: $organizationId) { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey title subTitle description title2 # ... } } ``` | argument | Type | | --- | --- | | organizationId | `ID!` | Response: `Organization!` --- #### getPublicOrganizationByKey ดึงข้อมูล organizations ตาม organizationKey ที่ isPublic = true AND isApproved = true AND isActive = true - การยืนยันตัวตน: ระดับแอป: header `X-APP-CLIENT-ID` (ไม่ต้อง login) ```graphql query GetPublicOrganizationByKey($organizationKey: String!) { getPublicOrganizationByKey(organizationKey: $organizationKey) { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey title subTitle description title2 # ... } } ``` | argument | Type | | --- | --- | | organizationKey | `String!` | Response: `Organization!` --- #### getUserOrganizations ดึงข้อมูล userOrganizations ทั้งหมดที่มีในระบบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserOrganizations` ```graphql query GetUserOrganizations($getInput: GetUserOrganizationsInput!) { getUserOrganizations(getInput: $getInput) { userOrganizations { _id authId organizationId organizationKey organizationPath } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `getInput`: `GetUserOrganizationsInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `UserOrganizationFilterInput` | | | | search | `UserOrganizationSearchInput` | | | | sort | `UserOrganizationSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `UserOrganizationPagination` --- #### getUserOrganizationById ดึงข้อมูล userOrganization โดย id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getUserOrganization` ```graphql query GetUserOrganizationById($id: String!) { getUserOrganizationById(id: $id) { _id authId organizationId organizationKey organizationPath } } ``` | argument | Type | | --- | --- | | id | `String!` | Response: `UserOrganization` --- ### Mutation เป็น API ที่ใช้สำหรับการแก้ไขข้อมูล --- #### createAppRole สร้างข้อมูล AppRole - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createAppRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation CreateAppRole($createInput: CreateAppRoleInput!) { createAppRole(createInput: $createInput) { _id appRoleKey title subTitle description systemNote isInvite isActive createdBy updatedBy # ... } } ``` `createInput`: `CreateAppRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String` | | สามารถสร้าง ข้าม app ได้โดยการระบุ appkey ที่อยากสร้างลงไป | | appRoleKey | `String` | | | | title | `String!` | ใช่ | | | subTitle | `String` | | | | description | `String` | | | | systemNote | `String` | | | | isInvite | `Boolean` | | | | isActive | `Boolean` | | | Response: `AppRole!` --- #### updateAppRole แก้ไขข้อมูล AppRole - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateAppRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation UpdateAppRole($appRoleId: ID!, $updateInput: UpdateAppRoleInput!) { updateAppRole(appRoleId: $appRoleId, updateInput: $updateInput) { _id appRoleKey title subTitle description systemNote isInvite isActive createdBy updatedBy # ... } } ``` `updateInput`: `UpdateAppRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | title | `String` | | | | subTitle | `String` | | | | description | `String` | | | | systemNote | `String` | | | | isInvite | `Boolean` | | | | isActive | `Boolean` | | | argument อื่น: `appRoleId: ID!` Response: `AppRole!` --- #### deleteAppRole ลบข้อมูล AppRole - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteAppRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation DeleteAppRole($appRoleId: ID!) { deleteAppRole(appRoleId: $appRoleId) { _id appRoleKey title subTitle description systemNote isInvite isActive createdBy updatedBy # ... } } ``` | argument | Type | | --- | --- | | appRoleId | `ID!` | Response: `AppRole!` --- #### deleteCustomRunningNumberLog ลบ custom running number log ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteCustomRunningNumberLog` (ระดับองค์กร) ```graphql mutation DeleteCustomRunningNumberLog($customRunningNumberLogId: String!) { deleteCustomRunningNumberLog(customRunningNumberLogId: $customRunningNumberLogId) { _id organizationId organizationKey customRunningNumberId customRunningNumberKey generatedCode createdBy updatedBy createdAt updatedAt # ... } } ``` | argument | Type | | --- | --- | | customRunningNumberLogId | `String!` | Response: `CustomRunningNumberLog` --- #### createCustomRunningNumber สร้าง custom running number - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createCustomRunningNumber` (ระดับองค์กร) ```graphql mutation CreateCustomRunningNumber($createInput: CreateCustomRunningNumberInput!) { createCustomRunningNumber(createInput: $createInput) { _id level organizationId organizationKey customRunningNumberKey refKey order title description serial # ... } } ``` `createInput`: `CreateCustomRunningNumberInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | level | `EnumLevel` | | level | | organizationKey | `String` | | key ของ org ถ้าเป็น org level ต้องระบุ app level ไม่ต้องระบุ | | customRunningNumberKey | `String` | | key ของ custom running number | | refKey | `String!` | ใช่ | ref key | | order | `Float` | | ลำดับ | | title | `String` | | ชื่อ | | description | `String` | | คำอธิบาย | | serial | `EnumSerialType` | | ประเภท serial | | padding | `Int` | | padding | | pattern | `String!` | ใช่ | pattern | | isDefault | `Boolean` | | เป็น default หรือไม่ | | isActive | `Boolean` | | สถานะการใช้งาน | Response: `CustomRunningNumber!` --- #### updateCustomRunningNumber แก้ไข custom running number ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateCustomRunningNumber` (ระดับองค์กร) ```graphql mutation UpdateCustomRunningNumber($customRunningNumberId: String!, $updateInput: UpdateCustomRunningNumberInput!) { updateCustomRunningNumber(customRunningNumberId: $customRunningNumberId, updateInput: $updateInput) { _id level organizationId organizationKey customRunningNumberKey refKey order title description serial # ... } } ``` `updateInput`: `UpdateCustomRunningNumberInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | order | `Float` | | ลำดับ | | title | `String` | | ชื่อ | | description | `String` | | คำอธิบาย | | serial | `EnumSerialType` | | ประเภท serial | | padding | `Int` | | padding | | pattern | `String` | | pattern | | isDefault | `Boolean` | | เป็น default หรือไม่ | | isActive | `Boolean` | | สถานะการใช้งาน ถ้าถูกใช้อยู่จะไม่สามารถเปลี่ยนแปลงได้ | argument อื่น: `customRunningNumberId: String!` Response: `CustomRunningNumber!` --- #### deleteCustomRunningNumber ลบ custom running number ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteCustomRunningNumber` (ระดับองค์กร) ```graphql mutation DeleteCustomRunningNumber($customRunningNumberId: String!) { deleteCustomRunningNumber(customRunningNumberId: $customRunningNumberId) { _id level organizationId organizationKey customRunningNumberKey refKey order title description serial # ... } } ``` | argument | Type | | --- | --- | | customRunningNumberId | `String!` | Response: `CustomRunningNumber` --- #### useCustomRunningNumber ใช้ custom running number ตาม id ออกเลขถัดไปและบันทึก log ของการใช้เลข - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `useCustomRunningNumber` (ระดับองค์กร) ```graphql mutation UseCustomRunningNumber($useInput: UseCustomRunningNumberInput!) { useCustomRunningNumber(useInput: $useInput) { _id organizationId organizationKey customRunningNumberId customRunningNumberKey generatedCode createdBy updatedBy createdAt updatedAt # ... } } ``` `useInput`: `UseCustomRunningNumberInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | customRunningNumberId | `String!` | ใช่ | key ของ custom running number | | generatedCode | `String!` | ใช่ | code ที่ใช้ | Response: `CustomRunningNumberLog` --- #### createCustomerType สร้าง customer type - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createCustomerType` (ระดับองค์กร) ```graphql mutation CreateCustomerType($createInput: CreateCustomerTypeInput!) { createCustomerType(createInput: $createInput) { _id organizationId organizationKey customerTypeKey customerTypeCode title subTitle description isActive createdBy # ... } } ``` `createInput`: `CreateCustomerTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationKey | `String!` | ใช่ | key ของ org | | customerTypeKey | `String` | | key ของ customer type | | customerTypeCode | `String` | | รหัสของ customer type | | title | `String!` | ใช่ | ชื่อของ customer type | | subTitle | `String` | | ชื่อย่อของ customer type | | description | `String` | | คำอธิบายของ customer type | | isActive | `Boolean` | | สถานะการใช้งาน | Response: `CustomerType!` --- #### updateCustomerType แก้ไข customer type ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateCustomerType` (ระดับองค์กร) ```graphql mutation UpdateCustomerType($customerTypeId: String!, $updateInput: UpdateCustomerTypeInput!) { updateCustomerType(customerTypeId: $customerTypeId, updateInput: $updateInput) { _id organizationId organizationKey customerTypeKey customerTypeCode title subTitle description isActive createdBy # ... } } ``` `updateInput`: `UpdateCustomerTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | customerTypeCode | `String` | | รหัสของ customer type | | title | `String` | | ชื่อของ customer type | | subTitle | `String` | | ชื่อย่อของ customer type | | description | `String` | | คำอธิบายของ customer type | | isActive | `Boolean` | | สถานะการใช้งาน ถ้าถูกใช้อยู่จะไม่สามารถเปลี่ยนแปลงได้ | argument อื่น: `customerTypeId: String!` Response: `CustomerType!` --- #### deleteCustomerType ลบ customer type ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteCustomerType` (ระดับองค์กร) ```graphql mutation DeleteCustomerType($customerTypeId: String!) { deleteCustomerType(customerTypeId: $customerTypeId) { _id organizationId organizationKey customerTypeKey customerTypeCode title subTitle description isActive createdBy # ... } } ``` | argument | Type | | --- | --- | | customerTypeId | `String!` | Response: `CustomerType` --- #### createDefaultOrganizationRole สร้างข้อมูล DefaultOrganizationRole - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createDefaultOrganizationRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation CreateDefaultOrganizationRole($createInput: CreateDefaultOrganizationRoleInput!) { createDefaultOrganizationRole(createInput: $createInput) { _id defaultOrganizationRoleKey title subTitle description systemNote isActive createdBy updatedBy createdAt # ... } } ``` `createInput`: `CreateDefaultOrganizationRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String` | | appKey แอพพลิเคชั่น (optional, จะใช้ของ user ถ้าไม่ระบุ) | | defaultOrganizationRoleKey | `String` | | defaultOrganizationRoleKey รหัส default organization role | | title | `String!` | ใช่ | ชื่อ | | subTitle | `String` | | ชื่อรอง ซึ้งอาจจะเป็นชื่อเวอร์ชั่นภาษาอื่น หรือชื่อย่อ | | description | `String` | | รายล่ะเอียด | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | Response: `DefaultOrganizationRole!` --- #### updateDefaultOrganizationRole แก้ไขข้อมูล DefaultOrganizationRole - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateDefaultOrganizationRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation UpdateDefaultOrganizationRole($defaultOrganizationRoleId: ID!, $updateInput: UpdateDefaultOrganizationRoleInput!) { updateDefaultOrganizationRole(defaultOrganizationRoleId: $defaultOrganizationRoleId, updateInput: $updateInput) { _id defaultOrganizationRoleKey title subTitle description systemNote isActive createdBy updatedBy createdAt # ... } } ``` `updateInput`: `UpdateDefaultOrganizationRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | title | `String` | | ชื่อ | | subTitle | `String` | | ชื่อรอง ซึ้งอาจจะเป็นชื่อเวอร์ชั่นภาษาอื่น หรือชื่อย่อ | | description | `String` | | รายล่ะเอียด | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | argument อื่น: `defaultOrganizationRoleId: ID!` Response: `DefaultOrganizationRole!` --- #### deleteDefaultOrganizationRole ลบข้อมูล DefaultOrganizationRole - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteDefaultOrganizationRole` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation DeleteDefaultOrganizationRole($defaultOrganizationRoleId: ID!) { deleteDefaultOrganizationRole(defaultOrganizationRoleId: $defaultOrganizationRoleId) { _id defaultOrganizationRoleKey title subTitle description systemNote isActive createdBy updatedBy createdAt # ... } } ``` | argument | Type | | --- | --- | | defaultOrganizationRoleId | `ID!` | Response: `DefaultOrganizationRole!` --- #### createOrganizationApproveDetail สร้างข้อมูล OrganizationApproveDetail - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createOrganizationApproveDetail` (ระดับองค์กร) ```graphql mutation CreateOrganizationApproveDetail($createInput: CreateOrganizationApproveDetailInput!) { createOrganizationApproveDetail(createInput: $createInput) { _id organizationId organizationKey organizationApproveId timeStamp isAuto isActive text files images # ... } } ``` `createInput`: `CreateOrganizationApproveDetailInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationKey | `String!` | ใช่ | organizationKey ของ organization ที่ระบุ | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | | text | `String!` | ใช่ | ข้อความแสดงผล | | files | `[String]` | | array files | | images | `[String]` | | array images | Response: `OrganizationApproveDetail!` --- #### updateOrganizationApproveDetail แก้ไขข้อมูล OrganizationApproveDetail - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateOrganizationApproveDetail` (ระดับองค์กร) ```graphql mutation UpdateOrganizationApproveDetail($organizationApproveDetailId: ID!, $updateInput: UpdateOrganizationApproveDetailInput!) { updateOrganizationApproveDetail(organizationApproveDetailId: $organizationApproveDetailId, updateInput: $updateInput) { _id organizationId organizationKey organizationApproveId timeStamp isAuto isActive text files images # ... } } ``` `updateInput`: `UpdateOrganizationApproveDetailInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | | text | `String` | | ข้อความแสดงผล | | files | `[String]` | | array files | | images | `[String]` | | array images | argument อื่น: `organizationApproveDetailId: ID!` Response: `OrganizationApproveDetail!` --- #### deleteOrganizationApproveDetail ลบข้อมูล OrganizationApproveDetail - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteOrganizationApproveDetail` (ระดับองค์กร) ```graphql mutation DeleteOrganizationApproveDetail($organizationApproveDetailId: ID!) { deleteOrganizationApproveDetail(organizationApproveDetailId: $organizationApproveDetailId) { _id organizationId organizationKey organizationApproveId timeStamp isAuto isActive text files images # ... } } ``` | argument | Type | | --- | --- | | organizationApproveDetailId | `ID!` | Response: `OrganizationApproveDetail!` --- #### setOrganizationApproveStatusToInProgress ใช้เพื่อเปลี่ยนสถานะใบอนุมัติเป็น IN_PROGRESS - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `setOrganizationApproveStatusToInProgress` (ระดับองค์กร) ```graphql mutation SetOrganizationApproveStatusToInProgress($input: SetOrganizationApproveStatusInput!) { setOrganizationApproveStatusToInProgress(input: $input) { _id organizationId organizationKey organization { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey } approveStatus createdBy updatedBy createdAt updatedAt } } ``` `input`: `SetOrganizationApproveStatusInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationKey | `String!` | ใช่ | organizationKey ที่ระบุ | | text | `String` | | ข้อความที่ใช้ระบุเหตุผลการดำเนินการ | Response: `OrganizationApprove!` --- #### setOrganizationApproveStatusToApproved ใช้เพื่อเปลี่ยนสถานะใบอนุมัติเป็น APPROVED - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `setOrganizationApproveStatusToApproved` (ระดับองค์กร) ```graphql mutation SetOrganizationApproveStatusToApproved($input: SetOrganizationApproveStatusInput!) { setOrganizationApproveStatusToApproved(input: $input) { _id organizationId organizationKey organization { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey } approveStatus createdBy updatedBy createdAt updatedAt } } ``` `input`: `SetOrganizationApproveStatusInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationKey | `String!` | ใช่ | organizationKey ที่ระบุ | | text | `String` | | ข้อความที่ใช้ระบุเหตุผลการดำเนินการ | Response: `OrganizationApprove!` --- #### setOrganizationApproveStatusToRejected ใช้เพื่อเปลี่ยนสถานะใบอนุมัติเป็น REJECTED - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `setOrganizationApproveStatusToRejected` (ระดับองค์กร) ```graphql mutation SetOrganizationApproveStatusToRejected($input: SetOrganizationApproveStatusInput!) { setOrganizationApproveStatusToRejected(input: $input) { _id organizationId organizationKey organization { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey } approveStatus createdBy updatedBy createdAt updatedAt } } ``` `input`: `SetOrganizationApproveStatusInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationKey | `String!` | ใช่ | organizationKey ที่ระบุ | | text | `String` | | ข้อความที่ใช้ระบุเหตุผลการดำเนินการ | Response: `OrganizationApprove!` --- #### setOrganizationApproveStatusToNeedMoreInformation ใช้เพื่อเปลี่ยนสถานะใบอนุมัติเป็น NEED_MORE_INFORMATION - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `setOrganizationApproveStatusToNeedMoreInformation` (ระดับองค์กร) ```graphql mutation SetOrganizationApproveStatusToNeedMoreInformation($input: SetOrganizationApproveStatusInput!) { setOrganizationApproveStatusToNeedMoreInformation(input: $input) { _id organizationId organizationKey organization { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey } approveStatus createdBy updatedBy createdAt updatedAt } } ``` `input`: `SetOrganizationApproveStatusInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationKey | `String!` | ใช่ | organizationKey ที่ระบุ | | text | `String` | | ข้อความที่ใช้ระบุเหตุผลการดำเนินการ | Response: `OrganizationApprove!` --- #### createOrganizationContactPeople สร้างข้อมูล OrganizationContactPeople - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createOrganizationContactPeople` (ระดับองค์กร) ```graphql mutation CreateOrganizationContactPeople($createInput: CreateOrganizationContactPeopleInput!) { createOrganizationContactPeople(createInput: $createInput) { _id organizationId organizationKey organizationPath organizationContactPeopleKey name description address { address1 address2 address3 city subdivision postalCode } phoneNumber position # ... } } ``` `createInput`: `CreateOrganizationContactPeopleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationContactPeopleKey | `String` | | ถ้าไม่ระบุ จะ auto gen | | organizationKey | `String!` | ใช่ | organizationKey organization ที่ระบุ | | organizationContactKeys | `[String]` | | organizationContactKey เพื่อสร้าง relation อัตโนมัต | | name | `String!` | ใช่ | ชื่อ | | description | `String` | | คำอธิบาย | | address | `OrganizationAddressInput` | | ข้อมูลที่อยู่ของ organization | | phoneNumber | `String` | | phoneNumber text | | position | `String` | | position text | | email | `String` | | email | | taxIdentificationNumber | `String` | | taxIdentificationNumber | | taxIdentificationExpireDate | `Date` | | วันหมดอายุของเลขประจำตัวผู้เสียภาษี | | systemNote | `String` | | ไว้สำหรับ admin | | documentKeys | `[String]` | | file key เอกสาร | | profileImageKey | `String` | | profileImageKey เป็น key ของรูปโปรไฟล์ที่เก็บใน storage | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | Response: `OrganizationContactPeople!` --- #### updateOrganizationContactPeople แก้ไขข้อมูล OrganizationContactPeople - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateOrganizationContactPeople` (ระดับองค์กร) ```graphql mutation UpdateOrganizationContactPeople($organizationContactPeopleId: ID!, $updateInput: UpdateOrganizationContactPeopleInput!) { updateOrganizationContactPeople(organizationContactPeopleId: $organizationContactPeopleId, updateInput: $updateInput) { _id organizationId organizationKey organizationPath organizationContactPeopleKey name description address { address1 address2 address3 city subdivision postalCode } phoneNumber position # ... } } ``` `updateInput`: `UpdateOrganizationContactPeopleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | name | `String` | | ชื่อ | | description | `String` | | คำอธิบาย | | address | `OrganizationAddressInput` | | ข้อมูลที่อยู่ของ organization | | phoneNumber | `String` | | phoneNumber text | | position | `String` | | position text | | email | `String` | | email | | taxIdentificationNumber | `String` | | taxIdentificationNumber | | taxIdentificationExpireDate | `Date` | | วันหมดอายุของเลขประจำตัวผู้เสียภาษี | | systemNote | `String` | | ไว้สำหรับ admin | | documentKeys | `[String]` | | file key เอกสาร | | profileImageKey | `String` | | profileImageKey เป็น key ของรูปโปรไฟล์ที่เก็บใน storage | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | argument อื่น: `organizationContactPeopleId: ID!` Response: `OrganizationContactPeople!` --- #### deleteOrganizationContactPeople ลบข้อมูล OrganizationContactPeople - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteOrganizationContactPeople` (ระดับองค์กร) ```graphql mutation DeleteOrganizationContactPeople($organizationContactPeopleId: ID!) { deleteOrganizationContactPeople(organizationContactPeopleId: $organizationContactPeopleId) { _id organizationId organizationKey organizationPath organizationContactPeopleKey name description address { address1 address2 address3 city subdivision postalCode } phoneNumber position # ... } } ``` | argument | Type | | --- | --- | | organizationContactPeopleId | `ID!` | Response: `OrganizationContactPeople!` --- #### createOrganizationContactRelation สร้างข้อมูล OrganizationContactRelation - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createOrganizationContactRelation` (ระดับองค์กร) ```graphql mutation CreateOrganizationContactRelation($createInput: CreateOrganizationContactRelationInput!) { createOrganizationContactRelation(createInput: $createInput) { _id organizationId organizationKey organizationPath organizationContactRelationKey organizationContactId organizationContactKey organizationContact { _id organizationId organizationKey organizationPath organizationContactKey organizationContactCode } organizationContactPeopleId organizationContactPeopleKey # ... } } ``` `createInput`: `CreateOrganizationContactRelationInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationContactKey | `String!` | ใช่ | organizationContactKey | | organizationContactPeopleKey | `String!` | ใช่ | organizationContactPeopleKey | | organizationContactRelationKey | `String` | | organizationContactRelationKey autogen ใช้เพื่อแสดงหน้าบ้าน | | order | `Float` | | ลำดับการแสดงผล | | systemNote | `String` | | โน๊ต | | position | `String` | | ต่ำแหน่งของผู้ติดต่อ | | documentKeys | `[String]` | | file key เอกสาร | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | Response: `OrganizationContactRelation` --- #### updateOrganizationContactRelation แก้ไขข้อมูล OrganizationContactRelation - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateOrganizationContactRelation` (ระดับองค์กร) ```graphql mutation UpdateOrganizationContactRelation($relationId: ID!, $updateInput: UpdateOrganizationContactRelationInput!) { updateOrganizationContactRelation(relationId: $relationId, updateInput: $updateInput) { _id organizationId organizationKey organizationPath organizationContactRelationKey organizationContactId organizationContactKey organizationContact { _id organizationId organizationKey organizationPath organizationContactKey organizationContactCode } organizationContactPeopleId organizationContactPeopleKey # ... } } ``` `updateInput`: `UpdateOrganizationContactRelationInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | order | `Float` | | ลำดับการแสดงผล | | systemNote | `String` | | โน๊ต | | position | `String` | | ต่ำแหน่งของผู้ติดต่อ | | documentKeys | `[String]` | | file key เอกสาร | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | argument อื่น: `relationId: ID!` Response: `OrganizationContactRelation` --- #### deleteOrganizationContactRelation ลบข้อมูล OrganizationContactRelation - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteOrganizationContactRelation` (ระดับองค์กร) ```graphql mutation DeleteOrganizationContactRelation($relationId: ID!) { deleteOrganizationContactRelation(relationId: $relationId) { _id organizationId organizationKey organizationPath organizationContactRelationKey organizationContactId organizationContactKey organizationContact { _id organizationId organizationKey organizationPath organizationContactKey organizationContactCode } organizationContactPeopleId organizationContactPeopleKey # ... } } ``` | argument | Type | | --- | --- | | relationId | `ID!` | Response: `OrganizationContactRelation` --- #### createOrganizationContact สร้างข้อมูล OrganizationContact - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createOrganizationContact` (ระดับองค์กร) ```graphql mutation CreateOrganizationContact($createInput: CreateOrganizationContactInput!) { createOrganizationContact(createInput: $createInput) { _id organizationId organizationKey organizationPath organizationContactKey organizationContactCode name name2 name3 description # ... } } ``` `createInput`: `CreateOrganizationContactInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationContactKey | `String` | | ถ้าไม่ระบุ จะ auto gen | | organizationContactCode | `String` | | ถ้าไม่ระบุ จะ auto gen | | organizationKey | `String!` | ใช่ | organizationKey organization ที่ระบุ | | name | `String!` | ใช่ | ชื่อ | | name2 | `String` | | ชื่อ 2 | | name3 | `String` | | ชื่อ 3 | | description | `String` | | คำอธิบาย | | type | `EnumOrganizationContactType` | | ประเภทของบริษัท เช่น บุคคลธรรมดา, นิติบุคคล, อื่นๆ | | customerTypeKey | `String` | | key ประเภทของลูกค้า | | address | `OrganizationAddressInput` | | ข้อมูลที่อยู่ของ organization | | address2 | `OrganizationAddressInput` | | ข้อมูลที่อยู่ของ organization | | phoneNumber | `String` | | phoneNumber text | | position | `String` | | position text | | email | `String` | | email | | taxIdentificationNumber | `String` | | taxIdentificationNumber | | systemNote | `String` | | ไว้สำหรับ admin | | documentKeys | `[String]` | | file key เอกสาร | | isCustomer | `Boolean` | | เป็น customer หรือไม่ | | isSupplier | `Boolean` | | เป็น supplier หรือไม่ | | directors | `[DirectorInput]` | | ข้อมูลกรรมการ | | documentFile | `DocumentFileInput` | | ข้อมูลไฟล์เอกสาร | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | | … | | | (มีอีก 1 ฟิลด์ ดู schema) | Response: `OrganizationContact!` --- #### updateOrganizationContact แก้ไขข้อมูล OrganizationContact - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateOrganizationContact` (ระดับองค์กร) ```graphql mutation UpdateOrganizationContact($organizationContactId: ID!, $updateInput: UpdateOrganizationContactInput!) { updateOrganizationContact(organizationContactId: $organizationContactId, updateInput: $updateInput) { _id organizationId organizationKey organizationPath organizationContactKey organizationContactCode name name2 name3 description # ... } } ``` `updateInput`: `UpdateOrganizationContactInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationContactCode | `String` | | ถ้าไม่ระบุ จะ auto gen | | name | `String` | | ชื่อ | | name2 | `String` | | ชื่อ 2 | | name3 | `String` | | ชื่อ 3 | | description | `String` | | คำอธิบาย | | type | `EnumOrganizationContactType` | | ประเภทของบริษัท เช่น บุคคลธรรมดา, นิติบุคคล, อื่นๆ | | customerTypeKey | `String` | | key ประเภทของลูกค้า null = ลบออก | | address | `OrganizationAddressInput` | | ข้อมูลที่อยู่ของ organization | | address2 | `OrganizationAddressInput` | | ข้อมูลที่อยู่ของ organization | | phoneNumber | `String` | | phoneNumber text | | position | `String` | | position text | | email | `String` | | email | | taxIdentificationNumber | `String` | | taxIdentificationNumber | | systemNote | `String` | | ไว้สำหรับ admin | | documentKeys | `[String]` | | file key เอกสาร | | isCustomer | `Boolean` | | เป็น customer หรือไม่ | | isSupplier | `Boolean` | | เป็น supplier หรือไม่ | | directors | `[DirectorInput]` | | ข้อมูลกรรมการ | | documentFile | `DocumentFileInput` | | ข้อมูลไฟล์เอกสาร | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | argument อื่น: `organizationContactId: ID!` Response: `OrganizationContact!` --- #### deleteOrganizationContact ลบข้อมูล OrganizationContact - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteOrganizationContact` (ระดับองค์กร) ```graphql mutation DeleteOrganizationContact($organizationContactId: ID!) { deleteOrganizationContact(organizationContactId: $organizationContactId) { _id organizationId organizationKey organizationPath organizationContactKey organizationContactCode name name2 name3 description # ... } } ``` | argument | Type | | --- | --- | | organizationContactId | `ID!` | Response: `OrganizationContact!` --- #### createOrganizationRole สร้างข้อมูล OrganizationRole - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createOrganizationRole` (ระดับองค์กร) ```graphql mutation CreateOrganizationRole($createInput: CreateOrganizationRoleInput!) { createOrganizationRole(createInput: $createInput) { _id organizationId organizationKey organizationRoleKey title subTitle description systemNote isInvite isActive # ... } } ``` `createInput`: `CreateOrganizationRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationRoleKey | `String` | | ถ้าไม่ระบุ จะ auto gen | | organizationKey | `String!` | ใช่ | organizationKey organization ที่ระบุ | | title | `String!` | ใช่ | ชื่อ | | subTitle | `String` | | ชื่อรอง ซึ้งอาจจะเป็นชื่อเวอร์ชั่นภาษาอื่น หรือชื่อย่อ | | description | `String` | | รายล่ะเอียด | | systemNote | `String` | | ไว้สำหรับ admin | | isInvite | `Boolean` | | นำไปใช้ใน inviteCode ได้ไหม | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | Response: `OrganizationRole!` --- #### updateOrganizationRole แก้ไขข้อมูล OrganizationRole - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateOrganizationRole` (ระดับองค์กร) ```graphql mutation UpdateOrganizationRole($organizationRoleId: ID!, $updateInput: UpdateOrganizationRoleInput!) { updateOrganizationRole(organizationRoleId: $organizationRoleId, updateInput: $updateInput) { _id organizationId organizationKey organizationRoleKey title subTitle description systemNote isInvite isActive # ... } } ``` `updateInput`: `UpdateOrganizationRoleInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | title | `String` | | ชื่อ | | subTitle | `String` | | ชื่อรอง ซึ้งอาจจะเป็นชื่อเวอร์ชั่นภาษาอื่น หรือชื่อย่อ | | description | `String` | | รายล่ะเอียด | | systemNote | `String` | | ไว้สำหรับ admin | | isInvite | `Boolean` | | นำไปใช้ใน inviteCode ได้ไหม | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | argument อื่น: `organizationRoleId: ID!` Response: `OrganizationRole!` --- #### deleteOrganizationRole ลบข้อมูล OrganizationRole - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteOrganizationRole` (ระดับองค์กร) ```graphql mutation DeleteOrganizationRole($organizationRoleId: ID!) { deleteOrganizationRole(organizationRoleId: $organizationRoleId) { _id organizationId organizationKey organizationRoleKey title subTitle description systemNote isInvite isActive # ... } } ``` | argument | Type | | --- | --- | | organizationRoleId | `ID!` | Response: `OrganizationRole!` --- #### createOrganizationTag สร้างข้อมูล OrganizationTag - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createOrganizationTag` ```graphql mutation CreateOrganizationTag($createInput: CreateOrganizationTagInput!) { createOrganizationTag(createInput: $createInput) { _id organizationTagKey organizationTagPath parentOrganizationTagId title subTitle description icon systemNote isActive # ... } } ``` `createInput`: `CreateOrganizationTagInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationTagKey | `String` | | ถ้าไม่ระบุ จะ auto gen | | parentOrganizationTagId | `String` | | _id organizationTag ของแม่ | | title | `String!` | ใช่ | ชื่อ | | subTitle | `String` | | ชื่อรอง ซึ้งอาจจะเป็นชื่อเวอร์ชั่นภาษาอื่น หรือชื่อย่อ | | description | `String` | | รายล่ะเอียด | | icon | `String` | | icon | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | | isPublic | `Boolean` | | สถานะการPublic true เมื่อเปิดให้ Public | Response: `OrganizationTag!` --- #### updateOrganizationTag แก้ไขข้อมูล OrganizationTag ถ้ามีลูก ห้ามแก้ organizationParent - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateOrganizationTag` ```graphql mutation UpdateOrganizationTag($organizationTagId: ID!, $updateInput: UpdateOrganizationTagInput!) { updateOrganizationTag(organizationTagId: $organizationTagId, updateInput: $updateInput) { _id organizationTagKey organizationTagPath parentOrganizationTagId title subTitle description icon systemNote isActive # ... } } ``` `updateInput`: `UpdateOrganizationTagInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | parentOrganizationTagId | `String` | | _id organizationTag ของแม่ | | title | `String` | | ชื่อ | | subTitle | `String` | | ชื่อรอง ซึ้งอาจจะเป็นชื่อเวอร์ชั่นภาษาอื่น หรือชื่อย่อ | | description | `String` | | รายล่ะเอียด | | icon | `String` | | icon | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | | isPublic | `Boolean` | | สถานะการPublic true เมื่อเปิดให้ Public | argument อื่น: `organizationTagId: ID!` Response: `OrganizationTag!` --- #### deleteOrganizationTag ลบข้อมูล OrganizationTag ถ้ามีลูก ห้ามลบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteOrganizationTag` ```graphql mutation DeleteOrganizationTag($organizationTagId: ID!) { deleteOrganizationTag(organizationTagId: $organizationTagId) { _id organizationTagKey organizationTagPath parentOrganizationTagId title subTitle description icon systemNote isActive # ... } } ``` | argument | Type | | --- | --- | | organizationTagId | `ID!` | Response: `OrganizationTag!` --- #### createOrganizationType สร้างข้อมูล OrganizationType - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createOrganizationType` ```graphql mutation CreateOrganizationType($createInput: CreateOrganizationTypeInput!) { createOrganizationType(createInput: $createInput) { _id organizationTypeKey organizationTypePath parentOrganizationTypeId title subTitle description icon systemNote isActive # ... } } ``` `createInput`: `CreateOrganizationTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationTypeKey | `String` | | ถ้าไม่ระบุ จะ auto gen | | parentOrganizationTypeId | `String` | | _id organizationType ของแม่ | | title | `String!` | ใช่ | ชื่อ | | subTitle | `String` | | ชื่อรอง ซึ้งอาจจะเป็นชื่อเวอร์ชั่นภาษาอื่น หรือชื่อย่อ | | description | `String` | | รายล่ะเอียด | | icon | `String` | | icon | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | | isPublic | `Boolean` | | สถานะการPublic true เมื่อเปิดให้ Public | Response: `OrganizationType!` --- #### updateOrganizationType แก้ไขข้อมูล OrganizationType ถ้ามีลูก ห้ามแก้ organizationParent - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateOrganizationType` ```graphql mutation UpdateOrganizationType($organizationTypeId: ID!, $updateInput: UpdateOrganizationTypeInput!) { updateOrganizationType(organizationTypeId: $organizationTypeId, updateInput: $updateInput) { _id organizationTypeKey organizationTypePath parentOrganizationTypeId title subTitle description icon systemNote isActive # ... } } ``` `updateInput`: `UpdateOrganizationTypeInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | parentOrganizationTypeId | `String` | | _id organizationType ของแม่ | | title | `String` | | ชื่อ | | subTitle | `String` | | ชื่อรอง ซึ้งอาจจะเป็นชื่อเวอร์ชั่นภาษาอื่น หรือชื่อย่อ | | description | `String` | | รายล่ะเอียด | | icon | `String` | | icon | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | | isPublic | `Boolean` | | สถานะการPublic true เมื่อเปิดให้ Public | argument อื่น: `organizationTypeId: ID!` Response: `OrganizationType!` --- #### deleteOrganizationType ลบข้อมูล OrganizationType ถ้ามีลูก ห้ามลบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteOrganizationType` ```graphql mutation DeleteOrganizationType($organizationTypeId: ID!) { deleteOrganizationType(organizationTypeId: $organizationTypeId) { _id organizationTypeKey organizationTypePath parentOrganizationTypeId title subTitle description icon systemNote isActive # ... } } ``` | argument | Type | | --- | --- | | organizationTypeId | `ID!` | Response: `OrganizationType!` --- #### createOrganization สร้างข้อมูล Organization สร้างองค์กรแล้ว ระบบจะสร้าง organizationRole จาก defaultOrganizationRole ทุกตัวให้อัตโนมัติ และสร้างใบอนุมัติองค์กร (ผู้ที่มีสิทธิ์ `autoOrganizationApprove` จะอนุมัติทันที) - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createOrganization` ```graphql mutation CreateOrganization($createInput: CreateOrganizationInput!) { createOrganization(createInput: $createInput) { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey title subTitle description title2 # ... } } ``` `createInput`: `CreateOrganizationInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | organizationKey | `String` | | ถ้าไม่ระบุ จะ auto gen | | parentOrganizationId | `String` | | _id organization ของแม่ | | organizationImageKey | `String` | | fileKey ของรูปภาพ ใช้เป็นรูป profile ของ organization | | organizationBackgroundImageKey | `String` | | fileKey ของรูปภาพ ใช้เป็นรูปภาพพื้นหลังของ organization | | title | `String!` | ใช่ | ชื่อ | | subTitle | `String` | | ชื่อรอง ซึ้งอาจจะเป็นชื่อเวอร์ชั่นภาษาอื่น หรือชื่อย่อ | | description | `String` | | รายล่ะเอียด | | title2 | `String` | | ชื่อ 2 | | subTitle2 | `String` | | ชื่อรอง 2 | | description2 | `String` | | รายละเอียด 2 | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | | isPublic | `Boolean` | | สถานะการPublic true เมื่อเปิดให้ Public | | address | `OrganizationAddressInput` | | ข้อมูลที่อยู่ของ organization | | address2 | `OrganizationAddressInput` | | ข้อมูลที่อยู่ของ organization2 | | organizationTypeId | `String` | | _id organizationType ที่ระบุ | | organizationTagIds | `[String]` | | array ของ _id organizationTag | Response: `Organization!` --- #### updateOrganization แก้ไขข้อมูล Organization ถ้ามีลูก ห้ามแก้ organizationParent - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateOrganization` (ระดับองค์กร) ```graphql mutation UpdateOrganization($organizationId: ID!, $updateInput: UpdateOrganizationInput!) { updateOrganization(organizationId: $organizationId, updateInput: $updateInput) { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey title subTitle description title2 # ... } } ``` `updateInput`: `UpdateOrganizationInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | parentOrganizationId | `String` | | _id organization ของแม่ | | organizationImageKey | `String` | | fileKey ของรูปภาพ ใช้เป็นรูป profile ของ organization | | organizationBackgroundImageKey | `String` | | fileKey ของรูปภาพ ใช้เป็นรูปภาพพื้นหลังของ organization | | title | `String` | | ชื่อ | | subTitle | `String` | | ชื่อรอง ซึ้งอาจจะเป็นชื่อเวอร์ชั่นภาษาอื่น หรือชื่อย่อ | | description | `String` | | รายล่ะเอียด | | title2 | `String` | | ชื่อ 2 | | subTitle2 | `String` | | ชื่อรอง 2 | | description2 | `String` | | รายละเอียด 2 | | systemNote | `String` | | ไว้สำหรับ admin | | isActive | `Boolean` | | สถานะการใช้งาน true เมื่อเปิดการใช้งาน | | isPublic | `Boolean` | | สถานะการPublic true เมื่อเปิดให้ Public | | address | `OrganizationAddressInput` | | ข้อมูลที่อยู่ของ organization | | address2 | `OrganizationAddressInput` | | ข้อมูลที่อยู่ของ organization2 | | organizationTypeId | `String` | | _id organizationType ที่ระบุ | | organizationTagIds | `[String]` | | array ของ _id organizationTag | argument อื่น: `organizationId: ID!` Response: `Organization!` --- #### deleteOrganization ลบข้อมูล Organization ถ้ามีลูก ห้ามลบ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteOrganization` (ระดับองค์กร) ```graphql mutation DeleteOrganization($organizationId: ID!) { deleteOrganization(organizationId: $organizationId) { _id organizationKey organizationPath parentOrganizationId organizationImageKey organizationBackgroundImageKey title subTitle description title2 # ... } } ``` | argument | Type | | --- | --- | | organizationId | `ID!` | Response: `Organization!` ---

## Kafka consume Reference ทุกข้อความมี header `appKey` และ `serviceKey` (service ปลายทาง) · payload อยู่ในรูป `{ <ข้อมูล>: {...}, action: "ADD" | "REMOVE" | ... }` · ข้อความของแอปที่ service นี้ไม่มี AppCertificate จะถูกข้าม --- ### topic มาตรฐาน | topic | ใช้ทำอะไร | | --- | --- | | `init-system` | ตั้งระบบจากศูนย์ (เฉพาะ core set) | | `refresh-data` | core สั่งให้ส่งข้อมูลที่ถืออยู่ขึ้นไปใหม่ (organization, type, tag, approve, contact, role, running number ฯลฯ และ `sync-permission`) + ล้าง cache | | `sync-app-certificate` | รับ AppCertificate ของ service ต่อแอป จาก core | | `sync-app-credential` | รับ AppCredential ของแอป จาก Authentication Service ใช้ตรวจ token / header | | `sync-service-setting` / `sync-app-service-setting` | รับค่าตั้งค่าเพิ่มเติมแบบ JSON ทั้งระบบ / รายแอป | | `sync-application` | รับข้อมูลแอป | | `sync-user-policy` | รับ UserPolicy ของ permission `unit` จาก ACL · ใช้ตรวจสิทธิ์ และใช้รู้ว่าผู้ใช้อยู่องค์กรใด (`getUserOrganizations`) | --- ### add-admin-app-role เมื่อมีแอปใหม่ core สั่งให้สร้าง appRole `admin` ของแอปนั้น แล้วส่ง `sync-app-role` (พร้อม `isAdmin: true`) ให้ ACL topic: add-admin-app-role | key | Type | คำอธิบาย | | --- | --- | --- | | adminAppRole.appKey | string | แอปที่ต้องการสร้าง role admin | | action | string | `ADD` | --- ### register-custom-running-number ให้ service อื่นลงทะเบียนรูปแบบเลขรันนิ่งที่ตัวเองใช้ topic: register-custom-running-number | key | Type | คำอธิบาย | | --- | --- | --- | | customRunningNumber.appKey | string | appKey | | customRunningNumber.level | string | `APP`, `ORG` | | customRunningNumber.organizationId / organizationKey | string | องค์กร (เมื่อ level = `ORG`) | | customRunningNumber.customRunningNumberKey | string | key ของเลขรันนิ่ง | | customRunningNumber.refKey | string | key อ้างอิงของผู้ใช้งาน | | customRunningNumber.title / description / order | | ข้อมูลแสดงผล | | customRunningNumber.serial | string | รอบการนับ `YEAR`, `MONTH`, `DAY`, `INDEX` | | customRunningNumber.padding | number | จำนวนหลัก | | customRunningNumber.pattern | string | รูปแบบเลข | | customRunningNumber.isDefault / isActive | boolean | สถานะ | | action | string | `ADD` | --- ### generate-running-number ให้ service อื่นขอเลขรันนิ่งถัดไป · ผลตอบกลับทาง `generated-running-number-result` topic: generate-running-number | key | Type | คำอธิบาย | | --- | --- | --- | | customRunNumber.customRunningNumberKey | string | key ของเลขรันนิ่ง | | customRunNumber.pattern | string | (ไม่บังคับ) pattern ที่ต้องการใช้แทนค่าที่ตั้งไว้ | | customRunNumber.targetId / targetKey / targetType | string | เอกสารปลายทางที่จะใช้เลขนี้ (ส่งกลับมาในผลลัพธ์) | | action | string | `ADD` | ---

## Kafka Produce Reference header `appKey` = แอปของข้อมูล · payload ของแต่ละ topic คือข้อมูลของ entity นั้นตาม type ใน API ด้านบน พร้อม `action` | topic | root key | ส่งเมื่อ | | --- | --- | --- | | `sync-organization` | `organization` | สร้าง / แก้ / ลบองค์กร (`id`, `appKey`, `organizationKey`, `organizationPath`, `parentOrganizationId`, `title`, `address`, `organizationTypeId`, `organizationTagIds`, `isApproved`, `isPublic`, `isActive`, ...) | | `sync-organization-type` | `organizationType` | สร้าง / แก้ / ลบประเภทองค์กร | | `sync-organization-tag` | `organizationTag` | สร้าง / แก้ / ลบแท็กองค์กร | | `sync-organization-approve` | `organizationApprove` | สร้างใบอนุมัติ / เปลี่ยนสถานะ (`approveStatus`) | | `sync-organization-approve-detail` | `organizationApproveDetail` | สร้าง / แก้ / ลบรายละเอียดใบอนุมัติ | | `sync-organization-contact` | `organizationContact` | สร้าง / แก้ / ลบผู้ติดต่อ | | `sync-organization-contact-people` | `organizationContactPeople` | สร้าง / แก้ / ลบบุคคลติดต่อ | | `sync-organization-contact-relation` | `organizationContactRelation` | สร้าง / แก้ / ลบความสัมพันธ์ผู้ติดต่อ | | `sync-app-role` | `appRole` | สร้าง / แก้ / ลบ appRole | | `sync-organization-role` | `organizationRole` | สร้าง / แก้ / ลบ organizationRole (รวมที่สร้างอัตโนมัติจากแม่แบบตอนสร้างองค์กร) | | `sync-default-organization-role` | `defaultOrganizationRole` | สร้าง / แก้ / ลบ defaultOrganizationRole | | `sync-custom-running-number` | `customRunningNumber` | สร้าง / แก้ / ลบเลขรันนิ่ง | | `generated-running-number-result` | `generatedRunningNumber` | ตอบผลของ `generate-running-number`: `customRunningNumberKey`, `targetId`, `targetKey`, `targetType`, `generatedCode` | | `sync-permission` | `permission` | ส่ง permission ของ `unit` ให้ ACL (ตอนได้ `refresh-data`) | --- > อัปเดตจากโค้ด gumon-unit-service@8cd0bc9 · 2026-10-05 --- # Profile Service Service สำหรับเก็บข้อมูลโปรไฟล์ของผู้ใช้ในแต่ละแอป (เดิมหน้านี้ชื่อ User Service) serviceKey: profile - **Profile** — ชื่อ (`firstName`, `middleName`, `lastName`, `displayName`), เพศ (`gender`: `MALE`, `FEMALE`, `NOT_SPECIFIED`), รูปโปรไฟล์ (`profileImage`), ลายเซ็นอิเล็กทรอนิกส์ (`electronicSignatureKey`) และองค์กรเริ่มต้น (`defaultOrganizationId`, `defaultOrganizationKey`) - **Metadata / Profile Custom** — แอปกำหนดฟิลด์เสริมของโปรไฟล์เองได้ (`createMetadata`) โดยไม่ต้องแก้โค้ด แล้วเก็บค่าของผู้ใช้แต่ละคนใน profile custom · ชนิดฟิลด์: `STRING`, `NUMBER`, `SINGLE_CHOICE`, `MULTIPLE_CHOICE`, `DATE`, `DATE_TIME`, `TIME`, `BOOLEAN` บัญชีผู้ใช้ (username, email, เบอร์โทร, รหัสผ่าน, การ login) อยู่ที่ [Authentication Service](authenticationService.md) · เมื่อมีการสมัครบัญชี Authentication Service จะส่งข้อมูลเริ่มต้นมาทาง topic `sync-auth` แล้ว Profile Service สร้างโปรไฟล์ให้
- [API Reference](#api-reference) - [kafka consume Reference](#kafka-consume-reference) - [Kafka Produce Reference](#kafka-produce-reference) ---

## API Reference การยืนยันตัวตนและ header ดูที่ [Authentication Service](authenticationService.md#app-credential) · "สิทธิ์" คือ permissionKey ของ service `profile` ที่ได้รับผ่าน role ใน [ACL Service](aclService.md) --- ### Query เป็น API ที่ใช้สำหรับการ Query ข้อมูลออกมา ไม่มีการแก้ไข Data --- #### getMetadataFields ดึงนิยามฟิลด์เสริม (metadata) ของโปรไฟล์ในแอปปัจจุบัน แบบแบ่งหน้า - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getMetadataProfile` ```graphql query GetMetadataFields($input: GetMetadataFieldInput) { getMetadataFields(input: $input) { metadataFields { _id fieldLabel fieldKey fieldType min max } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetMetadataFieldInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetMetadataFieldFilterInput` | | | | search | `GetMetadataFieldSearchInput` | | | | sort | `GetMetadataFieldSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `MetadataFieldPagination!` --- #### getMetadataById ดึงนิยามฟิลด์เสริมตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getMetadataProfileById` ```graphql query GetMetadataById($metadataId: ID!) { getMetadataById(metadataId: $metadataId) { _id fieldLabel fieldKey fieldType fieldOptions { key value } min max maxFileSize default systemDescription # ... } } ``` | argument | Type | | --- | --- | | metadataId | `ID!` | Response: `Metadata!` --- #### getMetadataByKey ดึงนิยามฟิลด์เสริมตาม key - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getMetadataProfileByKey` ```graphql query GetMetadataByKey($metadataKey: String!) { getMetadataByKey(metadataKey: $metadataKey) { _id fieldLabel fieldKey fieldType fieldOptions { key value } min max maxFileSize default systemDescription # ... } } ``` | argument | Type | | --- | --- | | metadataKey | `String!` | Response: `Metadata!` --- #### getMetadataFieldsByAppKey ดึงนิยามฟิลด์เสริมของแอปที่ระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetMetadataFieldsByAppKey($appKey: String!, $input: GetMetadataFieldInput) { getMetadataFieldsByAppKey(appKey: $appKey, input: $input) { metadataFields { _id fieldLabel fieldKey fieldType min max } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetMetadataFieldInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetMetadataFieldFilterInput` | | | | search | `GetMetadataFieldSearchInput` | | | | sort | `GetMetadataFieldSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `appKey: String!` Response: `MetadataFieldPagination` --- #### getMetadataByIdSystem ดึงนิยามฟิลด์เสริมตาม id โดยระบุ appKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetMetadataByIdSystem($appKey: String!, $metadataId: ID!) { getMetadataByIdSystem(appKey: $appKey, metadataId: $metadataId) { _id fieldLabel fieldKey fieldType fieldOptions { key value } min max maxFileSize default systemDescription # ... } } ``` | argument | Type | | --- | --- | | appKey | `String!` | | metadataId | `ID!` | Response: `Metadata!` --- #### getMetadataByKeySystem ดึงนิยามฟิลด์เสริมตาม key โดยระบุ appKey - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetMetadataByKeySystem($appKey: String!, $metadataKey: String!) { getMetadataByKeySystem(appKey: $appKey, metadataKey: $metadataKey) { _id fieldLabel fieldKey fieldType fieldOptions { key value } min max maxFileSize default systemDescription # ... } } ``` | argument | Type | | --- | --- | | appKey | `String!` | | metadataKey | `String!` | Response: `Metadata!` --- #### getProfileCustom ดึงค่าฟิลด์เสริมของผู้ใช้ตาม authId - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getProfileCustom` ```graphql query GetProfileCustom($authId: String!) { getProfileCustom(authId: $authId) { value metadata { _id fieldLabel fieldKey fieldType min max } updatedAt updatedBy } } ``` | argument | Type | | --- | --- | | authId | `String!` | Response: `[ProfileCustom]!` --- #### getMyProfileCustom ดึงค่าฟิลด์เสริมของผู้ใช้ที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetMyProfileCustom { getMyProfileCustom { value metadata { _id fieldLabel fieldKey fieldType min max } updatedAt updatedBy } } ``` Response: `[ProfileCustom]!` --- #### getMyProfile ดึงโปรไฟล์ของผู้ใช้ที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql query GetMyProfile { getMyProfile { _id appKey firstName middleName lastName displayName gender authId username profileImage # ... } } ``` Response: `Profile!` --- #### getProfileById ดึงโปรไฟล์ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getProfileById` ```graphql query GetProfileById($profileId: ID!) { getProfileById(profileId: $profileId) { _id appKey firstName middleName lastName displayName gender authId username profileImage # ... } } ``` | argument | Type | | --- | --- | | profileId | `ID!` | Response: `Profile!` --- #### getProfiles ดึงโปรไฟล์ทั้งหมดในแอปปัจจุบัน แบบแบ่งหน้า - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getProfiles` ```graphql query GetProfiles($input: GetProfilesInput) { getProfiles(input: $input) { profiles { _id appKey firstName middleName lastName displayName } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetProfilesInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetProfilesFilterInput` | | | | search | `GetProfilesSearchInput` | | | | sort | `GetProfilesSortInput` | | | | pagination | `CustomPaginateInput` | | | Response: `ProfilesPagination!` --- #### getProfilesByAppKey ดึงโปรไฟล์ของแอปที่ระบุ แบบแบ่งหน้า - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getProfilesByAppKey` ```graphql query GetProfilesByAppKey($appKey: String!, $input: GetProfilesInput) { getProfilesByAppKey(appKey: $appKey, input: $input) { profiles { _id appKey firstName middleName lastName displayName } pagination { limit page totalItems totalPages hasPrevPage hasNextPage } } } ``` `input`: `GetProfilesInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | filter | `GetProfilesFilterInput` | | | | search | `GetProfilesSearchInput` | | | | sort | `GetProfilesSortInput` | | | | pagination | `CustomPaginateInput` | | | argument อื่น: `appKey: String!` Response: `ProfilesPagination!` --- #### getProfileByIdSystem ดึงโปรไฟล์ตาม id โดยระบุ appKey (ใช้ข้ามแอป) - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `getProfileById` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql query GetProfileByIdSystem($profileId: ID!, $appKey: String) { getProfileByIdSystem(profileId: $profileId, appKey: $appKey) { _id appKey firstName middleName lastName displayName gender authId username profileImage # ... } } ``` | argument | Type | | --- | --- | | profileId | `ID!` | | appKey | `String` | Response: `Profile!` --- ### Mutation เป็น API ที่ใช้สำหรับการแก้ไขข้อมูล --- #### createMetadata สร้างนิยามฟิลด์เสริมของโปรไฟล์ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `createMetadataProfile` ```graphql mutation CreateMetadata($createMetadataInput: CreateMetadataInput!) { createMetadata(createMetadataInput: $createMetadataInput) { _id fieldLabel fieldKey fieldType fieldOptions { key value } min max maxFileSize default systemDescription # ... } } ``` `createMetadataInput`: `CreateMetadataInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String` | | appKey ที่ต้องการสร้าง metadata ไม่ใส่ใช้ appKey จาก login | | fieldLabel | `String!` | ใช่ | The Label when display in the form. | | fieldKey | `String!` | ใช่ | The Key of the metadata. | | fieldType | `EnumFieldTypes!` | ใช่ | The Type of the metadata. | | fieldOptions | `[FieldOptionsInput]` | | The Options when the field type is SINGLE_CHOICE or MULTIPLE_CHOICE. Example: - SINGLE_CHOICE: - "fieldOptions": [{"key": "test1","value": "test1"}] - MULTIPLE_CHOICE: - "fieldOptions": [{"key": "test1","value": "test1"}] | | min | `Float` | | The min value of the metadata. | | max | `Float` | | The max value of the metadata. | | maxFileSize | `Int` | | The max file size of the metadata. | | default | `JSON!` | ใช่ | The default value in form. Example: - STRING: "text" - NUMBER: 0 - SINGLE_CHOICE: - "default": {"key": "test1","value": "test1"} - MULTIPLE_CHOICE: - "default": [{"key": "test1","value": "test1"}] - DATE: "2021-01-01" - DATE_TIME: "2021-01-01T00:00:00.000Z" - TIME: "2021-01-01T00:00:00.000Z" | | systemDescription | `String` | | The text for explain about base system metadata. | | displayDescription | `String` | | The text for explain about metadata. | | placeHolderText | `String` | | The placeHolder for description of metadata. | | isActive | `Boolean!` | ใช่ | The status of metadata. | | isRequired | `Boolean!` | ใช่ | The field is required or not. | | isSensitive | `Boolean!` | ใช่ | The field is sensitive or not. | | ordinalNumber | `Int!` | ใช่ | The order when sorting metadata. | Response: `Metadata!` --- #### updateMetadata แก้ไขนิยามฟิลด์เสริม (ส่งได้หลายรายการ) - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateMetadataProfile` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation UpdateMetadata($updateMetadataInput: [UpdateMetadataInput]!, $appKey: String) { updateMetadata(updateMetadataInput: $updateMetadataInput, appKey: $appKey) { _id fieldLabel fieldKey fieldType fieldOptions { key value } min max maxFileSize default systemDescription # ... } } ``` `updateMetadataInput`: `UpdateMetadataInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | metadataId | `String!` | ใช่ | The ID of the metadata. | | fieldLabel | `String` | | The Label when display in the form. | | default | `JSON` | | The default value in form. Example: - STRING: "text" - NUMBER: 0 - SINGLE_CHOICE: - "default": {"key": "test1","value": "test1"} - MULTIPLE_CHOICE: - "default": [{"key": "test1","value": "test1"}] - DATE: "2021-01-01" - DATE_TIME: "2021-01-01T00:00:00.000Z" - TIME: "2021-01-01T00:00:00.000Z" | | systemDescription | `String` | | The text for explain about metadata. | | displayDescription | `String` | | The text for explain about metadata. | | placeHolderText | `String` | | The placeHolder for description of metadata. | | isActive | `Boolean` | | The status of metadata. | | isRequired | `Boolean` | | The field is required or not. | | ordinalNumber | `Int` | | the order when sorting metadata. | argument อื่น: `appKey: String` Response: `[Metadata]!` --- #### deleteMetadata ลบนิยามฟิลด์เสริมตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `deleteMetadataProfile` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation DeleteMetadata($metadataId: ID!, $appKey: String) { deleteMetadata(metadataId: $metadataId, appKey: $appKey) { _id fieldLabel fieldKey fieldType fieldOptions { key value } min max maxFileSize default systemDescription # ... } } ``` | argument | Type | | --- | --- | | metadataId | `ID!` | | appKey | `String` | Response: `Metadata!` --- #### updateProfileCustom แก้ไขค่าฟิลด์เสริมของผู้ใช้ที่ระบุ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateProfileCustoms` ```graphql mutation UpdateProfileCustom($updateProfileCustomInput: [UpdateProfileCustomInput]!) { updateProfileCustom(updateProfileCustomInput: $updateProfileCustomInput) { value metadata { _id fieldLabel fieldKey fieldType min max } updatedAt updatedBy } } ``` `updateProfileCustomInput`: `UpdateProfileCustomInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | appKey | `String!` | ใช่ | specify application key of the profile custom field | | authId | `String!` | ใช่ | specify auth ID of the profile custom field | | fieldMetadataId | `String!` | ใช่ | specify field metadata ID of the profile custom field | | value | `JSON` | | specify value of the profile custom field | Response: `[ProfileCustom]!` --- #### updateMyProfileCustom แก้ไขค่าฟิลด์เสริมของผู้ใช้ที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation UpdateMyProfileCustom($UpdateMyProfileCustomInput: [UpdateMyProfileCustomInput]!) { updateMyProfileCustom(UpdateMyProfileCustomInput: $UpdateMyProfileCustomInput) { value metadata { _id fieldLabel fieldKey fieldType min max } updatedAt updatedBy } } ``` `UpdateMyProfileCustomInput`: `UpdateMyProfileCustomInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | fieldMetadataId | `String!` | ใช่ | specify field metadata ID of the profile custom field | | value | `JSON` | | specify value of the profile custom field | Response: `[ProfileCustom]!` --- #### updateProfile แก้ไขโปรไฟล์ตาม id - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM - สิทธิ์: `updateProfile` - ทำงานข้ามแอปได้เมื่อมีสิทธิ์ `systemApp` ```graphql mutation UpdateProfile($profileId: ID!, $updateProfileInput: UpdateProfileInput!, $appKey: String) { updateProfile(profileId: $profileId, updateProfileInput: $updateProfileInput, appKey: $appKey) { _id appKey firstName middleName lastName displayName gender authId username profileImage # ... } } ``` `updateProfileInput`: `UpdateProfileInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | firstName | `String` | | first name of the profile. | | middleName | `String` | | middle name of the profile. | | lastName | `String` | | last name of the profile. | | displayName | `String` | | display name of the profile. | | gender | `EnumGender` | | gender of the profile. | | profileImage | `String` | | profile image of the profile. | | electronicSignatureKey | `String` | | file key of electronic signature. | argument อื่น: `profileId: ID!`, `appKey: String` Response: `Profile!` --- #### updateMyProfile แก้ไขโปรไฟล์ของผู้ใช้ที่ login อยู่ - การยืนยันตัวตน: ต้อง login (header `authorization`) หรือใช้ credential แบบ SYSTEM ```graphql mutation UpdateMyProfile($updateProfileInput: UpdateProfileInput!) { updateMyProfile(updateProfileInput: $updateProfileInput) { _id appKey firstName middleName lastName displayName gender authId username profileImage # ... } } ``` `updateProfileInput`: `UpdateProfileInput` | key | Type | จำเป็น | คำอธิบาย | | --- | --- | --- | --- | | firstName | `String` | | first name of the profile. | | middleName | `String` | | middle name of the profile. | | lastName | `String` | | last name of the profile. | | displayName | `String` | | display name of the profile. | | gender | `EnumGender` | | gender of the profile. | | profileImage | `String` | | profile image of the profile. | | electronicSignatureKey | `String` | | file key of electronic signature. | Response: `Profile!` ---

## Kafka consume Reference ทุกข้อความมี header `appKey` และ `serviceKey` (service ปลายทาง) · payload อยู่ในรูป `{ <ข้อมูล>: {...}, action: "ADD" | "REMOVE" | ... }` · ข้อความของแอปที่ service นี้ไม่มี AppCertificate จะถูกข้าม --- ### topic มาตรฐาน | topic | ใช้ทำอะไร | | --- | --- | | `init-system` | ตั้งระบบจากศูนย์ (เฉพาะ core set) | | `refresh-data` | core สั่งให้ส่งข้อมูลที่ถืออยู่ขึ้นไปใหม่ (`sync-profile` และ `sync-permission`) + ล้าง cache | | `sync-app-certificate` | รับ AppCertificate ของ service ต่อแอป จาก core | | `sync-app-credential` | รับ AppCredential ของแอป จาก Authentication Service ใช้ตรวจ token / header | | `sync-service-setting` / `sync-app-service-setting` | รับค่าตั้งค่าเพิ่มเติมแบบ JSON ทั้งระบบ / รายแอป | | `sync-application` | รับข้อมูลแอป | | `sync-user-policy` | รับ UserPolicy ของ permission `profile` จาก ACL ใช้ตรวจสิทธิ์ | | `sync-organization` | รับข้อมูลองค์กรจาก [Unit Service](unitService.md) | --- ### sync-auth รับข้อมูลบัญชีจาก [Authentication Service](authenticationService.md) แล้วสร้าง / อัปเดต / ลบโปรไฟล์ topic: sync-auth | key | Type | คำอธิบาย | | --- | --- | --- | | account.appKey | string | appKey | | account.authId | string | id ของบัญชี | | account.username | string | username | | account.firstName / middleName / lastName / displayName | string | ชื่อ | | account.gender | string | เพศ | | account.profileImage | string | fileKey ของรูปโปรไฟล์ | | account.electronicSignatureKey | string | fileKey ของลายเซ็น | | account.roleKey | string | `ADMIN`, `NONE` | | account.defaultOrganizationId / defaultOrganizationKey | string | องค์กรเริ่มต้น | | action | string | `ADD`, `REMOVE` | ค่า message ถูกห่อเป็น `{ "value": "" }` ต้อง parse ค่า `value` อีกชั้น (ดู [Authentication Service](authenticationService.md#sync-auth)) --- ### set-profile ให้ service อื่นสร้าง / แก้โปรไฟล์ของผู้ใช้ผ่าน Kafka topic: set-profile | key | Type | คำอธิบาย | | --- | --- | --- | | account.appKey | string | appKey | | account.authId | string | ผู้ใช้ | | account.username | string | username | | account.emails | string[] | email | | account.firstName / middleName / lastName / displayName | string | ชื่อ | | account.gender | string | `MALE`, `FEMALE`, `NOT_SPECIFIED` | | account.profileImage | string | fileKey ของรูปโปรไฟล์ | | account.roleKey | string | role | | action | string | `ADD`, `REMOVE` | ---

## Kafka Produce Reference --- ### sync-profile ส่งข้อมูลโปรไฟล์หลังมีการสร้าง / แก้ / ลบ ให้ service อื่นที่ต้องใช้ชื่อหรือรูปของผู้ใช้ (เช่น ACL Service, Notification Service) topic: sync-profile | key | Type | คำอธิบาย | | --- | --- | --- | | profile.id | string | id ของโปรไฟล์ | | profile.appKey | string | appKey | | profile.authId | string | id ของบัญชี | | profile.username | string | username | | profile.firstName / middleName / lastName / displayName | string | ชื่อ | | profile.gender | string | เพศ | | profile.profileImage | string | fileKey ของรูปโปรไฟล์ | | profile.electronicSignatureKey | string | fileKey ของลายเซ็น | | profile.defaultOrganizationId / defaultOrganizationKey | string | องค์กรเริ่มต้น | | action | string | `ADD`, `REMOVE` | --- ### sync-permission ส่ง permission ของ `profile` ให้ [ACL Service](aclService.md) (ตอนได้ `refresh-data`) topic: sync-permission --- > อัปเดตจากโค้ด gumon-profile-service@c4b90a4 · 2026-10-05 --- # Notification Service Service กลางสำหรับส่งการแจ้งเตือนของทุก service ในระบบ · serviceKey: `notification` ช่องทางที่รองรับ - **In-app** แจ้งเตือนในแอปแบบ realtime ผ่าน WebSocket (Socket.IO) และเก็บเป็นรายการให้ดึงย้อนหลัง / อ่าน / ปิดได้ - **Email** ส่งผ่าน SMTP provider ที่ตั้งค่าต่อแอป (เลือกตาม priority และสลับ provider อัตโนมัติเมื่อเกิน rate limit หรือส่งไม่สำเร็จ) - **SMS** ส่งผ่าน SMS provider ที่ตั้งค่าต่อแอป (หลักการเดียวกับ email) - **Webhook** มี API จัดการ provider แล้ว ช่องทางส่งอยู่ระหว่างพัฒนา service อื่นขอส่งแจ้งเตือนผ่าน Kafka topic `create-notification` (ไม่เรียก API ของ notification ตรง) · ตั้งเวลาส่งล่วงหน้าได้ โดย notification จะฝากเวลาไว้กับ [Schedule Service](scheduleService.md) ให้เอง ทุกช่องทางส่งผ่านคิว (Redis) ทำให้รัน notification หลาย replica ได้ ทั้งฝั่งคิวและ WebSocket
- [การขอส่งแจ้งเตือนจาก service อื่น](#การขอสงแจงเตอนจาก-service-อน) - [การเชื่อมต่อ WebSocket](#การเชอมตอ-websocket) - [API Reference](#api-reference) - [kafka consume Reference](#kafka-consume-reference) - [kafka produce Reference](#kafka-produce-reference) ---

## การขอส่งแจ้งเตือนจาก service อื่น produce ข้อความเข้า topic `create-notification` · header `appKey` = แอปที่จะส่ง, `serviceKey` = `notification` (service ปลายทาง) ใส่ช่องทางที่ต้องการได้หลายช่องทางในข้อความเดียว (`appNotification`, `email`, `sms`) ช่องทางที่ไม่ใส่จะไม่ถูกส่ง ```json { "action": "ADD", "notification": { "isSchedule": false, "organizationId": "665000000000000000000010", "refKey": "665000000000000000000020", "refType": "leaveRequest", "appNotification": { "type": "SELECT", "authId": ["665000000000000000000030"], "notificationType": "GENERAL", "title": "คำขอลาได้รับการอนุมัติ", "content": "คำขอลาวันที่ 1 พ.ย. ได้รับการอนุมัติแล้ว", "from": "SYSTEM", "to": "665000000000000000000030", "displayType": "SUCCESS", "url": "/leave/665000000000000000000020" }, "email": { "from": "noreply@example.com", "to": "user@example.com", "subject": "คำขอลาได้รับการอนุมัติ", "html": "

คำขอลาของคุณได้รับการอนุมัติแล้ว

" } } } ``` ตั้งเวลาส่ง: ใส่ `"isSchedule": true` และ `schedule` (โครงเดียวกับ payload `set-schedule` ของ [Schedule Service](scheduleService.md#set-schedule) เช่น `notificationAt`, `timeZone`, `isRecurring`, `recurrence`, `expiryAt`) · notification จะสร้างรายการไว้ก่อนแล้วลงทะเบียนเวลากับ schedule เอง ผู้ขอไม่ต้องส่ง `set-schedule` เอง **ยกเลิกแจ้งเตือนที่ตั้งเวลาไว้** (เช่น นัดถูกยกเลิก ไม่ต้องส่ง SMS เตือนแล้ว) — ส่ง `REMOVE` พร้อม `refKey` + `refType` เดียวกับที่ใช้ตอน `ADD` ```json { "action": "REMOVE", "notification": { "refKey": "665000000000000000000020", "refType": "booking.reminder" } } ``` - ยกเลิกทุกรายการที่ยังไม่ส่ง (in-app / email / SMS) ของ `refKey` + `refType` นั้นในแอปเดียวกัน และแจ้ง schedule ให้ถอนเวลา ⇒ ไม่ถูกส่ง - ต้องใส่ทั้ง `refKey` และ `refType` (ไม่ครบ = ERROR ไม่ยกเลิกอะไร) · ส่งซ้ำ หรืออ้างถึงที่ไม่มี = SUCCESS โดย `removedCount: 0` - จะยกเลิกได้ ตอน `ADD` ต้องใส่ `refKey` / `refType` ไว้ · ตั้ง `refType` ขึ้นต้นด้วยชื่อ service ของตัวเอง (เช่น `booking.reminder`) กันชนกับ service อื่นในแอปเดียวกัน ผลการรับคำขอส่งกลับทาง topic `create-notification-result` (`SUCCESS` / `ERROR`) รายละเอียดฟิลด์ดูที่ [Create Notification](#create-notification) --- ## การเชื่อมต่อ WebSocket ใช้รับแจ้งเตือน in-app แบบ realtime ที่หน้าบ้าน 1. เรียก [Get My AppNotification Config](#get-my-appnotification-config) เพื่อรับ `roomId` ของ user ที่ login 2. เชื่อมต่อ Socket.IO (default namespace) ที่ host ของ notification service โดยส่ง header `authorization` (access token) และ `roomId` (ทาง query `?roomId=` หรือ header `roomid`) 3. ฟัง event `appNotification` ได้ข้อมูลรูป `{ action, data }` - `action: CREATE` = แจ้งเตือนใหม่ - `action: UPDATE` = แจ้งเตือนเดิมถูกอ่าน / ปิด - `data` = ข้อมูล [AppNotification](#appnotification) ```js import { io } from "socket.io-client"; const socket = io(NOTIFICATION_URL, { query: { roomId }, extraHeaders: { authorization: `Bearer ${accessToken}` }, }); socket.on("appNotification", ({ action, data }) => { console.log(action, data.content.title); }); ``` --- ## API Reference --- GraphQL endpoint `/graphql` · ทุก API ต้อง login (header `authorization`) และต้องมี **permission ชื่อเดียวกับ API** (เช่น `getAppNotifications`) ยกเว้น API แบบ `...ByAppKey` ที่ใช้ app credential ของระบบภายนอก (header `X-APP-CLIENT-ID` + `X-APP-CLIENT-SECRET`) · การระบุ `appKey` อื่นนอกจากแอปที่ login ต้องมี permission `systemApp` API ที่มีคำว่า `My` ทำงานกับข้อมูลของ user ที่ login เท่านั้น ส่วน API ที่ไม่มี `My` ทำงานกับข้อมูลทั้งแอป (สำหรับผู้ดูแล) ทุก API แบบรายการรับ input รูปเดียวกัน `{ filter, search, sort: { sortBy, sortOrder }, pagination: { limit, page } }` และคืน `pagination { limit page totalItems totalPages hasPrevPage hasNextPage prevPage nextPage }` ### Query เป็น API ที่ใช้สำหรับการ Query ข้อมูลออกมา ไม่มีการแก้ไข Data #### Get My AppNotification Config คืน `roomId` (String) ของ user ที่ login สำหรับเชื่อมต่อ WebSocket ```graphql query { getMyAppNotificationConfig } ``` --- #### Get App Notifications ดึงแจ้งเตือน in-app ทั้งหมดของแอป ```graphql query { getAppNotifications(input: { filter: { isRead: false, notificationType: URGENT } sort: { sortBy: createdAt, sortOrder: DESC } pagination: { limit: 20, page: 1 } }) { appNotifications { _id authId notificationType isRead isClosed sentAt content { title content displayType url } } pagination { totalItems totalPages } } } ``` filter: `organizationId`, `authId`, `isSchedule`, `scheduleRefKey`, `scheduleRefType`, `sentStatus`, `isReSent`, `reSentBy`, `notificationType`, `isRead`, `isClosed`, `displayType`, `createdBy`, `updatedBy`, `createdAtStart/End`, `updatedAtStart/End` · search: `organizationId`, `scheduleRefKey`, `scheduleRefType`, `content { title content from to }` Response : `AppNotificationPagination` = `{ appNotifications: [AppNotification], pagination }` ##### AppNotification | key | Type | คำอธิบาย | | --- | --- | --- | | _id | ID | id ที่ใช้อ้างอิง | | organizationId | String | องค์กรที่ส่ง (ถ้าส่งในนามองค์กร) | | authId | String | user ผู้รับ | | isSchedule | Boolean | เป็นการตั้งเวลาส่งหรือไม่ | | scheduleRefKey / scheduleRefType | String | อ้างอิงกับ schedule (เมื่อ isSchedule = true) | | sentStatus | ENUM | `PREPARE` (รอเข้าคิว) · `QUEUED` · `SUCCEED` · `FAIL` | | sentAt | Date | เวลาที่จะส่ง / ส่ง | | succeedSentAt | Date | เวลาที่ส่งสำเร็จ | | isReSent / reSentBy | Boolean / String | ถูกส่งซ้ำแล้ว / id ต้นฉบับ | | notificationType | ENUM | `VERY_URGENT`, `URGENT`, `IMPORTANT`, `GENERAL`, `REMINDER`, `APP_NOTIFICATION` | | isRead / readAt | Boolean / Date | อ่านแล้ว / เวลาอ่าน | | isClosed / closedAt | Boolean / Date | ปิดการแสดงแล้ว (ยังดึงย้อนหลังได้) / เวลาปิด | | content.title | String | หัวข้อ | | content.content | String | เนื้อหา | | content.from / content.to | String | ข้อความบอกผู้ส่ง / ผู้รับ เช่น "จากโรงแรม A", "ถึงคุณสมชาย" | | content.displayType | ENUM | `SUCCESS`, `INFO`, `WARNING`, `ERROR` (ให้หน้าบ้านเลือกรูปแบบแสดงผล) | | content.url | String | ลิงก์เมื่อกดแจ้งเตือน | | createdAt / updatedAt | Date | | --- #### Get App Notification By ID ```graphql query { getAppNotificationById(appNotificationId: "6650f0c2a1b2c3d4e5f60718") { _id authId isRead content { title } } } ``` Response : `AppNotification` --- #### Get My App Notification ดึงแจ้งเตือน in-app ของ user ที่ login (filter เหมือน Get App Notifications ยกเว้น `authId`) ```graphql query { getMyAppNotification(input: { filter: { isClosed: false }, sort: { sortBy: createdAt, sortOrder: DESC }, pagination: { limit: 20, page: 1 } }) { appNotifications { _id isRead content { title content displayType url } createdAt } pagination { totalItems hasNextPage } } } ``` Response : `AppNotificationPagination` --- #### Get My App Notification By ID ```graphql query { getMyAppNotificationById(appNotificationId: "6650f0c2a1b2c3d4e5f60718") { _id isRead content { title content } } } ``` Response : `AppNotification` --- #### Get Email Transactions ดึงประวัติ email ทั้งหมดของแอป ```graphql query { getEmailTransactions(input: { filter: { sentStatus: FAIL }, pagination: { limit: 20, page: 1 } }) { emailTransactions { _id sentStatus sentAt succeedSentAt content { to subject } transactionLogs { sentAt isSucceed } } pagination { totalItems } } } ``` filter: `organizationId`, `isSchedule`, `scheduleRefKey`, `scheduleRefType`, `sentStatus`, `isReSent`, `reSentBy`, `createdBy`, `updatedBy` · search: `scheduleRefKey`, `scheduleRefType` Response : `EmailTransactionPagination` = `{ emailTransactions: [EmailTransaction], pagination }` ##### EmailTransaction | key | Type | คำอธิบาย | | --- | --- | --- | | _id | ID | id ที่ใช้อ้างอิง | | organizationId | String | องค์กรที่ส่ง | | isSchedule, scheduleRefKey, scheduleRefType | | การตั้งเวลา (เหมือน AppNotification) | | sentStatus | ENUM | `PREPARE`, `QUEUED`, `SUCCEED`, `FAIL` | | sentAt / succeedSentAt | Date | เวลาที่จะส่ง / เวลาส่งสำเร็จ | | isReSent / reSentBy | Boolean / String | การส่งซ้ำ | | content | EmailTransactionContent | `to`, `subject`, `html`, `text`, `attachments [JSON]`, `cc`, `bcc`, `from` | | transactionLogs | [EmailTransactionLog] | ประวัติแต่ละครั้งที่พยายามส่ง: `sentAt`, `emailProviderId`, `isSucceed`, `response` | --- #### Get My Email Transactions ดึงประวัติ email ที่ user ที่ login เป็นผู้สร้าง · input / response เหมือน Get Email Transactions ```graphql query { getMyEmailTransactions(input: { pagination: { limit: 20, page: 1 } }) { emailTransactions { _id sentStatus content { to subject } } } } ``` --- #### Get Email Transaction By ID ```graphql query { getEmailTransactionById(emailTransactionId: "6650f0c2a1b2c3d4e5f60718") { _id sentStatus content { to subject } transactionLogs { isSucceed response } } } ``` Input: `emailTransactionId: ID!`, `appKey: String` · Response : `EmailTransaction` --- #### Get My Email Transaction By ID ```graphql query { getMyEmailTransactionById(emailTransactionId: "6650f0c2a1b2c3d4e5f60718") { _id sentStatus } } ``` Response : `EmailTransaction` --- #### Get Email Transactions By AppKey สำหรับระบบภายนอกที่เรียกด้วย app credential ```graphql query { getEmailTransactionsByAppKey(appKey: "yourAppKey", input: { pagination: { limit: 20, page: 1 } }) { emailTransactions { _id sentStatus } } } ``` Response : `EmailTransactionPagination` --- #### Get Sms Transactions ดึงประวัติ SMS ทั้งหมดของแอป · input เหมือน Get Email Transactions ```graphql query { getSmsTransactions(input: { filter: { sentStatus: SUCCEED }, pagination: { limit: 20, page: 1 } }) { smsTransactions { _id sentStatus sentAt content { msisdn message sender } } pagination { totalItems } } } ``` Response : `SmsTransactionPagination` = `{ smsTransactions: [SmsTransaction], pagination }` SmsTransaction มีฟิลด์สถานะ / การตั้งเวลา / การส่งซ้ำ / `transactionLogs` เหมือน EmailTransaction และ `content`: | key | Type | คำอธิบาย | | --- | --- | --- | | msisdn | String | เบอร์ผู้รับ | | message | String | ข้อความ | | sender | String | ชื่อผู้ส่ง (sender name) | | scheduled_delivery | String | เวลาส่งฝั่ง provider (ถ้า provider รองรับ) | | force | String | ตัวเลือกเฉพาะ provider | | shorten_url / tracking_url | String | ตัวเลือกย่อ / ติดตามลิงก์ (ถ้า provider รองรับ) | | expire | String | อายุข้อความ (ถ้า provider รองรับ) | --- #### Get My Sms Transactions ```graphql query { getMySmsTransactions(input: { pagination: { limit: 20, page: 1 } }) { smsTransactions { _id sentStatus content { msisdn } } } } ``` Response : `SmsTransactionPagination` --- #### Get Sms Transaction By ID ```graphql query { getSmsTransactionById(smsTransactionId: "6650f0c2a1b2c3d4e5f60718") { _id sentStatus content { msisdn message } } } ``` Input: `smsTransactionId: ID!`, `appKey: String` · Response : `SmsTransaction` --- #### Get My Sms Transaction By ID ```graphql query { getMySmsTransactionById(smsTransactionId: "6650f0c2a1b2c3d4e5f60718") { _id sentStatus } } ``` Response : `SmsTransaction` --- #### Get Sms Transactions By AppKey สำหรับระบบภายนอกที่เรียกด้วย app credential ```graphql query { getSmsTransactionsByAppKey(appKey: "yourAppKey", input: { pagination: { limit: 20, page: 1 } }) { smsTransactions { _id sentStatus } } } ``` Response : `SmsTransactionPagination` --- #### Get Email Providers ดึงรายการ email provider (SMTP) ของแอป ```graphql query { getEmailProviders(input: { pagination: { limit: 10, page: 1 } }) { emailProviders { _id emailProviderKey title priority isActive maxEmailsPerMinute maxRetriesPerProvider totalSentCount totalFailedCount config { senderName host port secure } } pagination { totalItems } } } ``` ##### EmailProvider | key | Type | คำอธิบาย | | --- | --- | --- | | _id | ID | id ที่ใช้อ้างอิง | | emailProviderKey | String | key อ้างอิง | | priority | Int | ลำดับการเลือกใช้ | | title / description | String | ชื่อ / รายละเอียด | | isActive | Boolean | เปิดใช้งาน | | config | EmailProviderConfig | `senderName`, `host`, `port`, `secure`, `username` | | maxEmailsPerMinute | Int | จำนวน email สูงสุดต่อนาที ก่อนสลับไป provider ถัดไป | | maxRetriesPerProvider | Int | จำนวนครั้งที่ลองส่งซ้ำก่อนสลับ provider | | totalSentCount / totalFailedCount | Int | สถิติการส่ง | | sentDueToLimitCount, failedDueToLimitCount, sentDueToMaxRetriesCount, failedDueToMaxRetriesCount | Int | สถิติการสลับ provider | --- #### Get Email Provider By ID ```graphql query { getEmailProviderById(emailProviderId: "6650f0c2a1b2c3d4e5f60718") { _id title isActive } } ``` Response : `EmailProvider` --- #### Get Email Provider By Key ```graphql query { getEmailProviderByKey(emailProviderKey: "main-smtp") { _id title isActive } } ``` Response : `EmailProvider` --- #### Get Email Providers By AppKey สำหรับระบบภายนอกที่เรียกด้วย app credential ```graphql query { getEmailProvidersByAppKey(appKey: "yourAppKey") { emailProviders { _id title } } } ``` --- #### Get Sms Providers ดึงรายการ SMS provider ของแอป · ฟิลด์เหมือน EmailProvider โดยใช้ `smsProviderKey`, `maxSmsPerMinute` และเพิ่ม `providerType` ```graphql query { getSmsProviders(input: { pagination: { limit: 10, page: 1 } }) { smsProviders { _id smsProviderKey title providerType priority isActive maxSmsPerMinute } } } ``` `providerType`: enum `THAIBULKSMS`, `SMSMKT`, `INFOBIP`, `THSMS`, `MAILBIT`, `AWS`, `TWILIO` · ที่ส่งได้ในปัจจุบันคือ `THAIBULKSMS` และ `TWILIO` --- #### Get Sms Provider By ID ```graphql query { getSmsProviderById(smsProviderId: "6650f0c2a1b2c3d4e5f60718") { _id title providerType } } ``` --- #### Get Sms Provider By Key ```graphql query { getSmsProviderByKey(smsProviderKey: "main-sms") { _id title providerType } } ``` --- #### Get Sms Providers By AppKey สำหรับระบบภายนอกที่เรียกด้วย app credential ```graphql query { getSmsProvidersByAppKey(appKey: "yourAppKey") { smsProviders { _id title } } } ``` --- #### Get Webhook Providers ดึงรายการ webhook provider ของแอป · ฟิลด์เหมือน EmailProvider โดยใช้ `webhookProviderKey`, `maxWebhooksPerMinute` (ไม่มี priority) ```graphql query { getWebhookProviders(input: { pagination: { limit: 10, page: 1 } }) { webhookProviders { _id webhookProviderKey title isActive } } } ``` --- #### Get Webhook Provider By ID ```graphql query { getWebhookProviderById(webhookProviderId: "6650f0c2a1b2c3d4e5f60718") { _id title } } ``` --- #### Get Webhook Provider By Key ```graphql query { getWebhookProviderByKey(webhookProviderKey: "main-hook") { _id title } } ``` --- #### Get Webhook Providers By AppKey สำหรับระบบภายนอกที่เรียกด้วย app credential ```graphql query { getWebhookProvidersByAppKey(appKey: "yourAppKey") { webhookProviders { _id title } } } ``` --- ### Mutation เป็น API ที่ใช้สำหรับการแก้ไขข้อมูล #### Create App Notification สร้างแจ้งเตือน in-app จากหน้าบ้าน (ผลเหมือนส่ง `create-notification` ที่มี `appNotification`) ```graphql mutation { createAppNotification(input: { type: SELECT authIds: ["665000000000000000000030"] notificationType: GENERAL content: { title: "ประกาศ", content: "ระบบจะปิดปรับปรุงคืนนี้", displayType: WARNING } isSchedule: true schedule: { notificationAt: "2026-11-01T11:00:00Z", timeZone: "Asia/Bangkok" } }) { _id authId sentStatus sentAt } } ``` | key | Type | คำอธิบาย | | --- | --- | --- | | appKey | String | ไม่ใส่ = แอปที่ login | | organizationId | String | ส่งในนามองค์กร | | type | ENUM | `SELECT` (ส่งตาม authIds) · `ALL` (ส่งทุก user ในแอป) ค่าเริ่มต้น `SELECT` | | authIds | [ID] | ผู้รับ (เมื่อ type = SELECT) | | content | CreateAppNotificationContentInput! | `title`, `content`, `from`, `to`, `displayType` (ค่าเริ่มต้น `INFO`), `url` | | notificationType | ENUM | ค่าเริ่มต้น `APP_NOTIFICATION` | | isSchedule | Boolean | ตั้งเวลาส่ง (ค่าเริ่มต้น `false`) | | schedule | CreateAppNotificationScheduleInput | `notificationAt`, `timeZone`, `isRecurring`, `recurrence { type interval value specific { dayOfWeek dayOfMonth } }`, `expiryAt` | Response : `[AppNotification]` (หนึ่งรายการต่อผู้รับ) --- #### Read App Notification ตั้งแจ้งเตือนของแอปเป็นอ่านแล้ว (ของ user ใดก็ได้ในแอป สำหรับผู้ดูแล) ```graphql mutation { readAppNotification(input: { type: SELECT, appNotificationIds: ["6650f0c2a1b2c3d4e5f60718"] }) { _id isRead readAt } } ``` Input `ReadAppNotificationInput`: `type` `SELECT` (เฉพาะ `appNotificationIds`) / `ALL` · `appNotificationIds: [ID]` Response : `[AppNotification]` --- #### Read My App Notification ตั้งแจ้งเตือนของ user ที่ login เป็นอ่านแล้ว · `type: ALL` = อ่านทั้งหมด ```graphql mutation { readMyAppNotification(input: { type: ALL }) { _id isRead } } ``` --- #### Closed App Notification ปิดการแสดงแจ้งเตือนของแอป (สำหรับผู้ดูแล) · input `CloseAppNotificationInput` รูปเดียวกับ Read ```graphql mutation { closedAppNotification(input: { type: SELECT, appNotificationIds: ["6650f0c2a1b2c3d4e5f60718"] }) { _id isClosed closedAt } } ``` --- #### Closed My App Notification ปิดการแสดงแจ้งเตือนของ user ที่ login ```graphql mutation { closedMyAppNotification(input: { type: SELECT, appNotificationIds: ["6650f0c2a1b2c3d4e5f60718"] }) { _id isClosed } } ``` --- #### Create Email Transaction สร้างและส่ง email จากหน้าบ้าน ```graphql mutation { createEmailTransaction(input: { content: { from: "noreply@example.com", to: "user@example.com", subject: "ยินดีต้อนรับ", html: "

สวัสดี

" } }) { _id sentStatus } } ``` | key | Type | คำอธิบาย | | --- | --- | --- | | appKey | String | ไม่ใส่ = แอปที่ login | | organizationId | String | ส่งในนามองค์กร | | isSchedule | Boolean | ตั้งเวลาส่ง (ค่าเริ่มต้น `false`) | | sentAt | Date | เวลาที่ต้องการส่ง | | content.to | String! | ผู้รับ | | content.subject | String! | หัวข้อ | | content.from | String! | ผู้ส่ง | | content.html / content.text | String | เนื้อหา HTML / ข้อความล้วน (ส่งเนื้อหาสำเร็จรูปมาเอง ไม่มี template) | | content.cc / content.bcc | String | คั่นด้วย comma | | content.attachments | [JSON] | ไฟล์แนบ (รูปแบบ attachment ของ nodemailer) | Response : `EmailTransaction` --- #### Delete Email Transaction ลบประวัติ email ได้หลายรายการ ```graphql mutation { deleteEmailTransaction(emailTransactionIds: ["6650f0c2a1b2c3d4e5f60718"]) { _id } } ``` Input: `emailTransactionIds: [ID]!`, `appKey: String` · Response : `[EmailTransaction]` --- #### Delete My Email Transaction ลบประวัติ email ของ user ที่ login ```graphql mutation { deleteMyEmailTransaction(emailTransactionIds: ["6650f0c2a1b2c3d4e5f60718"]) { _id } } ``` --- #### Resent Email Transaction ส่งซ้ำ: สร้างรายการใหม่จากรายการเดิม (รายการเดิมถูกตั้ง `isReSent = true`, รายการใหม่มี `reSentBy` = id เดิม) ```graphql mutation { resentEmailTransaction(emailTransactionIds: ["6650f0c2a1b2c3d4e5f60718"]) { _id reSentBy sentStatus } } ``` --- #### Resent My Email Transaction ส่งซ้ำ email ของ user ที่ login ```graphql mutation { resentMyEmailTransaction(emailTransactionIds: ["6650f0c2a1b2c3d4e5f60718"]) { _id reSentBy } } ``` --- #### Sent All Queue Email Transaction นำ email ที่ยังค้างสถานะคิวกลับเข้าคิวส่งอีกครั้ง (สำหรับผู้ดูแล) ```graphql mutation { sentAllQueueEmailTransaction { _id sentStatus } } ``` Input: `appKey: String` · Response : `[EmailTransaction]` --- #### Create Sms Transaction สร้างและส่ง SMS จากหน้าบ้าน ```graphql mutation { createSmsTransaction(input: { content: { msisdn: "0812345678", message: "รหัสยืนยันของคุณคือ 123456", sender: "MyApp" } }) { _id sentStatus } } ``` Input: `appKey`, `organizationId`, `isSchedule`, `sentAt`, `content` (ฟิลด์ตาม [Get Sms Transactions](#get-sms-transactions)) · Response : `SmsTransaction` --- #### Delete Sms Transaction ```graphql mutation { deleteSmsTransaction(smsTransactionIds: ["6650f0c2a1b2c3d4e5f60718"]) { _id } } ``` --- #### Delete My Sms Transaction ```graphql mutation { deleteMySmsTransaction(smsTransactionIds: ["6650f0c2a1b2c3d4e5f60718"]) { _id } } ``` --- #### Resent Sms Transaction ```graphql mutation { resentSmsTransaction(smsTransactionIds: ["6650f0c2a1b2c3d4e5f60718"]) { _id reSentBy } } ``` --- #### Resent My Sms Transaction ```graphql mutation { resentMySmsTransaction(smsTransactionIds: ["6650f0c2a1b2c3d4e5f60718"]) { _id reSentBy } } ``` --- #### Sent All Queue Sms Transaction นำ SMS ที่ยังค้างสถานะคิวกลับเข้าคิวส่งอีกครั้ง (สำหรับผู้ดูแล) ```graphql mutation { sentAllQueueSmsTransaction { _id sentStatus } } ``` --- #### Create Email Provider เพิ่ม SMTP provider ให้แอป ```graphql mutation { createEmailProvider(create: { title: "Main SMTP" priority: 0 config: { senderName: "MyApp", host: "smtp.example.com", port: 587, secure: false, username: "smtp-user", password: "********" } maxEmailsPerMinute: 600 maxRetriesPerProvider: 3 }) { _id emailProviderKey } } ``` | key | Type | คำอธิบาย | | --- | --- | --- | | appKey | String | ไม่ใส่ = แอปที่ login | | emailProviderKey | String | key อ้างอิง | | priority | Int | ลำดับการเลือกใช้ (ค่าเริ่มต้น 0) | | title | String! | ชื่อ | | description | String | รายละเอียด | | isActive | Boolean | ค่าเริ่มต้น `true` | | config | CreateEmailProviderConfigInput! | `senderName` (บังคับ), `host`, `port`, `secure`, `username`, `password` | | maxEmailsPerMinute | Int | ค่าเริ่มต้น 600 | | maxRetriesPerProvider | Int | ค่าเริ่มต้น 3 | Response : `EmailProvider` --- #### Update Email Provider ```graphql mutation { updateEmailProvider(emailProviderId: "6650f0c2a1b2c3d4e5f60718", update: { isActive: false }) { _id isActive } } ``` Input: `emailProviderId: ID!`, `update: UpdateEmailProviderInput!` (ฟิลด์เดียวกับ create ไม่บังคับ), `appKey: String` --- #### Delete Email Provider ```graphql mutation { deleteEmailProvider(emailProviderId: "6650f0c2a1b2c3d4e5f60718") { _id } } ``` --- #### Create Sms Provider เพิ่ม SMS provider ให้แอป ```graphql mutation { createSmsProvider(create: { title: "ThaiBulkSMS" providerType: THAIBULKSMS config: { senderName: "MyApp", apiKey: "********", apiSecret: "********" } }) { _id smsProviderKey } } ``` ฟิลด์เหมือน Create Email Provider โดยใช้ `smsProviderKey`, `maxSmsPerMinute` และเพิ่ม `providerType` · `config`: `senderName` (บังคับ), `apiKey`, `apiSecret`, `hostname`, `username`, `password`, `port`, `headers`, `smsBody`, `etc` (ใช้ตามที่ provider แต่ละเจ้าต้องการ) --- #### Update Sms Provider ```graphql mutation { updateSmsProvider(smsProviderId: "6650f0c2a1b2c3d4e5f60718", update: { priority: 1 }) { _id priority } } ``` --- #### Delete Sms Provider ```graphql mutation { deleteSmsProvider(smsProviderId: "6650f0c2a1b2c3d4e5f60718") { _id } } ``` --- #### Create Webhook Provider ```graphql mutation { createWebhookProvider(create: { title: "Partner hook" config: { senderName: "MyApp", url: "https://partner.example.com/hook", method: POST } }) { _id webhookProviderKey } } ``` `config`: `senderName` (บังคับ), `url` (บังคับ), `method` (`GET` / `POST` ค่าเริ่มต้น `GET`), `apiKey`, `apiSecret`, `username`, `password`, `headers`, `WebhookBody` · และ `maxWebhooksPerMinute`, `maxRetriesPerProvider` --- #### Update Webhook Provider ```graphql mutation { updateWebhookProvider(webhookProviderId: "6650f0c2a1b2c3d4e5f60718", update: { isActive: false }) { _id isActive } } ``` --- #### Delete Webhook Provider ```graphql mutation { deleteWebhookProvider(webhookProviderId: "6650f0c2a1b2c3d4e5f60718") { _id } } ``` --- ## kafka consume Reference ทุก topic ตรวจว่า notification มีสิทธิ์ในแอปตาม `appKey` (มี appCertificate ของแอปนั้น) ก่อนทำงาน ### Standard topics | topic | คำอธิบาย | | --- | --- | | `init-system` | ตั้งระบบจากศูนย์ (เฉพาะ core set) | | `refresh-data` | ส่งข้อมูลที่ตัวเองถือขึ้นไปใหม่ (เช่น permission) และล้าง cache | | `sync-app-certificate` | รับ appCertificate ของแอปที่ notification มีสิทธิ์ | | `sync-app-credential` | รับข้อมูลการเข้าใช้ของ user ในแอป (ใช้ตรวจ token) | | `sync-application` | รับข้อมูลแอป | | `sync-user-policy` | รับ UserPolicy สำหรับตรวจ permission | --- ### Create Notification คำขอส่งแจ้งเตือนจาก service อื่น · ดูภาพรวมที่ [การขอส่งแจ้งเตือนจาก service อื่น](#การขอสงแจงเตอนจาก-service-อน) topic: create-notification header: appKey, serviceKey = notification Action: ADD | REMOVE notification | key | Type | คำอธิบาย | | --- | --- | --- | | isSchedule | boolean | `true` = ตั้งเวลาส่งตาม `schedule` · `false` = ส่งทันที | | schedule | object | โครงเดียวกับ `set-schedule` (`notificationAt`, `timeZone`, `isRecurring`, `recurrence`, `expiryAt`, `title`, `description`) | | organizationId | string | ส่งในนามองค์กร | | refKey / refType | string | อ้างอิงข้อมูลฝั่งผู้ขอ · **ใช้ยกเลิกด้วย `REMOVE`** — ตั้ง `refType` ขึ้นต้นด้วยชื่อ service ของตัวเอง | | appNotification | object | แจ้งเตือน in-app (ดูด้านล่าง) | | email | object | `to`, `subject`, `html`, `text`, `cc`, `bcc`, `attachments`, `from` | | sms | object | `msisdn`, `message`, `sender`, `scheduledDelivery`, `force`, `shortenUrl`, `trackingUrl`, `expire` | appNotification | key | Type | คำอธิบาย | | --- | --- | --- | | type | ENUM | `SELECT` = ส่งตาม `authId` · `ALL` = ส่งทุก user ในแอป | | authId | string[] | ผู้รับ (เมื่อ type = SELECT) | | notificationType | ENUM | `VERY_URGENT`, `URGENT`, `IMPORTANT`, `GENERAL`, `REMINDER`, `APP_NOTIFICATION` | | title | string | หัวข้อ | | content | string | เนื้อหา | | from / to | string | ข้อความบอกผู้ส่ง / ผู้รับ | | displayType | ENUM | `SUCCESS`, `INFO`, `WARNING`, `ERROR` | | url | string | ลิงก์เมื่อกดแจ้งเตือน | --- ### Schedule Alarm รับ alarm จาก [Schedule Service](scheduleService.md#schedule-alarm) สำหรับแจ้งเตือนที่ตั้งเวลาไว้ · รับเฉพาะรายการที่ `scheduleAlarm.serviceKey = notification` แล้วแยกช่องทางตาม `scheduleRefType` (`appNotificationTransaction`, `emailTransaction`, `smsTransaction`) จากนั้นนำรายการเข้าคิวส่ง และตอบ `schedule-alarm-result` topic: schedule-alarm Action: ADD --- ### Sync Profile รับรายชื่อ user ของแต่ละแอป ใช้สำหรับส่งแจ้งเตือนแบบ `type: ALL` topic: sync-profile Action: ADD | REMOVE | key | Type | คำอธิบาย | | --- | --- | --- | | profile.id | string | id ของ profile | | profile.appKey | string | appKey | | profile.authId | string | id ของ user | | profile.username, firstName, middleName, lastName, displayName, gender, profileImage | string | ข้อมูล profile | --- ## Kafka Produce Reference ### Set Schedule ลงทะเบียนเวลาส่งกับ schedule เมื่อคำขอมี `isSchedule = true` (หนึ่งรายการต่อ transaction) · `scheduleRefType` = `appNotificationTransaction` / `emailTransaction` / `smsTransaction` topic: set-schedule header: appKey, serviceKey = schedule Action: ADD --- ### Schedule Alarm Result ตอบกลับ schedule หลังรับ alarm (ส่งคืน `alarmRefKey` พร้อม `clientReceivedAt`, `clientSentAt`) topic: schedule-alarm-result header: appKey, serviceKey = schedule Action: ADD --- ### Create Notification Result ผลการรับคำขอจาก `create-notification` topic: create-notification-result Action: SUCCESS | ERROR | key | Type | คำอธิบาย | | --- | --- | --- | | action | string | `SUCCESS` หรือ `ERROR` | | requestAction | string | `REMOVE` เมื่อเป็นผลของคำขอยกเลิก | | notification | object | คำขอเดิม | | removedCount | number | จำนวนรายการที่ถูกยกเลิก (เฉพาะ `REMOVE`) | | error | object | รายละเอียดข้อผิดพลาด (กรณี ERROR) | --- ### Sync Permission ส่ง permission ของ notification ให้ access-control เมื่อได้รับ `refresh-data` เพื่อนำไปผูกกับ role (ชื่อ permission ตรงกับชื่อ API ด้านบน และ `systemApp`) topic: sync-permission Action: ADD --- > อัปเดตจากโค้ด gumon-notification-service@318d224 · 2026-10-05 --- # Schedule Service Service กลางสำหรับตั้งเวลาและรอบการทำงาน (cron/job) ของทุก service ในระบบ · serviceKey: `schedule` service อื่นไม่ต้องมี cron ของตัวเอง แต่ฝาก "นัดหมาย" ไว้ที่ schedule ผ่าน Kafka topic `set-schedule` เมื่อถึงเวลา schedule จะส่ง event `schedule-alarm` กลับไปหา service เจ้าของนัดหมาย ทำไมต้องรวมไว้ที่เดียว: ถ้าแต่ละ service ตั้ง cron เอง เมื่อ scale หลาย replica ทุก replica จะยิงงานเดียวกันซ้ำ · เมื่อใช้ schedule กลาง แต่ละ service รับ `schedule-alarm` ผ่าน consumer group ของตัวเอง Kafka จึงส่งแต่ละ alarm ให้ replica เดียวในกลุ่ม ⇒ service ปลายทางรันหลาย replica ได้โดยไม่ยิงซ้ำ ตัว schedule service เองออกแบบให้รัน **replica เดียว** (single instance)
- [ภาพรวมการใช้งาน](#ภาพรวมการใชงาน) - [API Reference](#api-reference) - [kafka consume Reference](#kafka-consume-reference) - [kafka produce Reference](#kafka-produce-reference) ---

## ภาพรวมการใช้งาน ``` service X ──set-schedule {action: ADD | REMOVE, schedule}──────────────▶ schedule schedule ──set-schedule-result {action: SUCCESS | ERROR}──────────────▶ service X [ตรวจทุก 1 นาที] หา schedule ที่ถึงเวลา → คำนวณรอบถัดไป → บันทึกประวัติ (transaction) schedule ──schedule-alarm {action: ADD, scheduleAlarm} header.serviceKey = X ──▶ service X service X ──schedule-alarm-result {action: ADD, scheduleAlarm + เวลารับ/ส่ง}──▶ schedule ``` - header ของทุกข้อความมี `appKey` และ `serviceKey` · `serviceKey` = **service ปลายทาง** เสมอ - ส่ง `set-schedule` / `schedule-alarm-result` มาที่ schedule ⇒ header `serviceKey: schedule` - schedule ส่ง `schedule-alarm` ⇒ header `serviceKey` = serviceKey ของ service เจ้าของนัดหมาย - ใน payload `schedule.serviceKey` = service ที่จะได้รับ alarm (ปกติคือ serviceKey ของผู้ส่งเอง) - ความละเอียดของเวลา 1 นาที - ใช้ `scheduleRefKey` / `scheduleRefType` อ้างถึงข้อมูลฝั่งตัวเอง และ `alarmData` ฝากข้อมูลอิสระ (object) ที่จะได้คืนมาตอน alarm - ประวัติการส่งเก็บเป็น ScheduleTransaction จับคู่กับผลตอบกลับด้วย `alarmRefKey` --- ## API Reference --- GraphQL endpoint `/graphql` · ทุก API ต้อง login (header `authorization`) และมี permission ตามที่ระบุ ยกเว้น API แบบ `...ByAppKey` ที่ใช้ app credential ของระบบภายนอก · การระบุ `appKey` อื่นนอกจากแอปที่ login ต้องมี permission `systemApp` ### Query เป็น API ที่ใช้สำหรับการ Query ข้อมูลออกมา ไม่มีการแก้ไข Data #### Get Schedules ดึงรายการ schedule ทั้งหมดของแอปที่ login (แบ่งหน้า) · permission: `getSchedules` ```graphql query { getSchedules(input: { filter: { serviceKey: "storage", isActive: true } search: { title: "แจ้งเตือน" } sort: { sortBy: createdAt, sortOrder: DESC } pagination: { limit: 10, page: 1 } }) { schedules { _id scheduleKey serviceKey title isRecurring nextNotificationAt isSent } pagination { page limit totalItems totalPages hasNextPage } } } ``` Input `GetScheduleInput` | key | Type | คำอธิบาย | | --- | --- | --- | | filter | GetScheduleFilterInput | กรองตรงตัว: serviceKey, scheduleKey, scheduleRefKey, scheduleRefType, title, description, isActive, timeZone, isRecurring, notificationCount, isSent, createdBy, updatedBy | | search | GetScheduleSearchInput | ค้นหาบางส่วน: serviceKey, scheduleKey, scheduleRefKey, scheduleRefType, title, description | | sort | GetScheduleSortInput | `sortBy` (ค่าเริ่มต้น `createdAt`), `sortOrder` ASC / DESC | | pagination | CustomPaginateInput | `limit` (ค่าเริ่มต้น 10), `page` (ค่าเริ่มต้น 1) | Response : `SchedulePagination` = `{ schedules: [Schedule], pagination: CustomPaginate }` Schedule | key | Type | คำอธิบาย | | --- | --- | --- | | _id | ID | id ที่ใช้อ้างอิง | | serviceKey | String | service ที่จะได้รับ alarm | | scheduleKey | String | key อ้างอิงของ schedule | | scheduleRefKey | String | key อ้างอิงข้อมูลฝั่ง service เจ้าของ | | scheduleRefType | String | ประเภทข้อมูลฝั่ง service เจ้าของ | | alarmData | JSON | ข้อมูลอิสระที่ฝากไว้ จะส่งคืนตอน alarm | | title | String | ชื่อ schedule | | description | String | รายละเอียด | | isActive | Boolean | เปิดใช้งานอยู่หรือไม่ | | timeZone | String | เขตเวลา เช่น `Asia/Bangkok` (ใช้กับ recurrence แบบ `SPECIFIC`) | | isRecurring | Boolean | วนซ้ำหรือไม่ | | recurrence | ScheduleRecurrence | การตั้งค่าวนซ้ำ (ดูด้านล่าง) | | firstNotificationAt | Date | เวลาแจ้งเตือนครั้งแรก | | nextNotificationAt | Date | เวลาแจ้งเตือนครั้งถัดไป | | notificationCount | Int | จำนวนครั้งที่แจ้งเตือนแล้ว | | isSent | Boolean | ส่งครบแล้วหรือไม่ | | expiryAt | Date | เวลาหมดอายุ | | lastSentAt | Date | เวลาส่งครั้งล่าสุด | | createdBy, updatedBy | String | ผู้สร้าง / ผู้แก้ไข | | createdAt, updatedAt | Date | เวลาสร้าง / แก้ไขล่าสุด | ScheduleRecurrence | key | Type | คำอธิบาย | | --- | --- | --- | | type | ENUM | `INTERVAL` (ทุก ๆ ช่วงเวลา) · `SPECIFIC` (วันที่กำหนด) | | interval | ENUM | `MINUTES`, `HOURS`, `DAYS`, `WEEKS`, `MONTHS` (ใช้กับ `INTERVAL`) | | value | Int | จำนวนหน่วยของ interval เช่น 24 + `HOURS` | | specific.dayOfWeek | Int | 0–6 (อาทิตย์ = 0) ใช้กับ `SPECIFIC` | | specific.dayOfMonth | Int | 1–31 ใช้กับ `SPECIFIC` | --- #### Get Schedule By ID ดึง schedule ตาม `_id` · permission: `getScheduleById` ```graphql query { getScheduleById(scheduleId: "6650f0c2a1b2c3d4e5f60718") { _id scheduleKey title nextNotificationAt recurrence { type interval value } } } ``` Response : `Schedule` --- #### Get Schedule By Key ดึง schedule ตาม `scheduleKey` · permission: `getScheduleByKey` ```graphql query { getScheduleByKey(scheduleKey: "aB3dE5fG7h") { _id title nextNotificationAt isSent } } ``` Response : `Schedule` --- #### Get Schedule By AppKey ดึงรายการ schedule ของแอปที่ระบุ สำหรับระบบภายนอกที่เรียกด้วย app credential (header `X-APP-CLIENT-ID` + `X-APP-CLIENT-SECRET`) ```graphql query { getScheduleByAppKey(appKey: "yourAppKey", input: { pagination: { limit: 10, page: 1 } }) { schedules { _id title nextNotificationAt } pagination { totalItems } } } ``` Response : `SchedulePagination` --- #### Get Schedule Transactions ดึงประวัติการส่ง alarm ของแอปที่ login (แบ่งหน้า) · permission: `getScheduleTransactions` ```graphql query { getScheduleTransactions(input: { filter: { isSuccess: false } sort: { sortBy: scheduleSentAt, sortOrder: DESC } pagination: { limit: 20, page: 1 } }) { scheduleTransaction { _id scheduleKey alarmRefKey scheduleSentAt clientReceivedAt isSuccess } pagination { totalItems } } } ``` Input `GetScheduleTransactionInput`: `filter` (scheduleKey, isSuccess, alarmRefKey, createdBy, updatedBy, schedule) · `search` (scheduleKey, alarmRefKey) · `sort` · `pagination` Response : `ScheduleTransactionPagination` = `{ scheduleTransaction: [ScheduleTransaction], pagination: CustomPaginate }` ScheduleTransaction | key | Type | คำอธิบาย | | --- | --- | --- | | _id | ID | id ที่ใช้อ้างอิง | | scheduleId | ID | id ของ schedule | | scheduleKey | String | key ของ schedule | | alarmRefKey | String | key ของการส่งรอบนี้ ใช้จับคู่กับ `schedule-alarm-result` | | scheduleSentAt | Date | เวลาที่ schedule ส่ง alarm | | clientReceivedAt | Date | เวลาที่ service ปลายทางได้รับ | | clientSentAt | Date | เวลาที่ service ปลายทางตอบกลับ | | scheduleReceivedAt | Date | เวลาที่ schedule ได้รับผลตอบกลับ | | isSuccess | Boolean | ได้รับผลตอบกลับแล้วหรือไม่ | | schedule | Schedule | ข้อมูล schedule ณ เวลาที่ส่ง | | createdAt, updatedAt | Date | | --- #### Get Schedule Transaction By ID ดึงประวัติการส่งตาม `_id` · permission: `getScheduleTransactionById` ```graphql query { getScheduleTransactionById(scheduleTransactionId: "6650f0c2a1b2c3d4e5f60719") { _id alarmRefKey isSuccess scheduleSentAt scheduleReceivedAt } } ``` Response : `ScheduleTransaction` --- #### Get Schedule Transactions By AppKey ดึงประวัติการส่งของแอปที่ระบุ สำหรับระบบภายนอกที่เรียกด้วย app credential ```graphql query { getScheduleTransactionsByAppKey(appKey: "yourAppKey", input: { pagination: { limit: 20, page: 1 } }) { scheduleTransaction { _id alarmRefKey isSuccess } } } ``` Response : `ScheduleTransactionPagination` --- ### Mutation เป็น API ที่ใช้สำหรับการแก้ไขข้อมูล #### Create Schedule สร้าง schedule จากหน้าบ้าน (ผลเหมือนส่ง `set-schedule` action `ADD`) · permission: `createSchedule` ```graphql mutation { createSchedule(input: { serviceKey: "yourServiceKey" scheduleRefType: "alarmTimeConfig" title: "แจ้งเตือนเข้างาน" timeZone: "Asia/Bangkok" isRecurring: true recurrence: { type: INTERVAL, interval: HOURS, value: 24 } firstNotificationAt: "2026-11-01T02:00:00Z" expiryAt: "2027-11-01T02:00:00Z" }) { _id scheduleKey nextNotificationAt } } ``` Input `CreateScheduleInput` | key | Type | คำอธิบาย | | --- | --- | --- | | appKey | String | ไม่ใส่ = แอปที่ login | | serviceKey | String! | service ที่จะได้รับ alarm | | scheduleRefKey | String | key อ้างอิงข้อมูลฝั่ง service เจ้าของ | | scheduleRefType | String | ประเภทข้อมูลฝั่ง service เจ้าของ | | title | String | ชื่อ | | description | String | รายละเอียด | | isActive | Boolean | เปิดใช้งาน | | timeZone | String | เขตเวลา เช่น `Asia/Bangkok` | | isRecurring | Boolean | วนซ้ำหรือไม่ (ค่าเริ่มต้น `false`) | | recurrence | CreateScheduleRecurrenceInput | `type` (ค่าเริ่มต้น `INTERVAL`), `interval`, `value`, `specific { dayOfWeek, dayOfMonth }` | | firstNotificationAt | Date! | เวลาแจ้งเตือนครั้งแรก | | expiryAt | Date | เวลาหมดอายุ | Response : `Schedule` --- #### Update Schedule แก้ไข schedule · permission: `updateSchedule` ```graphql mutation { updateSchedule( scheduleId: "6650f0c2a1b2c3d4e5f60718" input: { title: "แจ้งเตือนเข้างาน (ใหม่)", isRecurring: true, isActive: true } ) { _id title isActive } } ``` Input: `scheduleId: String!`, `appKey: String` (ไม่ใส่ = แอปที่ login), `input: UpdateScheduleInput` (ฟิลด์เดียวกับ CreateScheduleInput ยกเว้น appKey และไม่บังคับทุกฟิลด์) · ควรส่ง `isRecurring` ทุกครั้งที่แก้ schedule แบบวนซ้ำ (ค่าเริ่มต้นของฟิลด์นี้คือ `false`) Response : `Schedule` --- #### Delete Schedule ลบ schedule · permission: `deleteSchedule` ```graphql mutation { deleteSchedule(scheduleId: "6650f0c2a1b2c3d4e5f60718") { _id title } } ``` Input: `scheduleId: ID!`, `appKey: String` Response : `Schedule` (ข้อมูลที่ถูกลบ) --- #### Sent Schedule สั่งส่ง alarm ของ schedule นี้ทันทีโดยไม่รอเวลา (ส่ง `schedule-alarm` ไปยัง service เจ้าของ) ```graphql mutation { sentSchedule(scheduleId: "6650f0c2a1b2c3d4e5f60718") { _id notificationCount lastSentAt } } ``` Input: `scheduleId: ID!`, `appKey: String` Response : `Schedule` --- ## kafka consume Reference ทุก topic ตรวจว่า schedule มีสิทธิ์ในแอปตาม header `appKey` (มี appCertificate ของแอปนั้น) ก่อนทำงาน ### Standard topics topic มาตรฐานของ service ใน core set | topic | คำอธิบาย | | --- | --- | | `init-system` | ตั้งระบบจากศูนย์ (เฉพาะ core set) | | `refresh-data` | ส่งข้อมูลที่ตัวเองถือขึ้นไปใหม่ (เช่น permission) และล้าง cache | | `sync-app-certificate` | รับ appCertificate ของแอปที่ schedule มีสิทธิ์ | | `sync-app-credential` | รับข้อมูลการเข้าใช้ของ user ในแอป (ใช้ตรวจ token) | | `sync-application` | รับข้อมูลแอป | | `sync-user-policy` | รับ UserPolicy สำหรับตรวจ permission | --- ### Set Schedule ลงทะเบียน / ยกเลิกนัดหมายจาก service อื่น · นี่คือช่องทางหลักที่ service อื่นควรใช้ topic: set-schedule header: appKey = แอปของนัดหมาย, serviceKey = schedule #### Add สร้าง schedule ใหม่ (ทุกข้อความ ADD สร้างรายการใหม่เสมอ ⇒ อย่าส่งซ้ำสำหรับนัดเดียวกัน) Action: ADD ```json { "action": "ADD", "schedule": { "appKey": "yourAppKey", "serviceKey": "yourServiceKey", "scheduleRefKey": "665000000000000000000001", "scheduleRefType": "alarmTimeConfig", "alarmData": { "timeRecordId": "abcd", "timeConfigType": "startTime" }, "title": "แจ้งเตือนเข้างาน", "description": "แจ้งเตือนเข้างาน", "timeZone": "Asia/Bangkok", "isRecurring": true, "recurrence": { "type": "INTERVAL", "interval": "HOURS", "value": 24 }, "notificationAt": "2026-11-01T02:00:00Z", "expiryAt": "2027-11-01T02:00:00Z" } } ``` | key | Type | คำอธิบาย | | --- | --- | --- | | appKey | string | แอปของนัดหมาย | | serviceKey | string | service ที่จะได้รับ `schedule-alarm` (ปกติคือตัวผู้ส่งเอง) | | scheduleRefKey | string | key อ้างอิงข้อมูลฝั่งผู้ส่ง (ใช้ตอน REMOVE ด้วย) | | scheduleRefType | string | ประเภทข้อมูลฝั่งผู้ส่ง ใช้แยกว่า alarm นี้เป็นงานแบบไหน | | alarmData | object | ข้อมูลอิสระ จะได้คืนใน `schedule-alarm` | | title, description | string | ชื่อ / รายละเอียด | | timeZone | string | ใช้คำนวณ "วันในสัปดาห์ / วันที่ของเดือน" ของนัดซ้ำแบบ `SPECIFIC` ตามเวลาท้องถิ่น · ไม่ใส่ = เขตเวลาของเครื่อง schedule ⇒ ควรระบุเสมอ | | isRecurring | boolean | วนซ้ำหรือไม่ | | recurrence | object | `{ type, interval, value, specific: { dayOfWeek, dayOfMonth } }` ดู [ScheduleRecurrence](#get-schedules) · ไม่วนซ้ำให้ส่ง `null` | | notificationAt | Date | **เวลาที่ยิงจริง** (ครั้งแรก) เป็นเวลาสากล ISO 8601 เช่น `2026-11-01T02:00:00Z` = 09:00 เวลาไทย — `timeZone` ไม่เปลี่ยนค่านี้ | | expiryAt | Date | เวลาหมดอายุ (ไม่ใส่ = notificationAt + 1 ปี) | #### Remove ยกเลิก schedule ทั้งหมดของแอปที่ตรงกับ `scheduleRefKey` + `scheduleRefType` Action: REMOVE | key | Type | คำอธิบาย | | --- | --- | --- | | schedule.appKey | string | แอปของนัดหมาย | | schedule.scheduleRefKey | string | key อ้างอิงที่ใช้ตอน ADD | | schedule.scheduleRefType | string | ประเภทที่ใช้ตอน ADD | --- ### Schedule Alarm Result service ปลายทางตอบกลับหลังได้รับ `schedule-alarm` เพื่อบันทึกประวัติ · schedule จับคู่ด้วย `appKey` + `alarmRefKey` แล้วตั้ง `isSuccess = true` และ `scheduleReceivedAt` topic: schedule-alarm-result header: appKey, serviceKey = schedule Action: ADD | key | Type | คำอธิบาย | | --- | --- | --- | | scheduleAlarm.id | string | id ของ schedule (ส่งคืนตามที่ได้รับ) | | scheduleAlarm.appKey | string | appKey | | scheduleAlarm.serviceKey | string | serviceKey ของผู้ตอบ | | scheduleAlarm.scheduleRefKey | string | ตามที่ได้รับ | | scheduleAlarm.scheduleRefType | string | ตามที่ได้รับ | | scheduleAlarm.alarmData | object | ตามที่ได้รับ | | scheduleAlarm.alarmRefKey | string | **ต้องส่งคืน** ใช้จับคู่ประวัติ | | scheduleAlarm.clientReceivedAt | Date | เวลาที่ผู้ตอบได้รับ alarm | | scheduleAlarm.clientSentAt | Date | เวลาที่ผู้ตอบส่งผลกลับ | --- ## Kafka Produce Reference ### Schedule Alarm ส่งเมื่อถึงเวลาของ schedule (หรือสั่งด้วย `sentSchedule`) · เป็น topic เดียวที่ทุก service subscribe ได้ ผู้รับต้องกรองเอาเฉพาะของตัวเองด้วย `scheduleAlarm.serviceKey` (หรือ header `serviceKey`) และแยกงานด้วย `scheduleRefType` topic: schedule-alarm header: appKey = แอปของนัดหมาย, serviceKey = service เจ้าของนัดหมาย Action: ADD ```json { "action": "ADD", "scheduleAlarm": { "id": "6650f0c2a1b2c3d4e5f60718", "appKey": "yourAppKey", "serviceKey": "yourServiceKey", "scheduleRefKey": "665000000000000000000001", "scheduleRefType": "alarmTimeConfig", "alarmData": { "timeRecordId": "abcd", "timeConfigType": "startTime" }, "alarmRefKey": "Xy12Ab34Cd" } } ``` | key | Type | คำอธิบาย | | --- | --- | --- | | id | string | id ของ schedule | | appKey | string | appKey | | serviceKey | string | service ที่ต้องทำงาน | | scheduleRefKey | string | ตามที่ลงทะเบียน | | scheduleRefType | string | ตามที่ลงทะเบียน | | alarmData | object | ตามที่ลงทะเบียน | | alarmRefKey | string | key ของการส่งรอบนี้ ให้ส่งคืนใน `schedule-alarm-result` | ผู้รับควร produce `schedule-alarm-result` กลับมาหลังรับงาน เพื่อให้ประวัติการส่งสมบูรณ์ --- ### Set Schedule Result ผลการลงทะเบียนจาก `set-schedule` topic: set-schedule-result Action: SUCCESS | ERROR | key | Type | คำอธิบาย | | --- | --- | --- | | action | string | `SUCCESS` หรือ `ERROR` | | schedule | object | ข้อมูล schedule ที่ส่งมา | | error | object | รายละเอียดข้อผิดพลาด (กรณี ERROR) | --- ### Sync Permission ส่ง permission ของ schedule ให้ access-control เมื่อได้รับ `refresh-data` เพื่อนำไปผูกกับ role (`getSchedules`, `getScheduleById`, `getScheduleByKey`, `createSchedule`, `updateSchedule`, `deleteSchedule`, `sentSchedule`, `getScheduleTransactions`, `getScheduleTransactionById`, `systemApp`) topic: sync-permission Action: ADD --- > อัปเดตจากโค้ด gumon-schedule-service@4bea2c8 · 2026-10-05 --- # Storage Service Service กลางสำหรับจัดการไฟล์ของทุกแอป · serviceKey: `storage` storage ไม่รับตัวไฟล์ผ่านตัวเองตอนอัปโหลด แต่ออก **presigned URL** ให้หน้าบ้านอัปโหลด / ดาวน์โหลดกับ object storage (S3 หรือ S3-compatible) โดยตรง แล้วเก็บข้อมูลไฟล์ (metadata) ไว้อ้างอิงด้วย `fileKey`
- [ภาพรวมการใช้งาน](#ภาพรวมการใชงาน) - [API Reference](#api-reference) - [kafka consume Reference](#kafka-consume-reference) - [kafka produce Reference](#kafka-produce-reference) ---

## ภาพรวมการใช้งาน ### อัปโหลดไฟล์ 1. หน้าบ้านเรียก [Put Signed Upload File](#put-signed-upload-file) (login แล้ว) หรือ [Put Public Signed Upload File](#put-public-signed-upload-file) (ยังไม่ login) พร้อม `fileName`, `contentType`, `path`, `acl` 2. ได้ `signedUrl` (อายุ 1 ชั่วโมง) และ `fileKey` กลับมา 3. หน้าบ้าน `PUT` ตัวไฟล์ไปที่ `signedUrl` โดยตรง ใส่ header `Content-Type` ให้ตรงกับ `contentType` ที่ขอไว้ ```js await fetch(signedUrl, { method: "PUT", headers: { "Content-Type": file.type }, body: file }); ``` 4. เก็บ `fileKey` ไว้ในข้อมูลของ service ตัวเอง (เช่น รูปโปรไฟล์ เอกสารแนบ) ใช้อ้างถึงไฟล์นี้ต่อไป 5. storage ตั้งเวลากับ [Schedule Service](scheduleService.md) ไว้ตอนหมดอายุ signedUrl แล้วตรวจว่าไฟล์ถูกอัปโหลดจริงหรือไม่ · สถานะไฟล์จะเปลี่ยนจาก `UNKNOWN` เป็น `SUCCESS` (พร้อมขนาดไฟล์) หรือ `FAILED` ### File key รูปแบบ `fileKey` = `//<นามสกุลไฟล์>` เช่น `myApp/profile/6650f0c2a1b2c3d4e5f60718.png` - `path` คือหมวดที่ใช้จัดกลุ่มไฟล์ในแอป (ค่าเริ่มต้น `default`) - ไฟล์ทุกไฟล์อยู่ใต้ `appKey` ของแอปตัวเอง ### ดาวน์โหลด / แสดงไฟล์ - `acl: PUBLIC` ใช้ `publicUrl` ได้ตรง ๆ - `acl: PRIVATE` ต้องเรียก [Get Signed Url](#get-signed-url) ด้วย `fileKey` เพื่อขอ URL ชั่วคราว (อายุ 1 ชั่วโมง) ทุกครั้งที่จะแสดงไฟล์ · ต้อง login - `publicUrl` อาจชี้ไปที่ object storage โดยตรง หรือชี้มาที่ `GET /file/` ของ storage service (ส่งไฟล์แบบ stream, `Content-Disposition: inline`) ขึ้นกับการตั้งค่าของระบบ ให้ใช้ URL ตามที่ได้รับ ### ไฟล์ที่ service อื่นสร้างเอง service ที่สร้างไฟล์ลง object storage เอง (เช่น ผลการแปลงวิดีโอ) ลงทะเบียนไฟล์นั้นกับ storage ผ่าน Kafka topic [`sync-file-upload`](#sync-file-upload) เพื่อให้เรียกดูผ่าน API เดียวกันได้ --- ## API Reference --- GraphQL endpoint `/graphql` · API ที่ต้อง login ใช้ header `authorization` และต้องมี permission ตามที่ระบุ · API ที่ระบุว่า "app credential" เรียกได้ทั้งแบบ login หรือแบบไม่ login โดยใช้ client ของแอป (header `X-APP-CLIENT-ID` และสำหรับระบบภายนอกเพิ่ม `X-APP-CLIENT-SECRET`) ### Query เป็น API ที่ใช้สำหรับการ Query ข้อมูลออกมา ไม่มีการแก้ไข Data #### Get Signed Url ขอ URL สำหรับเปิดไฟล์จาก `fileKey` (ได้หลายไฟล์พร้อมกัน) · app credential - ไฟล์ `PUBLIC` คืน `publicUrl` - ไฟล์ `PRIVATE` คืน `signedUrl` อายุ 1 ชั่วโมง และ `expired` · ต้อง login ถ้าไม่ login จะได้ error Forbidden - ทุก `fileKey` ต้องมีอยู่จริงในแอป ไม่อย่างนั้นได้ error Not Found ```graphql query { getSignedUrl(fileKeys: ["myApp/profile/6650f0c2a1b2c3d4e5f60718.png"]) { _id fileKey fileName acl publicUrl signedUrl expired contentType } } ``` Response : `[FileUploadsUrl]` FileUploadsUrl | key | Type | คำอธิบาย | | --- | --- | --- | | _id | ID | id ที่ใช้อ้างอิง | | acl | ENUM | `PUBLIC`, `PRIVATE` | | signedUrl | String | URL ชั่วคราว (อัปโหลด: ใช้ `PUT` ไฟล์ · ดาวน์โหลดไฟล์ PRIVATE: ใช้เปิดไฟล์) | | publicUrl | String | URL ของไฟล์ (ไฟล์ PRIVATE ต้องใช้ signedUrl แทน) | | fileKey | String | key อ้างอิงไฟล์ | | fileName | String | ชื่อไฟล์ | | contentType | String | MIME type | | expired | Date | เวลาหมดอายุของ signedUrl | | fileType | ENUM | `INTERNAL` (อัปโหลดผ่าน storage) · `EXTERNAL` (ลงทะเบียนจากภายนอก) | --- #### Get File Uploads ดึงรายการไฟล์ของแอปที่ login (แบ่งหน้า) · permission: `getFileUploads` ```graphql query { getFileUploads(input: { filter: { acl: PRIVATE, path: "myApp/profile" } search: { fileName: "invoice" } sort: { sortBy: createdAt, sortOrder: DESC } pagination: { limit: 20, page: 1 } }) { fileUploads { _id fileKey fileName contentType acl authId createdAt } pagination { totalItems totalPages hasNextPage } } } ``` Input `GetFileUploadsInput` | key | Type | คำอธิบาย | | --- | --- | --- | | filter | GetFileUploadsFilterInput | `acl`, `signedUrl`, `publicUrl`, `path`, `fileKey`, `fileName`, `contentType`, `authId`, `fileType` | | search | GetFileUploadsSearchInput | ค้นหาบางส่วน: `path`, `fileKey`, `fileName`, `contentType` | | sort | GetFileUploadsSortInput | `sortBy` (ค่าเริ่มต้น `createdAt`), `sortOrder` ASC / DESC | | pagination | CustomPaginateInput | `limit` (ค่าเริ่มต้น 10), `page` (ค่าเริ่มต้น 1) | Response : `FileUploadsPagination` = `{ fileUploads: [FileUploads], pagination: CustomPaginate }` FileUploads | key | Type | คำอธิบาย | | --- | --- | --- | | _id | ID | id ที่ใช้อ้างอิง | | acl | ENUM | `PUBLIC`, `PRIVATE` | | signedUrl | String | URL ที่ใช้อัปโหลดตอนสร้าง | | publicUrl | String | URL ของไฟล์ | | path | String | หมวดของไฟล์ (รวม appKey นำหน้า) | | fileKey | String | key อ้างอิงไฟล์ | | fileName | String | ชื่อไฟล์ | | contentType | String | MIME type | | authId | String | user ที่อัปโหลด | | expired | Date | เวลาหมดอายุของ signedUrl ตอนอัปโหลด | | fileType | ENUM | `INTERNAL`, `EXTERNAL` | | createdBy, updatedBy | String | ผู้สร้าง / ผู้แก้ไข | | createdAt, updatedAt | Date | เวลาสร้าง / แก้ไขล่าสุด | --- #### Get File Upload By ID ดึงข้อมูลไฟล์ตาม `_id` · permission: `getFileUploadById` ```graphql query { getFileUploadById(fileUploadId: "6650f0c2a1b2c3d4e5f60718") { _id fileKey fileName acl publicUrl } } ``` Response : `FileUploads` --- #### Get File Upload By File Key ดึงข้อมูลไฟล์ตาม `fileKey` · permission: `getFileUploadByFileKey` ```graphql query { getFileUploadByFileKey(fileKey: "myApp/profile/6650f0c2a1b2c3d4e5f60718.png") { _id fileName acl publicUrl } } ``` Response : `FileUploads` --- ### Mutation เป็น API ที่ใช้สำหรับการแก้ไขข้อมูล #### Put Signed Upload File ขอ presigned URL สำหรับอัปโหลดไฟล์ (อายุ 1 ชั่วโมง) แล้วบันทึกข้อมูลไฟล์สถานะ `UNKNOWN` และตั้งเวลาตรวจผลการอัปโหลดกับ schedule · ต้อง login · permission: `putSignedUploadFille` ```graphql mutation { putSignedUploadFile(input: { acl: PRIVATE path: "documents" fileName: "invoice-2026-10.pdf" contentType: "application/pdf" }) { _id fileKey signedUrl publicUrl expired } } ``` Input `CreateFileUploadsInput` | key | Type | คำอธิบาย | | --- | --- | --- | | acl | ENUM | `PUBLIC` (ค่าเริ่มต้น) หรือ `PRIVATE` | | path | String | หมวดของไฟล์ (ค่าเริ่มต้น `default`) | | fileName | String! | ชื่อไฟล์พร้อมนามสกุล (นามสกุลใช้ต่อท้าย fileKey) | | contentType | String! | MIME type ของไฟล์ ต้องตรงกับ header `Content-Type` ตอน `PUT` | Response : `FileUploadsUrl` --- #### Put Public Signed Upload File ขอ presigned URL สำหรับอัปโหลดไฟล์โดยไม่ต้อง login (เช่น ฟอร์มสาธารณะ) · app credential · ไฟล์เป็น `acl: PUBLIC` เสมอ และบันทึกผู้อัปโหลดเป็น `SYSTEM` · ชื่อ API สะกด `Singed` ตามโค้ด ```graphql mutation { putPublicSingedUploadFile(input: { path: "public-form" fileName: "photo.jpg" contentType: "image/jpeg" }) { _id fileKey signedUrl publicUrl expired } } ``` Input `CreatePublicFileUploadsInput`: `path` (ค่าเริ่มต้น `default`), `fileName: String!`, `contentType: String!` Response : `FileUploadsUrl` --- #### Delete File Upload ลบข้อมูลไฟล์และลบไฟล์ใน object storage ตาม `fileKey` (ได้หลายไฟล์) · permission: `deleteFileUpload` ```graphql mutation { deleteFileUpload(fileKeys: ["myApp/documents/6650f0c2a1b2c3d4e5f60718.pdf"]) { _id fileKey } } ``` Response : `[FileUploads]` (ข้อมูลที่ถูกลบ) --- ### REST #### Get File GET /file/ ส่งไฟล์แบบ stream ผ่าน storage service (`Content-Disposition: inline` พร้อม `Content-Type` ของไฟล์) · ใช้เป็น `publicUrl` เมื่อระบบตั้งค่าให้เปิดไฟล์ผ่าน storage · ไม่พบไฟล์ได้ HTTP 404 --- ## kafka consume Reference topic ส่วนใหญ่ตรวจว่า storage มีสิทธิ์ในแอปตาม `appKey` (มี appCertificate ของแอปนั้น) ก่อนทำงาน ### Standard topics | topic | คำอธิบาย | | --- | --- | | `init-system` | ตั้งระบบจากศูนย์ (เฉพาะ core set) | | `refresh-data` | ส่งข้อมูลที่ตัวเองถือขึ้นไปใหม่ (เช่น permission) และล้าง cache | | `sync-app-certificate` | รับ appCertificate ของแอปที่ storage มีสิทธิ์ | | `sync-app-credential` | รับข้อมูลการเข้าใช้ของแอป (ใช้ตรวจ token และ app credential) | | `sync-application` | รับข้อมูลแอป | | `sync-user-policy` | รับ UserPolicy สำหรับตรวจ permission | | `sync-service-setting` | รับค่าตั้งค่าเพิ่มเติมของ storage ระดับระบบ (header `serviceKey: storage`) | | `sync-app-service-setting` | รับค่าตั้งค่าเพิ่มเติมของ storage เฉพาะแอป (header `serviceKey: storage`) | --- ### Sync File Upload ลงทะเบียนไฟล์ที่ service อื่นสร้างไว้ใน object storage แล้ว ให้ storage รู้จักและเรียกดูผ่าน API ได้ topic: sync-file-upload header: appKey, serviceKey = storage Action: ADD ```json { "action": "ADD", "fileUpload": { "appKey": "myApp", "acl": "PRIVATE", "publicUrl": null, "path": "myApp/videos", "fileKey": "myApp/videos/6650f0c2a1b2c3d4e5f60718/index.m3u8", "fileName": "index.m3u8", "fileSize": 1024, "fileType": "EXTERNAL", "contentType": "application/vnd.apple.mpegurl", "authId": "665000000000000000000030", "status": "SUCCESS", "createdBy": "665000000000000000000030", "updatedBy": "665000000000000000000030" } } ``` | key | Type | คำอธิบาย | | --- | --- | --- | | appKey | string | แอปเจ้าของไฟล์ | | acl | ENUM | `PUBLIC`, `PRIVATE` | | publicUrl | string \| null | URL ของไฟล์ (ถ้ามี) | | path | string | หมวดของไฟล์ | | fileKey | string | key ของไฟล์ใน object storage | | fileName | string | ชื่อไฟล์ | | fileSize | number \| null | ขนาดไฟล์ (byte) | | fileType | string | รูปแบบการจัดการไฟล์ เช่น `EXTERNAL` | | contentType | string | MIME type | | authId | string | user เจ้าของไฟล์ | | status | ENUM | `UNKNOWN`, `SUCCESS`, `FAILED` (ค่าเริ่มต้น `SUCCESS`) | | createdBy, updatedBy | string | ผู้สร้าง / ผู้แก้ไข | --- ### Schedule Alarm รับ alarm จาก [Schedule Service](scheduleService.md#schedule-alarm) เมื่อ signedUrl ของการอัปโหลดหมดอายุ · รับเฉพาะรายการที่ `scheduleAlarm.serviceKey = storage` แล้วใช้ `scheduleRefKey` (= `_id` ของไฟล์) ตรวจกับ object storage ว่ามีไฟล์จริงหรือไม่ จากนั้นอัปเดตสถานะเป็น `SUCCESS` (พร้อมขนาดไฟล์) หรือ `FAILED` topic: schedule-alarm Action: ADD | key | Type | คำอธิบาย | | --- | --- | --- | | scheduleAlarm.serviceKey | string | `storage` | | scheduleAlarm.scheduleRefKey | string | `_id` ของไฟล์ | | scheduleAlarm.scheduleRefType | string | `fileUploads` | | scheduleAlarm.alarmData | object | `_id`, `description`, `timeStamp`, `expiredTime` | --- ## Kafka Produce Reference ### Set Schedule ลงทะเบียนเวลาตรวจผลการอัปโหลดกับ schedule ทุกครั้งที่ออก presigned URL สำหรับอัปโหลด (ครั้งเดียว ไม่วนซ้ำ ที่เวลาหมดอายุของ signedUrl) topic: set-schedule header: appKey, serviceKey = schedule Action: ADD | key | Type | คำอธิบาย | | --- | --- | --- | | schedule.appKey | string | แอปของไฟล์ | | schedule.serviceKey | string | `storage` (ผู้รับ alarm) | | schedule.scheduleRefKey | string | `_id` ของไฟล์ | | schedule.scheduleRefType | string | `fileUploads` | | schedule.alarmData | object | `_id`, `description`, `timeStamp`, `expiredTime` | | schedule.title / description | string | `file upload` / `storage upload file: ` | | schedule.isRecurring | boolean | `false` | | schedule.notificationAt | Date | เวลาหมดอายุของ signedUrl | --- ### Sync Permission ส่ง permission ของ storage ให้ access-control เมื่อได้รับ `refresh-data` เพื่อนำไปผูกกับ role (`putSignedUploadFille`, `getSignedUrl`, `getFileUploads`, `getFileUploadById`, `getFileUploadByFileKey`, `deleteFileUpload`) topic: sync-permission Action: ADD --- > อัปเดตจากโค้ด gumon-storage-service@3f02969 · 2026-10-05 --- # Label Service > ⚠️ **เลิกใช้แล้ว** — หน้าที่ของ Label Service ถูกรวมเข้า Unit Service · หน้านี้เก็บไว้อ้างอิงเท่านั้น > > ดูของปัจจุบัน (organization, organization type / tag และ role) ที่ [Unit Service](unitService.md) Service สำหรับจัดการข้อมูล label ต่างๆ ซึ้งมีทั้ง 1. Organization 2. Business Unit 3. Position 4. Role 5. Custom
- [API Reference](#api-reference) - [CLI Reference](gumoncli.md) - [kafka consume Reference](#kafka-consume-reference) - [kafka produce Reference](#kafka-produce-reference) ---

## API Reference --- ### Query เป็น API ที่ใช้สำหรับการ Query ข้อมูลออกมา ไม่มีการแก้ไข Data #### Get Label API สำหรับการเรียกดูรายการข้อมูล label API name : getLabels Response : labelSchema[] labelSchema | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _id | string | id ที่ใช้อ้างอิง | | appKey | string | appKey | | name | String | ชื่อ label ที่สร้าง | | description | String | คำอธิบาย | --- #### Get Label By ID API สำหรับการเรียกดูข้อมูล Label โดยอ้างอิงจาก ID API name : getLabelByID Input Fields | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _id | String | ID ของ label | Response : labelSchema labelSchema | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _id | string | id ที่ใช้อ้างอิง | | appKey | string | appKey | | name | String | ชื่อ label ที่สร้าง | | description | String | คำอธิบาย | --- --- ### Mutation เป็น API ที่ใช้สำหรับการแก้ไขข้อมูล #### Create Label API สำหรับการสร้าง Label API name : createLabel Input Fields | key | Type | คำอธิบาย | | ------ | ------ | ------ | | name | String | ชื่อ label ที่สร้าง | | description | String | คำอธิบาย | Response : labelSchema labelSchema | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _id | string | id ที่ใช้อ้างอิง | | appKey | string | appKey | | name | String | ชื่อ label ที่สร้าง | | description | String | คำอธิบาย | --- #### Update Label API สำหรับการแก้ไขข้อมูล Label API name : updateLabel Input Fields | key | Type | คำอธิบาย | | ------ | ------ | ------ | | id | String | id ของ label ที่ต้องหารแก้ไข | | name | String | ชื่อ label ที่สร้าง | | description | String | คำอธิบาย | Response : labelSchema labelSchema | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _id | string | id ที่ใช้อ้างอิง | | appKey | string | appKey | | name | String | ชื่อ label ที่สร้าง | | description | String | คำอธิบาย | --- #### delete Label API สำหรับการลบ Label API name : deleteLabels Input Fields | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _ids | String[] | list ID ของ label ที่ต้องการลบ | Response : DeleteStatus DeleteStatus | key | Type | คำอธิบาย | | ------ | ------ | ------ | | status | ENUM | SUCCESS, ERROR | | _id | string[] | list ของ ID | --- ## kafka consume Reference ### Application consume ข้อมูล application topic: sync-application --- #### Add Application รับข้อมูล Application เมื่อมีการสร้าง Application ใหม่ขึ้นมาในระบบ Action: ADD | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _id | string | id ที่ใช้อ้างอิง | | appKey | string | appKey | | name | string | ชื่อ app | | attribute | object | | --- #### Update Application รับข้อมูล Application เมื่อมีการ update Application Action: UPDATE | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _id | string | id ที่ใช้อ้างอิง | | appKey | string | appKey | | name | string | ชื่อ app | | attribute | object | | --- #### Delete Application รับข้อมูล Application เมื่อมีการ DELETE Application Action: DELETE | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _id | string | id ที่ใช้อ้างอิง | | appKey | string | appKey | --- #### RefreshData เมื่อมีคำสั่งนี้มา ให้ทำการส่งข้อมูลของตัวเอง อัตเดตขึ้น kafka Action: REFRESHDATA | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _id | string | id ที่ใช้อ้างอิง | | appKey | string | appKey | --- ## Kafka Produce Reference ### Label produce ข้อมูล label topic: sync-label --- #### Add Label ส่งข้อมูล label เมื่่อมีการสร้าง label ใหม่ Action: ADD | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _id | string | id ที่ใช้อ้างอิง | | appKey | string | appKey | | name | String | ชื่อ label ที่สร้าง | | description | String | คำอธิบาย | --- #### Update Label ส่งข้อมูล Label เมื่่อมีการแก้ไข Action: UPDATE | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _id | string | id ที่ใช้อ้างอิง | | appKey | string | appKey | | name | String | ชื่อ label ที่สร้าง | | description | String | คำอธิบาย | --- #### Delete Service ส่งข้อมูล Label เมื่่อมีการลบ Label Action: DELETE | key | Type | คำอธิบาย | | ------ | ------ | ------ | | _id | string | id ที่ใช้อ้างอิง | | appKey | string | appKey | ---