docs: add Trellis planning and project specs
This commit is contained in:
113
.trellis/tasks/07-01-nextjs-fullstack-crud-rbac-audit/design.md
Normal file
113
.trellis/tasks/07-01-nextjs-fullstack-crud-rbac-audit/design.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Design
|
||||
|
||||
## Architecture
|
||||
|
||||
This task keeps the app in a single Next.js project and adds a small server-side domain layer inside `modules/`:
|
||||
|
||||
- `modules/core/server/`: server-only persistence, session, permissions, and audit helpers.
|
||||
- `modules/auth/`: UI and client interactions for setup, login, logout, and session state.
|
||||
- `modules/elders/`: elder schemas/types, server actions or API client helpers, and UI components.
|
||||
- `modules/settings/`: role/account/audit display components.
|
||||
- `app/api/.../route.ts`: Route Handlers for auth, session, elders, accounts, and audit APIs.
|
||||
|
||||
The persistence boundary should be centralized behind helper functions that read/write JSON files. UI and route handlers must not know file paths directly.
|
||||
|
||||
## Data Storage
|
||||
|
||||
Use a JSON file store under `.data/teatea.json` by default. The file should contain:
|
||||
|
||||
```ts
|
||||
type AppData = {
|
||||
accounts: Account[];
|
||||
sessions: Session[];
|
||||
elders: Elder[];
|
||||
auditLogs: AuditLog[];
|
||||
};
|
||||
```
|
||||
|
||||
The store module owns initialization, read, write, and update operations. Updates should read the current snapshot, apply a synchronous mutation callback, and write the full file back. This is enough for the MVP and gives a clear future replacement boundary for Drizzle/Postgres.
|
||||
|
||||
## API Contracts
|
||||
|
||||
Route Handlers return a consistent response shape:
|
||||
|
||||
```ts
|
||||
type ApiResult<T> =
|
||||
| ({ success: true; reason: string } & T)
|
||||
| { success: false; reason: string };
|
||||
```
|
||||
|
||||
Planned endpoints:
|
||||
|
||||
- `GET /api/auth/bootstrap`: returns whether setup is required.
|
||||
- `POST /api/auth/setup`: creates the first admin account, creates a session, logs setup.
|
||||
- `POST /api/auth/login`: validates credentials, creates a session, logs login.
|
||||
- `POST /api/auth/logout`: deletes current session cookie/session, logs logout.
|
||||
- `GET /api/auth/session`: returns current account and permissions.
|
||||
- `GET /api/elders`: lists elder profiles.
|
||||
- `POST /api/elders`: creates an elder profile.
|
||||
- `PATCH /api/elders/[id]`: updates an elder profile.
|
||||
- `DELETE /api/elders/[id]`: deletes an elder profile.
|
||||
- `GET /api/settings/accounts`: lists accounts for authorized roles.
|
||||
- `GET /api/settings/roles`: lists built-in role definitions.
|
||||
- `GET /api/audit-logs`: lists recent audit events.
|
||||
|
||||
## Authentication
|
||||
|
||||
Sessions use an HTTP-only cookie. Route Handlers read and write cookies with `await cookies()` from `next/headers`, matching current Next.js behavior.
|
||||
|
||||
Passwords are never stored in plain text. For the MVP, use Node built-in crypto with a per-account salt and `scryptSync` or `scrypt` for password hashing. This avoids adding a dependency while keeping the current local milestone meaningfully better than browser-only auth.
|
||||
|
||||
## Permissions
|
||||
|
||||
Define built-in role permissions in one server/shared constants module:
|
||||
|
||||
- `account:read`
|
||||
- `account:manage`
|
||||
- `audit:read`
|
||||
- `elder:read`
|
||||
- `elder:create`
|
||||
- `elder:update`
|
||||
- `elder:delete`
|
||||
|
||||
Route Handlers call a shared `requirePermission(permission)` helper. This helper returns an authenticated context or a structured forbidden/unauthorized result and writes denied audit entries.
|
||||
|
||||
## Audit Logging
|
||||
|
||||
Audit logging is implemented as a server helper that appends immutable records to the store:
|
||||
|
||||
- timestamp
|
||||
- actor account ID/email if known
|
||||
- action
|
||||
- target type
|
||||
- target ID
|
||||
- result: `success` or `denied` or `failure`
|
||||
- reason
|
||||
|
||||
Audit writes are part of the route handler flow. The MVP accepts best-effort logging for logout when a session has already expired.
|
||||
|
||||
## UI Flow
|
||||
|
||||
Protected app pages should use server session state where practical instead of a client-only `AuthGate`. The app layout can fetch session data and redirect unauthenticated users before rendering protected content.
|
||||
|
||||
The elder page becomes a real data view:
|
||||
|
||||
- Server Component loads initial elders.
|
||||
- Client component handles create/edit/delete forms and refreshes after mutations.
|
||||
- Controls are disabled or hidden based on current permissions.
|
||||
|
||||
The settings page becomes a server-loaded administrative view:
|
||||
|
||||
- Role definitions table.
|
||||
- Accounts table.
|
||||
- Recent audit log table.
|
||||
|
||||
## Compatibility
|
||||
|
||||
Static module pages not in scope remain untouched except for auth/layout integration. Existing visual design should be preserved: dense operational screens, restrained cards/tables, and Tailwind/Radix-compatible UI components.
|
||||
|
||||
## Risks and Rollback
|
||||
|
||||
- File persistence is not safe for high-concurrency production writes. This is acceptable for MVP and documented as a migration boundary.
|
||||
- Replacing `AuthGate` with server-side auth can affect all `/app` routes. Rollback point: keep the old component until server session redirect is working.
|
||||
- Password hashing must use Node runtime APIs, so affected Route Handlers should run in the Node runtime if needed.
|
||||
Reference in New Issue
Block a user