ข้ามไปที่เนื้อหา

S8 — Official Website — Backend Implementation Blueprint

เอกสารนี้คืออะไร

แผน implement backend สำหรับทีมพัฒนา — ผูก REQ → endpoint → service → ตารางใน schema ใหม่ บน backend เดิมของกรมฯ · ไม่ใช่โค้ดจริง (repo นี้เป็น KB) · อ้างอิง Traceability · Data Dictionary

1. Stack เดิม & จุดที่ต่อเติม

Layer ของเดิม (SRS Tech Stack) สิ่งที่ทำใน Phase 2
Backend PHP / .NET (เดิม) ปรับปรุง CMS + เพิ่ม RESTful Web API layer (REQ-WEB-007)
CMS ระบบจัดการเนื้อหาเดิม WYSIWYG ใหม่ + Code Mode + Workflow + Auto-resize
DB RLPD_WEB (legacy, .226) redesign WEB บน PSDBPRDDB (ดู migration)
Auth (admin) Username/Password + Session (เดิม) เปลี่ยนเป็น Keycloak SSO (ThaiD/AD, realm rlpd) — เลิก local password (ดู §9)
Frontend Responsive (Vertical Template) WCAG 2.2 AA, 5 templates

สถาปัตยกรรมเป้าหมาย: แยกเป็น 3 ส่วน — (1) CMS Backend (admin, server-rendered/SPA), (2) Public Web (อ่านเนื้อหา + cache), (3) Web API (/api/v1/* สำหรับระบบภายนอก) ทั้งหมดอ่าน/เขียน schema WEB เดียวกัน

2. Layering (แนะนำ)

Controller / API endpoint
   └─ Service (business rule + workflow + validation)
        └─ Repository (1 ตัวต่อ aggregate: Content, Banner, Media, EmbedCode, ...)
             └─ WEB.* (MSSQL)
Cross-cutting: Keycloak SSO middleware (verify JWT, realm `rlpd`) · AuthZ (Role/RoleMenu) · AuditLog middleware · Cache · MediaProcessor · Sanitizer

ทุก write ผ่าน Service เพื่อบังคับ audit (WEB.AuditLog) + soft-delete (IsDeleted) + workflow status

3. REQ-by-REQ Implementation

REQ-WEB-001 — WCAG 2.2 AA

  • ส่วนใหญ่ frontend (contrast, keyboard nav, ARIA, alt text) — ไม่ใช่งาน backend
  • Backend ที่ต้องทำ:
  • บังคับ alt ไม่ว่าง ตอนบันทึก Banner/Media (validation) → เก็บ WEB.BannerTranslation.Alt, WEB.MediaAsset.AltText
  • Accessibility admin module: CRUD WEB.AccessibilityIssue (Known-Issues register) + หน้า Accessibility Statement
  • (option) รับผล axe/WAVE scan อัตโนมัติ → insert AccessibilityIssue (Severity=CRITICAL)
  • เกณฑ์ผ่าน: รายงาน AccessibilityIssue WHERE Severity='CRITICAL' AND Status='OPEN' = 0

REQ-WEB-002 — Template & Navbar

  • GET /api/departments — Department Selection (ต้อง cache + ≤2s) → WEB.Department
  • Template module (Super Admin): list/seed 5 templates → WEB.Template (Kind, IsStandard)
  • DepartmentTheme service: department เลือก template+สี → upsert WEB.DepartmentTheme (active 1 แถว/department) + log WEB.DepartmentThemeHistory
  • Navbar CRUD (แก้เองไม่ต้องแก้โค้ด): WEB.Menu + WEB.MenuTranslation (hierarchical via ParentMenuId), drag-order = Sequence
  • หน้า public อ่าน template+menu+theme จาก cache

REQ-WEB-003 — CMS (หัวใจของระบบ)

WYSIWYG + Code Mode: ฝัง editor (TinyMCE/CKEditor) ฝั่ง frontend; backend เก็บ HTML ดิบใน WEB.ContentArticle.HtmlContent (nvarchar(max), รองรับ ≥1MB) — ห้าม strip tag ตอนสลับ Visual/HTML

Content workflow (ContentWorkflowService):

CONTENT_EDITOR  สร้าง/แก้ → WEB.ContentRevision (Status=DRAFT) → submit (PENDING)
CONTENT_APPROVER review → APPROVED/REJECTED (+ReviewNote)
publish → copy revision ล่าสุดที่ APPROVED ลง WEB.ContentArticle + WEB.Content.Status=PUBLISHED
- เก็บประวัติทุกเวอร์ชันใน ContentRevision (VersionNo)

Media upload + Auto-resize (MediaProcessor — งานสำคัญ):

upload → validate ชนิดไฟล์กับ WEB.MediaType (jpg/png/gif/webp/pdf/docx/xlsx)
       → เก็บต้นฉบับ WEB.MediaAsset (Width/Height/Checksum)
       → ถ้าเป็นรูป: gen renditions ตามขนาด Section ของ Template
         (Banner: Template.BannerMain/ServiceWidth/Height ; Article: ขนาด article)
       → insert หลายแถวใน WEB.MediaVariant (VariantLabel='banner-main'|'article'|'thumb')
ใช้ ImageMagick / GD (PHP) หรือ ImageSharp (.NET) · เก็บต้นฉบับไว้เสมอ, แสดงผลใช้ variant

Upload/Embed รวมในหน้าเดียวกับสร้างบทความ: endpoint เดียว POST /cms/content/{id}/media ผูก WEB.ContentImage/WEB.ContentAttachFile

REQ-WEB-004 — Embed Code

  • EmbedCode admin (CRUD) → WEB.EmbedCode (Position HEADER/FOOTER/SECTION, Scope, Provider, IsEnabled)
  • กติกาสิทธิ์: Position='HEADER' ⇒ ต้องเป็น SUPER_ADMIN (ตั้ง RequiresSuperAdmin=1, เช็คใน service)
  • XSS protection: sanitize/allow-list ก่อนบันทึก + render ผ่าน CSP (NFR-S07) — เก็บ snippet ดิบแต่ render อย่างควบคุม
  • ทุกการเปลี่ยนแปลง → insert WEB.EmbedCodeAudit (Action/OldValue/NewValue)
  • เปิด/ปิดรายเว็บ/หน่วยงานผ่าน DepartmentId + IsEnabled
  • Public render: query EmbedCode WHERE IsEnabled=1 AND (DepartmentId=@a OR DepartmentId IS NULL) ตาม Position

REQ-WEB-005 — Backend Performance

  • Article Search (GET /api/articles/search?q=): ใช้ SQL Server Full-Text Catalog บน WEB.Content(Name), WEB.ContentArticle(Title,HtmlContent) (หรือ Elasticsearch) — เป้า ≤1s/100k บทความ; log → WEB.SearchLog (SearchScope='ARTICLE')
  • Caching: cache template/menu/published content (Redis/in-memory), invalidate ตอน publish
  • Login เร็ว ≤2s: SSO ผ่าน Keycloak — optimize callback/JWT verify + โหลด profile+role (cache), หลีกเลี่ยง N+1
  • Perf monitoring: middleware เก็บ response time → log/APM (NFR-P0x)

REQ-WEB-006 — Internal Search (Admin)

  • GET /cms/search?q= ค้น เมนูตั้งค่า (WEB.AdminMenu) + ไฟล์อัปโหลด (WEB.MediaAsset)
  • จัดกลุ่มผลตามประเภท, รองรับ TH/EN + substring + fuzzy (app-side หรือ FTS)
  • กรองตามสิทธิ์: join WEB.RoleMenu ของ role ผู้ใช้ → เห็นเฉพาะที่มีสิทธิ์
  • log → WEB.SearchLog (SearchScope='INTERNAL', UserId)

REQ-WEB-007 — Web API (Data Integration)

  • /api/v1/news, /api/v1/articles, /api/v1/articles/{shortUrl} (read-only, JSON, OpenAPI 3.0)
  • Auth middleware: API Key (hash เทียบ WEB.ApiClient.ApiKeyHash) หรือ OAuth2 ; เช็ค AllowedIps
  • Rate limit: ตาม WEB.ApiClient.RateLimitPerMin (token-bucket; เกิน → 429)
  • Scope: เช็ค WEB.ApiClientScope (news:read/article:read, ราย Department)
  • Versioning: prefix /api/v1, /api/v2
  • Audit: ทุก call → WEB.ApiAccessLog (Endpoint/StatusCode/ResponseTimeMs)

4. API Surface (สรุป)

Endpoint Method Auth ตารางหลัก REQ
/api/departments GET public (cache) Department 002
/cms/templates, /cms/department-theme GET/PUT SUPER/DEPARTMENT_ADMIN Template, DepartmentTheme 002
/cms/menus CRUD DEPARTMENT_ADMIN Menu, MenuTranslation 002
/cms/content, /cms/content/{id}/revisions CRUD EDITOR/APPROVER Content, ContentArticle, ContentRevision 003
/cms/content/{id}/media POST EDITOR MediaAsset, MediaVariant, ContentImage 003
/cms/banners CRUD EDITOR Banner, BannerTranslation, BannerSetting 003
/cms/embed-codes CRUD SUPER(Header)/DEPARTMENT EmbedCode, EmbedCodeAudit 004
/api/articles/search GET public Content, ContentArticle (+FTS), SearchLog 005
/cms/search GET admin AdminMenu, MediaAsset, SearchLog 006
/api/v1/news, /api/v1/articles GET API Key/OAuth Content, ContentArticle, ApiAccessLog 007
/cms/accessibility-issues CRUD SUPER_ADMIN AccessibilityIssue 001

5. Auth (SSO) + Authorization (รวมศูนย์)

Authentication = Keycloak SSO (realm rlpd, เดียวกับ portal) — ThaiD (ประชาชน/ผู้ดูแลเนื้อหา) / AD (เจ้าหน้าที่) - login → redirect Keycloak (OIDC) → JWT → middleware verify → map sub (Keycloak UUID) เข้า WEB.[User].KeycloakUserId - first login ที่ยังไม่มี profile → auto-provision WEB.[User] (ดึง name/email/citizenId จาก token claims) - ไม่มี password/lockout ใน WEB — credential/lockout/password-aging = Keycloak realm policy

Authorization = กำหนดสิทธิ์ที่เดียวทุกระบบ (REQ-WPT-005) — S8 ไม่เก็บ role/grant เอง: - Role catalog: 6 roles ของ S8 ลงทะเบียนที่ PORTAL.ModuleRoles (SystemCode='S8') — เห็นรวมกับทุกระบบที่ portal - การมอบสิทธิ์: ผ่านฟอร์มที่ portal → PORTAL.PermissionRequest/RequestedPermissions → อนุมัติ → เขียน PORTAL.UserRole และ sync เป็น Keycloak client role ของ client s8-cms - Runtime ใน S8: อ่าน role จาก JWT (resource_access['s8-cms'].roles) — ไม่ query DB หาสิทธิ์ user - map role→หน้าจอ (config เฉพาะแอป): WEB.RoleMenu (RoleCode = central role code) × WEB.AdminMenu, PermissionLevel 0/1/2 — ใช้กับ Internal Search filter (REQ-WEB-006) ด้วย - Department scope ของ DEPARTMENT_ADMIN: ส่งมากับ grant (role + department attribute ใน Keycloak group / claim) → S8 filter DepartmentId ใน repository ; WEB.[User].DepartmentId = หน่วยงานต้นสังกัด (default scope) - API_CONSUMER/PUBLIC_USER ไม่ผ่าน Keycloak admin login (API = key/OAuth ; public = ไม่ต้อง login)

6. NFR Implementation

NFR วิธีทำ
S06 lockout / password Keycloak realm policy (login ผิด, lockout, password-aging) — ไม่อยู่ใน WEB
Auth Keycloak SSO (ThaiD/AD, OIDC) — WEB.[User] เก็บแค่ profile + CMS roles
M02 logging AuditLog middleware (admin actions) + ApiAccessLog (API) → centralized log (JSON)
S07 CSP / XSS CSP header + sanitize EmbedCode/HtmlContent
P04 search Full-Text Catalog + index ใน schema.sql

7. ลำดับงาน (Build Order)

  1. Foundation: Language, Department, Keycloak SSO middleware + User profile + AdminMenu/RoleMenu (ลงทะเบียน 6 roles ที่ PORTAL.ModuleRoles)
  2. Template/Theme/Menu → Public render + Department Selection (cache)
  3. Media pipeline (MediaAsset/MediaVariant) → Banner → Content/Article + workflow
  4. Embed Code + audit
  5. Search (article FTS + internal) + AuditLog
  6. Web API (/api/v1) + OpenAPI doc
  7. Accessibility register + WCAG hardening (frontend)

8. Migration: legacy RLPD_WEBWEB

ETL ครั้งเดียว (script/SSIS) ก่อน cutover:

legacy → target logic
Department Department map field, set Code จาก subdomain
Theme_Choice/Theme/Theme_Color Template/DepartmentTheme/ThemeColor จับคู่ template เดิม → 5 template ใหม่
Content (+++,copy1,bk) Content เลือกเฉพาะตัวจริง ทิ้งตารางซ้ำ; set Status='PUBLISHED'
Content_Article (+copy1/2/3) ContentArticle ตัวล่าสุด → article; (ไม่ต้องสร้าง revision ย้อนหลัง)
AttachFile/Content_IMG MediaAsset (+gen MediaVariant) re-process รูปเพื่อสร้าง variant
Menu(+copy1)/Menu_Detail Menu/MenuTranslation
Users(+_bk) [User] provision เข้า Keycloak (ThaiD/AD) → map KeycloakUserId; keep Identity_idCitizenId; ไม่ migrate password
Role/UserRoles PORTAL.ModuleRoles + PORTAL.UserRole + Keycloak ย้ายไปบริหารกลาง (ลงทะเบียน 6 roles ของ S8 ที่ ModuleRoles)
Role_Menu/Tm_Menu RoleMenu/AdminMenu RoleMenu keyed by central role code
inline Embed_Code EmbedCode แตกเป็นรายตำแหน่ง
Survey* ตัดสินใจ archive/migrate ข้อมูลเดิม (ย้ายแบบประเมินไป S9 REQ-WPT-009)

Verify หลัง ETL: นับ Content/Banner/Menu/User ต้นทาง vs ปลายทาง, ตรวจ FK ครบ, สุ่มเทียบเนื้อหา

9. SSO Integration Status

ส่วน สถานะ หมายเหตุ
Keycloak (realm rlpd, sso.rlpd.go.th) ✅ พร้อม (Phase 1) OIDC/OAuth2 — S8 admin เชื่อมเป็น OIDC client เพิ่ม
ThaiD / Digital ID ✅ พร้อม login ประชาชน/ผู้ดูแลเนื้อหาผ่าน ThaiD
AD (เจ้าหน้าที่) ✅ พร้อม Keycloak ↔ LDAP federation
S8 CMS admin ⬜ ต้องทำใน Phase 2 สร้าง Keycloak client ของ S8 + map KeycloakUserId; เลิก local username/password ของ legacy Users

เปลี่ยนจาก SRS S8 (ที่ระบุ Username/Password local) → ใช้ SSO กลาง ตามทิศทางกรมฯ (ทุกระบบ SSO) · CMS roles ยังบริหารใน WEB (Keycloak ทำแค่ authentication ไม่ใช่ authorization ของ CMS)

10. Dependencies / Open items

  • ต้องสรุปกับลูกค้า: redesign สะอาด vs คง schema เดิม (ดู parent decisions)
  • ขนาด section ของ 5 template (ใช้ตั้งค่า auto-resize) — ต้องได้จากทีม UX
  • เลือก search engine: SQL FTS (ในตัว) หรือ Elasticsearch (แยก service)
  • OAuth provider สำหรับ Web API (ถ้าไม่ใช้ API Key) — ตกลงกับหน่วยงานภายนอก
  • การ provision ผู้ดูแลเนื้อหาเดิมเข้า Keycloak (สร้าง user ใน realm rlpd + ผูก role S8)