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:readaccount:manageaudit:readelder:readelder:createelder:updateelder: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:
successordeniedorfailure - 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
AuthGatewith server-side auth can affect all/approutes. 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.