Files
teatea-pension/.trellis/tasks/07-01-nextjs-fullstack-crud-rbac-audit/design.md

4.6 KiB

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:

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:

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.