docs: add Trellis planning and project specs
This commit is contained in:
86
.trellis/spec/backend/local-json-mvp.md
Normal file
86
.trellis/spec/backend/local-json-mvp.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# 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: "/",
|
||||
});
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user