feat: add system configuration platform
This commit is contained in:
@@ -22,6 +22,8 @@
|
||||
- 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.
|
||||
- Routes or layouts that read local JSON state or session cookies must opt out of static prerendering with `export const dynamic = "force-dynamic"`.
|
||||
- API JSON responses backed by local JSON state or session cookies must include `Cache-Control: no-store`.
|
||||
- API response shape:
|
||||
|
||||
```ts
|
||||
@@ -51,6 +53,7 @@ type ApiResult<T extends Record<string, unknown>> =
|
||||
- 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.
|
||||
- Bad: `/app/*`, `/login`, or `/setup` is prerendered as static content and caches a redirect based on build-time empty data.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
@@ -60,6 +63,8 @@ type ApiResult<T extends Record<string, unknown>> =
|
||||
- Manual or automated integration assertions:
|
||||
- first setup creates admin and cookie session
|
||||
- protected app route redirects without cookie
|
||||
- protected app route renders with a valid cookie after setup and does not return a cached `/setup` redirect
|
||||
- auth/bootstrap/session API responses include `Cache-Control: no-store`
|
||||
- CRUD mutation persists across reload/API list
|
||||
- denied permission returns 403 and writes an audit log
|
||||
|
||||
@@ -84,3 +89,28 @@ cookieStore.set({
|
||||
});
|
||||
```
|
||||
|
||||
#### Wrong
|
||||
|
||||
```ts
|
||||
export default async function AppLayout({ children }: AppLayoutProps) {
|
||||
const bootstrap = await getBootstrapState();
|
||||
if (bootstrap.setupRequired) {
|
||||
redirect("/setup");
|
||||
}
|
||||
return children;
|
||||
}
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```ts
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AppLayout({ children }: AppLayoutProps) {
|
||||
const bootstrap = await getBootstrapState();
|
||||
if (bootstrap.setupRequired) {
|
||||
redirect("/setup");
|
||||
}
|
||||
return children;
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user