Files
teatea-pension/.trellis/spec/backend/local-json-mvp.md

87 lines
3.0 KiB
Markdown

# Local JSON MVP Persistence and API Contracts
## Scenario: Single-Repo MVP Before Database Adoption
### 1. Scope / Trigger
- Trigger: implementing full-stack CRUD, auth, RBAC, or audit logging before Drizzle/Postgres/oRPC/better-auth are installed.
- Applies to this single-package Next.js app when the feature must be real and durable in local/runtime execution, but production database infrastructure is not yet available.
- Store boundary: `modules/core/server/store.ts`.
### 2. Signatures
- `readData(): Promise<AppData>`
- `writeData(data: AppData): Promise<void>`
- `updateData<T>(mutator: (data: AppData) => T): Promise<T>`
- `getCurrentAuthContext(): Promise<AuthContext | null>`
- `requirePermission(permission: Permission, auditContext: DeniedAuditContext): Promise<PermissionCheckSuccess | PermissionCheckFailure>`
- Route Handlers use standard `GET`, `POST`, `PATCH`, and `DELETE` exports and return `Response`.
### 3. Contracts
- Default data file: `.data/teatea.json`.
- Override key: `TEATEA_DATA_DIR` points to the directory containing `teatea.json`.
- `.data/` must stay ignored; it may contain account hashes and session IDs.
- API response shape:
```ts
type ApiResult<T extends Record<string, unknown>> =
| ({ success: true; reason: string } & T)
| { success: false; reason: string };
```
- Session cookie:
- Name: `teatea_session`
- Flags: `httpOnly`, `sameSite: "lax"`, `path: "/"`
- Lifetime: 7 days
- Passwords:
- Never persist plaintext.
- Use Node crypto salt + scrypt hash until a dedicated auth library is introduced.
### 4. Validation & Error Matrix
- Missing/expired session -> `401` with `{ success: false, reason: "未登录或会话已过期" }`.
- Missing permission -> `403` with `{ success: false, reason: "权限不足" }` and a denied audit log.
- Invalid JSON body -> `400` with a Chinese user-facing `reason`.
- Missing record by ID -> `404` with `{ success: false, reason: "<entity>不存在" }`.
- Duplicate account email -> `409` with `{ success: false, reason: "账号已存在" }`.
### 5. Good/Base/Bad Cases
- Good: UI submits to Route Handler, Route Handler validates input, calls `requirePermission`, mutates data through `updateData`, writes audit log, returns `ApiResult`.
- Base: Server Component reads data directly via `readData` after session/permission checks.
- Bad: UI or route handler reads `.data/teatea.json` directly, bypasses `requirePermission`, or stores auth state in localStorage.
### 6. Tests Required
- `pnpm lint`
- `pnpm type-check`
- `pnpm build`
- Manual or automated integration assertions:
- first setup creates admin and cookie session
- protected app route redirects without cookie
- CRUD mutation persists across reload/API list
- denied permission returns 403 and writes an audit log
### 7. Wrong vs Correct
#### Wrong
```ts
window.localStorage.setItem("teatea.session", JSON.stringify(session));
```
#### Correct
```ts
const cookieStore = await cookies();
cookieStore.set({
name: "teatea_session",
value: sessionId,
httpOnly: true,
sameSite: "lax",
path: "/",
});
```