API Guide
This is a single Next.js 16 app — there is no separate API server and no OpenAPI spec. The "API surface" is three things: Server-Component data fetching, Server Actions, and Route Handlers.
| Surface | Where | Use for | Cross-origin? |
|---|---|---|---|
| Server Component fetch | app/**/page.tsx (async) | reading data to render a page | No |
Server Action ("use server") | actions/*.ts | mutations from forms/components | No |
| Route Handler | app/api/**/route.ts | webhooks, health, OAuth | Yes |
Route Handlers
Route Handlers live in next-app/app/api/ and follow the App Router route.ts convention. Use them only when something outside the app must reach an HTTP URL.
Health Check
GET /api/healthReturns { status: "ok", timestamp: "..." } from app/api/health/route.ts. Use this to verify the deployed app is reachable and the interactive API playground is pointed at the right base URL.
Current Route Handler Surface
GET|POST /api/auth/[...nextauth] → Auth.js v5 (session, 工牌登入/登出)
GET /api/health → health probe
GET /api/openapi → OpenAPI 描述瑞成 PMS 沒有金流 webhook(Stripe / ECPay billing 功能已從產品移除),也沒有 OAuth callback — 登入採工牌編號 + 密碼的 Credentials provider。
Auth Session (Auth.js v5)
Auth.js v5 uses the JWT strategy — sessions are stateless. The session object is:
type Session = {
user: {
id: string
badge: string // 工牌編號,例如 "R00001"
name: string | null
role: 'admin' | 'operator' | 'staff' // 管理員 / 行政 / 一般
}
expires: string
}Read it server-side with auth() from @/lib/auth. The Credentials provider 以工牌編號 + 密碼 驗證,requires JWT sessions(it cannot create DrizzleAdapter database sessions)。
Server Actions
Server Actions ("use server") run on the server and are invoked directly from React components or forms. They compile to same-origin POST endpoints — they cannot be called cross-origin.
案件 (actions/cases.ts)
createCase(input): Promise<Result> // 4 步精靈建檔
editCase(id, input): Promise<Result>
voidCase(id, reason): Promise<Result> // 軟刪除(作廢),必填原因,可復原
restoreCase(id): Promise<Result>
updateProgress(...): Promise<Result> // 自填式進度(必填備註;覆寫他人僅 admin)
completeNode(caseId, nodeId): Promise<Result>
addPrereq / editPrereq / markPrereq / deletePrereq(...) // 前置資料(NAS 連結)
addSubcase / editSubcase / subStatusChange / deleteSubcase(...) // B2 母子追加案每個 mutation 依角色呼叫對應守衛(見下方 RBAC),驗證輸入,並經 rcLog 寫入稽核日誌。 進度 % 僅技師(admin)可覆寫他人 — 行政(operator)不可填進度(核心責任界線)。
業主 CRM (actions/clients.ts)、待辦 (actions/todos.ts)、設定(accounts.ts / categories.ts / calendars.ts)
業主/窗口 CRUD、自訂分類、業主工作日曆、帳號(停用為軟刪除,不硬刪)。一般人員(staff)對 CRM 唯讀。
稽核與通知 (actions/admin.ts, actions/notifications*.ts)
稽核日誌僅 admin 可讀(可篩選 + 匯出 CSV,永久不可刪不可改);通知為系統內鈴鐺,不寄 Email。
RBAC Guard Pattern
Guards live in @/lib/permissions。每個守衛解析 session 並從資料庫 re-read live role (非 JWT 快照),因此降權立即生效。權限矩陣的唯一真實來源是 lib/rc-permissions.ts(RC_PERM_MATRIX,10 旗標 × 三角色)。
| Guard | Allows |
|---|---|
requireAuth() | 任何已登入使用者 |
requireOperatorOrAdmin() | admin、operator(文書類操作) |
requireAdmin() | admin(樣板/分類管理、稽核日誌、進度覆寫) |
requireFlag(flag) | 具備指定權限旗標的角色(依 RC_PERM_MATRIX) |
import { requireAdmin } from '@/lib/permissions'
export async function adminAction() {
'use server'
const session = await requireAdmin() // re-reads role from DB; redirects non-admins
// ...admin logic
}Server Actions are public POST endpoints — never trust an argument's TypeScript type at runtime. Validate against a shared Zod schema (lib/validations/*) or a runtime allowlist。
Validation Envelope
Item/account actions return a small state object — { error?: string } | null (null = success). Admin actions return { success: true } | { error: string }. Validation schemas are shared from lib/validations/* so the same Zod rules run on the client form and the server action.
Interactive Playground
Test live Route Handlers against your deployed Next.js app: