架構 (Architecture)
瑞成 PMS 是一個 單一 Next.js 16 全端 app。沒有獨立的 API server、沒有 FastAPI/Vite 拆分: Server Components 與 Server Actions 透過 Drizzle ORM 直接讀寫 PostgreSQL,Auth.js v5(JWT session)負責認證。
本頁面向開發者,整理系統邊界、請求/變更生命週期、Auth + RBAC、領域模型、燈號預警引擎與資料庫 schema。 細節對齊程式碼;引用的檔案路徑相對於 repo 根目錄。
系統圖 (System Diagram)
┌──────────────────────────────────────────────────────────────┐
│ NEXT.JS APP (next-app/) │
│ │
│ React 19 · App Router · TypeScript · Tailwind v4 · shadcn/ui │
│ Server Components(預設)+ Client Components("use client") │
│ │
│ ┌────────────────────┐ ┌──────────────────────────────┐ │
│ │ Server Actions │ │ Route Handlers (app/api/**) │ │
│ │ actions/*.ts │ │ health · openapi · auth/* │ │
│ │ (真正的 API,RPC) │ │ (唯一真實 HTTP 介面) │ │
│ └─────────┬──────────┘ └───────────────┬──────────────┘ │
│ │ │ │
│ └──────────────┬────────────────┘ │
│ │ Drizzle ORM (postgres-js) │
├───────────────────────────┼────────────────────────────────-──┤
│ ▼ │
│ PostgreSQL │
│ users · accounts · cases · clients · contacts · calendars │
│ case_categories · node_templates · audit_log · notifications│
└───────────────────────────────────────────────────────────────┘
Edge: proxy.ts → auth() 檢查 → redirect /login 或放行設計原則: Next.js 是唯一的全端邊界。沒有 billing、沒有金流 webhook、沒有 FastAPI/Vite——這些在 產品從 SaaS 模板 strip 為內部工具時(migration 0008_strip_saas_surfaces)已移除。唯一的 I/O edge 是 Drizzle;完全沒有外部 API。
請求生命週期 (Request Lifecycle)
瀏覽器 Request
→ proxy.ts (edge middleware:auth guard、未登入 redirect /login)
→ Next.js router 比對 segment
→ layout.tsx (Server Component:session、導覽資料)
→ page.tsx (Server Component:以 Drizzle 取頁面資料,依 live role 收斂可見性)
→ Client Components 在瀏覽器 hydrate變更生命週期 (Mutation Lifecycle)
Client 表單 / 按鈕觸發 Server Action ("use server")
→ auth() 取 session
→ getLiveRole() 從 DB re-read 目前角色 → 守衛 (requireAuth / guard(flag) / requireAdmin)
→ Zod 驗證輸入 (lib/validations/*)
→ Drizzle 寫入
→ rcLog 寫入稽核日誌 (audit_log)
→ revalidatePath() → Next.js 重新渲染受影響 segment
→ 回傳 ActionResult ({ ok: true } / { error } / { fieldErrors })CRUD 採 modal 不轉頁:建立/編輯開 shadcn Dialog,action 回成功(不 redirect),modal 關閉後列表以 revalidatePath + router.refresh() 刷新。詳見 API 參考 與 Server Actions。
Auth + RBAC
工牌登入 (Badge Login)
登入是 工牌編號(R#####)+ 密碼,不是 email——沒有 OAuth、沒有 2FA(刻意排除)。 Auth.js v5 的 Credentials provider 以 bcrypt 比對(lib/password.ts)。
JWT session
Credentials provider 必須用 JWT session(不是 DrizzleAdapter 預設的 DB session)。登入時角色被 snapshot 進 JWT,設為 httpOnly cookie。
認證流程
1. 使用者訪問 /dashboard/*
2. proxy.ts (edge) → auth() → 無 session → redirect /login
3. 提交工牌登入表單
4. Auth.js handler (app/api/auth/[...nextauth]) 驗證 credentials(bcrypt compare)
5. 鑄造 JWT session(role snapshot 進 token);設 httpOnly cookie
6. Edge middleware 放行
7. Server Component 呼叫 auth() → 取 session → 以該使用者身分查 DB
8. RBAC 守衛 (lib/permissions.ts) 從 DB re-read live role三層角色 + 10 旗標權限矩陣
角色定義於 lib/schema/auth.ts(type Role = "admin" | "operator" | "staff")。權限矩陣 RC_PERM_MATRIX 在 lib/rc-permissions.ts——10 個旗標的唯一真實來源(can(role, flag)):
| 旗標 | admin | operator | staff |
|---|---|---|---|
seeAll(可見全部案件) | ✓ | ✓ | ✗(僅被指派) |
createCase(建立案件) | ✓ | ✓ | ✗ |
editPrereq(前置資料增改/標記) | ✓ | ✓ | ✓(僅被指派案件) |
overridePct(覆寫他人進度 %) | ✓ | ✗ | ✗ |
editNodes(節點樣板/標記完成) | ✓ | ✗ | ✗ |
addSubcase(追加案增改刪/狀態) | ✓ | ✓ | ✗ |
manageUsers(帳號管理) | ✓ | ✓ | ✗ |
viewAudit(稽核日誌查閱/匯出) | ✓ | ✗ | ✗ |
manageTemplates(分類/樣板/自訂日曆) | ✓ | ✗ | ✗ |
deleteData(作廢/刪除類) | ✓ | ✓ | ✗ |
核心責任界線: 行政(operator)可「代操作」一切文書類動作,但 不能決定進度數字—— overridePct / editNodes 為 admin(技師)專屬。進度 % 是技師的專業判斷與責任。
getLiveRole() — 從 DB re-read 角色
伺服器端守衛(lib/permissions.ts:requireAuth / requireOperatorOrAdmin() / requireAdmin()) 不信任 JWT 裡的角色,而是用 getLiveRole(userId) 從 DB re-read 目前角色——所以降權立即生效, 不必等 session 過期。UI 隱藏不是安全控制;案件層級可見性(staff 僅見被指派案件)由 lib/rc-visibility.ts 的 canSeeCase 在查詢/動作層收斂。
領域模型 (Domain Model)
六大分類 + 三進度模式
案件分 六大分類 A1–B3(公共工程 / 私人案 / 其他)外加自訂分類(Sn 碼),每類綁定三種進度模式之一:
| 模式 | 鍵 | 進度怎麼算 |
|---|---|---|
| 節點式 | step | Σ 已完成節點的 weight;以 completeNode 推進 |
| 自填式 | self | 技師手填 % + 必填備註(updateProgress) |
| 彈性混合式 | hybrid | 節點為主,可用 addNode 動態增節點 |
母子追加案 (B2)
僅 B2 分類支援母子追加案:母案下掛追加案(子案),由 addSubcase / editSubcase / subStatusChange / deleteSubcase 管理。追加案不計母案進度 %。
前置資料 (Prerequisites)
案件可掛前置資料項,每項可標 critical、附 NAS 連結(link-only,不上傳檔案)。由 addPrereq / editPrereq / markPrereq(標記已到)/ deletePrereq 管理。
燈號預警 schedule engine
系統核心:把案件的 業主工作日 時程消耗轉成「燈號」,再推導每日通知與 dashboard。 純函式、零 db import、可單元測試(lib/rc-schedule-utils.ts、lib/rc-alerts.ts、lib/rc-holidays.ts、 lib/rc-notify-utils.ts、lib/rc-dashboard-utils.ts)。
業主工作日曆(gov / biz / 24-7;國定假日 + 春節排除:rc-holidays)+ 合約起訖
│ scheduleConsumptionPct(case, calendar)
▼
時程消耗 % ── alertLevelOf(timePct, status) ──▶ 燈號
│
├─▶ actions/notifications-scan.ts → rc-notify-utils → notifications (每日 02:00)
└─▶ dashboard pages + 6 charts → rc-dashboard-utilsalertLevelOf(timePct, status)(lib/rc-schedule-utils.ts)的判定:
| 條件 | 燈號 |
|---|---|
status 已作廢 | void(短路) |
status 已結案 | closed(短路) |
status 尚未開始 | idle(短路) |
timePct ≥ 90 | 🔴 red |
timePct ≥ 75 | 🟠 amber(橙) |
timePct ≥ 50 | 🟡 yellow(黃) |
timePct < 50 | 🟢 ok(綠) |
每日 02:00 in-app 掃描(runNotificationScanCore,去重 upsert;runNotificationScan 為 Admin 手動觸發、 requireAdmin() 守衛),只發系統內通知鈴鐺,不寄 Email / LINE。完整 import-graph 詳見 docs/architecture/scheduling-alert-engine-map.md。
稽核日誌 rcLog (immutable)
所有寫入動作都經 rcLog 寫入 audit_log(actor / action / target / 時間,索引於 actorId 與 createdAt)。日誌不可篡改、保存 10 年,僅 Admin 可查閱/匯出 CSV(exportAuditLog / viewAudit 旗標)。
Drizzle Schema 概覽
Schema 在 lib/schema/,匯入點 lib/schema/index.ts;Drizzle client lib/db.ts(lazy-init、postgres-js)。
| 檔案 | 關鍵 tables |
|---|---|
auth.ts | users(含 role enum)· accounts · sessions · verification_tokens · email_verification_tokens · password_reset_tokens |
engineering.ts | cases · case_assignees · case_nodes · case_prereqs · case_subcases · progress_updates · clients · contacts · case_categories · node_templates · node_template_items · work_calendars |
system.ts | audit_log(稽核)· notifications(通知鈴鐺) |
items.ts | 模板遺留的 items(示範用,非瑞成核心領域) |
Migrations: 生成於 next-app/drizzle/migrations/,目前 → 0012(0008 strip 掉 SaaS billing surfaces,0009 引入工程領域)。以 pnpm db:migrate 套用,永不手改生成的 SQL;schema 變更請改 Drizzle table 定義後重新生成。
相關文件
- API 參考 — 兩種介面(HTTP Route Handlers + Server Actions RPC)
- Server Actions Reference — 全 46 個 action 依領域分組 + 權限
- Auth Endpoints — 工牌登入端點與三層角色
docs/architecture/scheduling-alert-engine-map.md— 燈號引擎 import graph(repo 內)