21 KiB
Phase 1 - Architecture Audit
Executive Summary
ภาพรวมสถาปัตยกรรมของระบบปัจจุบันมีแกนหลักที่ชัดเจนในระดับ app-owned stack คือ Next.js App Router + Auth.js + Drizzle + PostgreSQL + TanStack Query และหลาย feature หลักเดินตาม pattern page -> feature api/service -> route handler -> server data -> db ได้ดี โดยเฉพาะ training-records, courses, announcements, reports, และ permission-management
อย่างไรก็ตาม implementation ปัจจุบันยังมี architectural drift อยู่หลายจุดเมื่อเทียบกับ target architecture ในเอกสารโครงการ โดยประเด็นสำคัญที่สุดคือ runtime ยังพึ่งพาโมเดล employees อย่างกว้างขวาง แม้เอกสารสถาปัตยกรรมจะระบุว่า users ควรเป็น runtime person entity หลักและ employees เป็น legacy migration data เท่านั้น นอกจากนี้ยังพบ production dashboard surface ที่ยังรวม route กลุ่ม demo/template/legacy และมีความไม่สม่ำเสมอของ guard placement และ route contract บางจุด
System Shape Observed
- Framework: Next.js 16 App Router
- Auth boundary:
src/auth.ts,src/lib/auth/session.ts,src/lib/auth/page-guards.ts,src/proxy.ts - Data boundary:
src/app/api/*route handlers + Drizzle schema ในsrc/db/schema.ts - Feature modularization: ส่วนใหญ่ใช้
src/features/<feature>ตามแนวทางที่เอกสารกำหนด - Canonical app-owned domains ที่เห็นชัด:
users,organizations,memberships,courses,training_records,training_matrices,training_policies,announcements,online_lessons,reports,permissions
Strengths
- มีการแยก auth/session, page guard, API handler, และ feature server helpers ค่อนข้างชัด
- route handlers สำคัญส่วนมากไม่เข้าฐานข้อมูลจาก UI โดยตรง แต่ผ่าน service/server data helpers
- RBAC model แบบ
systemRole + membership.role + effective permissionsถูกวางโครงไว้ครบทั้ง session และ permission-resolution - feature หลักจำนวนมากเดินตาม shared stack เดียวกัน เช่น
PageContainer, TanStack Query, shared table/form primitives
Findings
[High] Runtime architecture ยังผูกกับ legacy employees อย่างลึก แม้ target architecture ระบุให้ users เป็น runtime person entity หลัก
Module: Auth, Schema, Training Records, Reports, Overview, Employee Directory, Training Policy
ตำแหน่งที่พบ:
src/db/schema.tsประมาณบรรทัด 81, 281, 438, 541src/auth.tsประมาณบรรทัด 14, 126, 155, 285src/features/employees/server/employee-identity-linking.tssrc/features/training-records/server/training-record-data.tssrc/features/reports/server/report-data.tssrc/features/overview/server/overview-data.tssrc/features/training-policy/server/employee-training-target-data.ts
รายละเอียด:
เอกสาร docs/PROJECT_ARCHITECTURE.md และ AGENTS.md ระบุว่า runtime person data ควรอยู่บน users และ employees เป็น legacy migration data เท่านั้น แต่ implementation ปัจจุบันยังใช้ employees เป็น dependency หลักในหลาย workflow สำคัญ เช่น:
users.employeeIdยังอ้างอิงemployees.id- มีตาราง
user_employee_maps training_records.employeeIdยังอ้างอิงemployees.idemployee_training_targets.employeeIdยังถูกใช้ใน target resolution- session enrichment ใน
src/auth.tsเรียกsyncUserEmployeeLink(...)ตอน login - reports, overview, training matrix compliance, และ training records query หลักยัง join ผ่าน
employees
ผลกระทบ:
- target architecture กับ implementation ปัจจุบันไม่สอดคล้องกัน
- การพัฒนาฟีเจอร์ใหม่มีความเสี่ยงสร้าง dual-source-of-truth ระหว่าง
usersและemployees - การวาง roadmap ย้ายข้อมูลหรือ permission scoping ต่อจากนี้จะซับซ้อนขึ้น
หลักฐานจากโค้ด:
src/auth.tsเรียกsyncUserEmployeeLink(...)ระหว่าง authorize และเก็บemployeeIdลง sessionsrc/db/schema.tsยังเก็บ foreign key หลายจุดไปที่employeessrc/features/reports/server/report-data.tsและsrc/features/overview/server/overview-data.tsใช้employeesเป็นฐานการคำนวณ scope และ dataset
สถานการณ์ตัวอย่าง:
หากทีมพัฒนาฟีเจอร์ใหม่โดยอิงเอกสารอย่างเดียว จะเข้าใจว่า users คือ runtime source of truth แต่เมื่อเชื่อมกับ reports หรือ training records จริง ระบบยังต้องพึ่ง employees อยู่ ทำให้เกิดการ implement ผิดชั้นข้อมูลได้ง่าย
คำแนะนำ:
ระบุสถานะสถาปัตยกรรมปัจจุบันให้ชัดว่าอยู่ในช่วง transition และจัดทำ migration roadmap ระดับสถาปัตยกรรมก่อน coding phase แยกให้ชัดว่า domain ใดใช้ users จริงแล้ว และ domain ใดยังผูกกับ employees
ต้องแก้ก่อน Production หรือไม่: ใช่
[Medium] Dashboard production surface ยังรวม route กลุ่ม demo/template/legacy ไว้ใน app/dashboard
Module: Dashboard routing, legacy/template features
ตำแหน่งที่พบ:
src/app/dashboard/billing/page.tsxsrc/app/dashboard/chat/page.tsxsrc/app/dashboard/forms/page.tsxsrc/app/dashboard/forms/advanced/page.tsxsrc/app/dashboard/forms/basic/page.tsxsrc/app/dashboard/forms/multi-step/page.tsxsrc/app/dashboard/forms/sheet-form/page.tsxsrc/app/dashboard/react-query/page.tsxsrc/app/dashboard/kanban/page.tsxsrc/app/dashboard/product/page.tsxsrc/app/dashboard/workspaces/page.tsxsrc/app/dashboard/elements/icons/page.tsxsrc/app/dashboard/exclusive/page.tsx
รายละเอียด:
เอกสารสถาปัตยกรรมระบุให้บางพื้นที่ถูกมองเป็น legacy/template เท่านั้น แต่ route เหล่านี้ยังอยู่ใต้ dashboard จริง ทำให้ production route tree ปัจจุบันปะปนระหว่าง business modules กับ sample/demo/template modules
ผลกระทบ:
- เพิ่มความคลุมเครือว่าหน้าใดเป็น production-supported feature
- ทำให้การ audit permission, navigation, QA scope, และ release readiness ซับซ้อนขึ้น
- เพิ่ม maintenance overhead เพราะ route เหล่านี้ต้องถูกคำนึงถึงใน middleware, session, layout, และ regression surface
หลักฐานจากโค้ด:
src/app/dashboard/chat/page.tsxrenderChatViewPageตรงsrc/app/dashboard/react-query/page.tsxใช้features/react-query-demosrc/app/dashboard/billing/page.tsxระบุชัดว่าเป็น placeholder หลังถอด Clerk billing ออกแล้ว
สถานการณ์ตัวอย่าง:
เมื่อทีมทำ full-system regression หรือ permission audit จะต้องตัดสินใจเองว่าหน้าเหล่านี้อยู่ในขอบเขต production หรือไม่ เพราะ route ยังอยู่ใน tree เดียวกับโมดูลธุรกิจหลัก
คำแนะนำ:
กำหนด policy ระดับสถาปัตยกรรมให้ชัดว่าหน้า demo/template ต้องย้ายออก, ปิด route, หรือ mark เป็น non-production module อย่างเป็นระบบ
ต้องแก้ก่อน Production หรือไม่: ควรพิจารณา
[Medium] การวาง authorization guard ไม่สม่ำเสมอระหว่าง page routes
Module: Dashboard route access control
ตำแหน่งที่พบ:
src/app/dashboard/training-matrix/page.tsxsrc/app/dashboard/organizers/page.tsxsrc/app/dashboard/overview/page.tsxsrc/app/dashboard/layout.tsxsrc/lib/auth/page-guards.tssrc/proxy.ts
รายละเอียด:
บางหน้าใช้ shared page guards ตามมาตรฐาน (requireEmployeeDashboardAccess, requireHRDDashboardAccess, requirePermissionDashboardAccess) แต่บางหน้าใช้ custom auth/redirect เอง และบางหน้าพึ่ง proxy หรือ parent layout แทน โดยไม่มี local page guard ตรงตัว
ตัวอย่าง:
src/app/dashboard/training-matrix/page.tsxไม่เรียก page guard เลยsrc/app/dashboard/organizers/page.tsxใช้auth()+isHRD(...)+redirect(...)เอง แทน shared page guardsrc/app/dashboard/overview/page.tsxไม่มี guard ตรงตัว โดย guard อยู่ที่src/app/dashboard/overview/layout.tsxsrc/app/dashboard/layout.tsxไม่มี auth check ในตัวเอง และอาศัยsrc/proxy.tsเป็น outer boundary
ผลกระทบ:
- access policy อ่านยากและตรวจสอบยาก
- เพิ่มโอกาสให้ route ใหม่ถูกสร้างโดยลืมวาง guard ในตำแหน่งที่เหมาะสม
- ลดความสม่ำเสมอของ architecture ที่เอกสารพยายามกำหนดไว้
หลักฐานจากโค้ด:
src/app/dashboard/training-matrix/page.tsxparse search params และ render listing ทันทีsrc/app/dashboard/organizers/page.tsxไม่ใช้ helper จากsrc/lib/auth/page-guards.ts
สถานการณ์ตัวอย่าง:
หากมีการ refactor proxy หรือย้าย layout/sub-layout ในอนาคต หน้าเหล่านี้อาจเปลี่ยนพฤติกรรมการป้องกันโดยไม่ได้ตั้งใจ เพราะ guard placement ไม่ได้อยู่รูปแบบเดียวกัน
คำแนะนำ:
กำหนด architecture rule เดียวสำหรับ dashboard pages ว่าต้องใช้ shared page guard ใน route entrypoint ทุกครั้ง หรือบันทึกข้อยกเว้นเป็น explicit convention
ต้องแก้ก่อน Production หรือไม่: ควรพิจารณา
[Medium] Route contract ของ Training Matrix ไม่สมบูรณ์: มี CTA ไปยัง route ที่ไม่มีอยู่จริง
Module: Training Matrix dashboard route
ตำแหน่งที่พบ:
src/app/dashboard/training-matrix/page.tsxประมาณบรรทัด 28- ไม่พบ
src/app/dashboard/training-matrix/new/page.tsx
รายละเอียด:
หน้า training-matrix สร้างปุ่ม Add Rule ที่ลิงก์ไป /dashboard/training-matrix/new แต่จากโครงสร้างไฟล์ปัจจุบันไม่พบ route ปลายทางดังกล่าว
ผลกระทบ:
- route map ของ feature ไม่ครบตามที่ UI สื่อสาร
- เพิ่มสัญญาณว่าระบบยังมี implementation gap ระหว่าง page shell กับ feature workflow จริง
หลักฐานจากโค้ด:
src/app/dashboard/training-matrix/page.tsxมีhref='/dashboard/training-matrix/new'- ตรวจไม่พบไฟล์
src/app/dashboard/training-matrix/new/page.tsx
สถานการณ์ตัวอย่าง:
ผู้ใช้ HRD/แอดมินกด Add Rule แล้วไปสู่ 404 หรือเส้นทางที่ไม่รองรับ ทำให้ workflow หลักของ matrix management ไม่ต่อเนื่อง
คำแนะนำ:
ระบุให้ชัดว่า feature นี้ตั้งใจใช้ modal/sheet แทน route create หรือมี route create จริงแต่ยังขาด implementation จากนั้นปรับ route contract ให้สอดคล้อง
ต้องแก้ก่อน Production หรือไม่: ใช่
[Low] API perimeter protection ใน proxy.ts ใช้ allowlist แบบบางส่วน ทำให้ boundary strategy ไม่สม่ำเสมอ
Module: Middleware / API protection strategy
ตำแหน่งที่พบ:
src/proxy.ts- เปรียบเทียบกับ API modules ใต้
src/app/api
รายละเอียด:
src/proxy.ts ป้องกัน dashboard ทั้งหมด และ API บาง prefix เท่านั้น เช่น /api/products, /api/training-records, /api/reports, /api/announcements แต่ API กลุ่มใหม่บางส่วน เช่น permission-templates, permission-audit-logs, user-permissions, permissions/catalog ไม่ได้อยู่ใน protectedApiPrefixes และพึ่ง handler-level auth เป็นหลัก
ประเด็นนี้ไม่ใช่ auth bypass โดยตรง เพราะ handler หลายตัวมี requireSuperAdminOrganizationAccess() อยู่แล้ว แต่เป็นความไม่สม่ำเสมอของ perimeter architecture
ผลกระทบ:
- อ่าน boundary policy ได้ยาก
- เพิ่มความเสี่ยงที่ API ใหม่ในอนาคตจะถูกลืมเพิ่มเข้า proxy allowlist หรือทำ auth ซ้ำซ้อนแบบไม่เป็นระบบ
หลักฐานจากโค้ด:
src/proxy.tsระบุprotectedApiPrefixesแบบ hard-coded- API permission management อยู่ใต้
src/app/api/permission-templates,src/app/api/user-permissions,src/app/api/permission-audit-logs,src/app/api/permissions
สถานการณ์ตัวอย่าง:
ทีมเพิ่ม API ใหม่ตาม feature module แล้วคิดว่า proxy คุ้มครองทุก API อยู่แล้ว ทั้งที่จริง route บางกลุ่มต้องพึ่ง handler guard เพียงอย่างเดียว
คำแนะนำ:
กำหนด perimeter strategy ให้เป็นแบบเดียว เช่น ปกป้อง API app-owned ทั้งหมดด้วย proxy matcher หรือประกาศชัดเจนว่า proxy มีไว้กัน unauthenticated only และ authorization ทั้งหมดอยู่ที่ handler
ต้องแก้ก่อน Production หรือไม่: ไม่ใช่
Commands Executed and Results
Get-Content -Raw docs/AI_DEVELOPMENT_GUIDE.mdResult: ใช้เป็นเกณฑ์อ้างอิง pattern หลักของโปรเจ็กต์Get-Content -Raw docs/PROJECT_ARCHITECTURE.mdResult: ใช้ยืนยัน target architecture และ legacy areasGet-Content -Raw plans/tms-system-full-audit-prompts.mdResult: ใช้กำหนดรูปแบบรายงานและข้อห้ามระหว่าง auditGet-ChildItem src/features | Select-Object -ExpandProperty NameResult: ใช้ inventory feature modulesrg -n "require(Employee|HRD|IT|Organization|SuperAdmin).*" src/app/dashboard src/app/apiResult: ใช้สำรวจ route/page guard และ API guard ที่ใช้อยู่จริงGet-ChildItem -Recurse -Depth 2 src/app/dashboard | Select-Object FullNameResult: ใช้ตรวจ route surface ทั้งหมดใน dashboardGet-Content -Raw src/auth.tsResult: พบ session enrichment และ employee-link syncGet-Content -Raw src/lib/auth/session.tsResult: ใช้ยืนยัน org access และ permission enforcement helperGet-Content -Raw src/lib/auth/page-guards.tsResult: ใช้เทียบกับ guard placement ในแต่ละ dashboard pageGet-Content -Raw src/proxy.tsResult: ใช้ตรวจ perimeter protection ของ dashboard และ APIrg -n "/dashboard/training-matrix/new" srcResult: พบ CTA route reference เพียงจุดเดียวในtraining-matrix/page.tsxTest-Path src/app/dashboard/training-matrix/new/page.tsxResult:False
Files Reviewed
docs/AI_DEVELOPMENT_GUIDE.mddocs/PROJECT_ARCHITECTURE.mdplans/tms-system-full-audit-prompts.mdsrc/auth.tssrc/proxy.tssrc/lib/auth/session.tssrc/lib/auth/page-guards.tssrc/lib/auth/roles.tssrc/db/schema.tssrc/app/dashboard/layout.tsxsrc/app/dashboard/page.tsxsrc/app/dashboard/overview/page.tsxsrc/app/dashboard/organizers/page.tsxsrc/app/dashboard/training-matrix/page.tsxsrc/app/dashboard/training-policy/page.tsxsrc/app/dashboard/users/page.tsxsrc/app/dashboard/billing/page.tsxsrc/app/dashboard/chat/page.tsxsrc/app/dashboard/react-query/page.tsxsrc/app/dashboard/product/page.tsxsrc/app/api/training-matrix/route.tssrc/app/api/reports/export/route.tssrc/app/api/auth/register/route.tssrc/features/reports/server/report-data.tssrc/features/products/components/product-listing.tsxsrc/features/organizers/components/organizer-listing.tsxsrc/features/chat/components/chat-view-page.tsxsrc/features/react-query-demo/components/pokemon-info.tsxsrc/features/employees/server/employee-identity-linking.ts
Areas Not Verified
- ไม่ได้รันแอปจริงใน browser เพื่อยืนยันพฤติกรรม redirect/404/runtime navigation
- ไม่ได้เชื่อมต่อฐานข้อมูลจริงเพื่อตรวจสอบ data migration state ระหว่าง
usersและemployees - ไม่ได้ตรวจ coverage ของทุก route handler แบบครบทั้งระบบในเฟสนี้
- ยังไม่ได้ประเมินคุณภาพของ UI, performance, security hardening, หรือ production ops detail เพราะอยู่นอกขอบเขตของ Phase 1
Recommended Next Step
Phase ถัดไปควรเป็น Phase 2 - Functional Review โดยเริ่มจาก feature inventory ของโมดูลธุรกิจหลักและแยกขอบเขต production module ออกจาก demo/template module ให้ชัด เพื่อให้ findings เชิง functional และ permission ใน phase ถัดไปไม่ปะปนกัน