docs: sync drizzle task contracts
This commit is contained in:
@@ -2,112 +2,156 @@
|
||||
|
||||
## Architecture
|
||||
|
||||
This task keeps the app in a single Next.js project and adds a small server-side domain layer inside `modules/`:
|
||||
This task keeps the app in a single Next.js project and continues the Drizzle/PostgreSQL-backed server 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.
|
||||
- `modules/core/server/`: Drizzle connection, schema, session, permissions, audit helpers, and compatibility read models.
|
||||
- `modules/auth/`: UI and client interactions for setup, register, login, logout, and session state.
|
||||
- `modules/elders/`: elder schemas/types, validation, and CRUD UI components.
|
||||
- `modules/settings/`: role/account/organization/audit/status display and management components.
|
||||
- `modules/facilities` or existing page-local modules: room, bed, occupancy, admission, transfer, and discharge UI as the feature grows.
|
||||
- `app/api/.../route.ts`: Route Handlers for auth, session, elders, facilities, admissions, accounts, roles, organizations, status, 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.
|
||||
The persistence boundary is now Drizzle/PostgreSQL, not JSON files. UI and route handlers should use domain helpers or direct Drizzle queries through `getDatabase()` in server-only modules. New mutation paths must not use `writeData()` or `updateData()`.
|
||||
|
||||
## Data Storage
|
||||
|
||||
Use a JSON file store under `.data/teatea.json` by default. The file should contain:
|
||||
The current persistent model is PostgreSQL with Drizzle schema in `modules/core/server/schema.ts` and generated migrations under `drizzle/`.
|
||||
|
||||
```ts
|
||||
type AppData = {
|
||||
accounts: Account[];
|
||||
sessions: Session[];
|
||||
elders: Elder[];
|
||||
auditLogs: AuditLog[];
|
||||
};
|
||||
```
|
||||
Important existing tables:
|
||||
|
||||
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.
|
||||
- Identity and tenancy: `accounts`, `sessions`, `organizations`, `memberships`, `join_requests`, `organization_invitations`.
|
||||
- Authorization: `roles`, `permissions`, `role_permissions`.
|
||||
- Operations: `elders`, `rooms`, `beds`, `admissions`.
|
||||
- Governance and status: `audit_logs`, `system_incidents`, `system_settings`.
|
||||
|
||||
`modules/core/server/store.ts` should be treated as a compatibility read model only:
|
||||
|
||||
- `readData()` may aggregate Drizzle rows into existing UI-friendly types while legacy pages are migrated.
|
||||
- `writeData()` and `updateData()` intentionally throw after the PostgreSQL migration.
|
||||
- New feature work should prefer focused query helpers so the compatibility model does not become the long-term domain layer.
|
||||
|
||||
## API Contracts
|
||||
|
||||
Route Handlers return a consistent response shape:
|
||||
|
||||
```ts
|
||||
type ApiResult<T> =
|
||||
type ApiResult<T extends Record<string, unknown>> =
|
||||
| ({ success: true; reason: string } & T)
|
||||
| { success: false; reason: string };
|
||||
```
|
||||
|
||||
Planned endpoints:
|
||||
Existing or expected 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/setup`: creates the first platform admin, first organization, session, roles/membership, and audit log.
|
||||
- `POST /api/auth/register`: creates a user registration or invitation-based account flow where enabled.
|
||||
- `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.
|
||||
- `GET /api/auth/session`: returns current account, organization, membership, and permissions.
|
||||
- `GET /api/elders`: lists elder profiles for the active organization.
|
||||
- `POST /api/elders`: creates an elder profile and optional active admission.
|
||||
- `PATCH /api/elders/[id]`: updates an elder profile.
|
||||
- `DELETE /api/elders/[id]`: deletes an elder profile.
|
||||
- `GET /api/facilities/rooms`: lists rooms for the active organization.
|
||||
- `POST /api/facilities/rooms`: creates room records when facility management UI exposes this.
|
||||
- `GET /api/facilities/beds`: lists beds and current occupancy.
|
||||
- `POST /api/facilities/beds`: creates bed records when facility management UI exposes this.
|
||||
- `GET /api/admissions`: lists admission and transfer history.
|
||||
- `POST /api/admissions`: admits or transfers an elder into an available bed.
|
||||
- Planned: `PATCH /api/admissions/[id]` or equivalent mutation endpoint for transfer/discharge if POST cannot express the workflow cleanly.
|
||||
- `GET /api/settings/accounts`: lists accounts for authorized roles.
|
||||
- `GET /api/settings/roles`: lists built-in role definitions.
|
||||
- `GET /api/settings/roles`: lists built-in and organization roles.
|
||||
- `GET /api/settings/permissions`: lists permission coverage.
|
||||
- `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.
|
||||
Sessions use an HTTP-only cookie named `teatea_session`. 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.
|
||||
Passwords are never stored in plain text. The current implementation uses Node built-in crypto with per-account salt and `scryptSync`, which is acceptable for this local MVP until a dedicated auth library is introduced in a separate task.
|
||||
|
||||
Protected app routes should use server session state where practical instead of client-only localStorage guards. Server-rendered pages that depend on session cookies must opt out of static prerendering with `export const dynamic = "force-dynamic"` where needed.
|
||||
|
||||
## Permissions
|
||||
|
||||
Define built-in role permissions in one server/shared constants module:
|
||||
Permissions and role definitions live in the core server/shared type boundary:
|
||||
|
||||
- `account:read`
|
||||
- `account:manage`
|
||||
- `audit:read`
|
||||
- `elder:read`
|
||||
- `elder:create`
|
||||
- `elder:update`
|
||||
- `elder:delete`
|
||||
- Platform: `platform:manage`, `organization:read`, `organization:manage`.
|
||||
- Account/role/security: `account:read`, `account:manage`, `role:read`, `role:manage`, `permission:read`, `audit:read`.
|
||||
- Operations: `facility:read`, `facility:manage`, `admission:read`, `admission:manage`, `elder:read`, `elder:create`, `elder:update`, `elder:delete`.
|
||||
- Status: `incident:read`, `incident:manage`.
|
||||
|
||||
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.
|
||||
Route Handlers call a shared `requirePermission(permission)` helper. This helper returns an authenticated context or a structured forbidden/unauthorized response and writes denied audit entries.
|
||||
|
||||
## Audit Logging
|
||||
## Admission Transactions
|
||||
|
||||
Audit logging is implemented as a server helper that appends immutable records to the store:
|
||||
Bed/admission mutations must run inside a Drizzle transaction.
|
||||
|
||||
- timestamp
|
||||
- actor account ID/email if known
|
||||
- action
|
||||
- target type
|
||||
- target ID
|
||||
- result: `success` or `denied` or `failure`
|
||||
- reason
|
||||
Admit flow:
|
||||
|
||||
Audit writes are part of the route handler flow. The MVP accepts best-effort logging for logout when a session has already expired.
|
||||
1. Validate active organization and `admission:manage`.
|
||||
2. Validate elder belongs to the active organization.
|
||||
3. Validate target bed belongs to the active organization and has status `available`.
|
||||
4. Close or transfer any active admission for that elder if the operation is a transfer.
|
||||
5. Insert a new `admissions` row with status `active`.
|
||||
6. Set target bed status to `occupied`.
|
||||
7. Set elder status to `active`.
|
||||
8. Record an audit log.
|
||||
|
||||
Transfer flow:
|
||||
|
||||
1. Find active admission for the elder.
|
||||
2. Set previous admission status to `transferred` and `dischargedAt` to now.
|
||||
3. Set previous bed status to `available`.
|
||||
4. Insert the new active admission and occupy the target bed.
|
||||
5. Record an audit log.
|
||||
|
||||
Discharge flow:
|
||||
|
||||
1. Find active admission.
|
||||
2. Set admission status to `discharged` and `dischargedAt` to now.
|
||||
3. Set bed status to `available`.
|
||||
4. Set elder status to `discharged` or another explicitly selected status.
|
||||
5. Record an audit log.
|
||||
|
||||
All conflict checks must return structured API failures rather than partially mutating state.
|
||||
|
||||
## 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 app should remain an operational workspace: dense, restrained, and action-oriented.
|
||||
|
||||
The elder page becomes a real data view:
|
||||
Elder page:
|
||||
|
||||
- Server Component loads initial elders.
|
||||
- Server Component loads initial elders and available beds.
|
||||
- Client component handles create/edit/delete forms and refreshes after mutations.
|
||||
- Controls are disabled or hidden based on current permissions.
|
||||
- Controls are disabled or hidden based on current permissions, while APIs still enforce permission checks.
|
||||
|
||||
The settings page becomes a server-loaded administrative view:
|
||||
Bed/admission page:
|
||||
|
||||
- Role definitions table.
|
||||
- Accounts table.
|
||||
- Recent audit log table.
|
||||
- Replace raw API placeholders with page-local controls.
|
||||
- Use tabs or segmented navigation for overview, bed status, admissions/history, and management actions.
|
||||
- Place admit/transfer/discharge actions inside the bed/admission workspace, not as a dead global top-bar button.
|
||||
- Show occupancy metrics, active admissions, room/bed tables, and history from Drizzle-backed data.
|
||||
|
||||
Dashboard:
|
||||
|
||||
- Replace hard-coded counters for implemented domains with Drizzle-backed data.
|
||||
- Use a standard chart library for selected charts such as occupancy distribution or admission activity.
|
||||
- Keep chart usage modest; tables and status lists remain the primary record surfaces.
|
||||
|
||||
Screenshot feedback:
|
||||
|
||||
- Move page-level secondary navigation into tabs where appropriate.
|
||||
- Remove oversized intro/hero blocks from routine operational pages.
|
||||
- Remove redundant header action controls that are not wired to the current page workflow.
|
||||
|
||||
## 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.
|
||||
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, project UI adapters under `components/ui/*`, and Tailwind-compatible layout.
|
||||
|
||||
## 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.
|
||||
- Drizzle schema and migrations are now the persistence source of truth; mismatches between schema and migrations can block deployment. Rollback point: keep migration changes separate from UI-only work.
|
||||
- Admission mutations touch multiple tables. Rollback point: keep transaction helpers isolated and temporarily render read-only admission data if mutation UI is unstable.
|
||||
- Replacing global header actions can affect user navigation habits. Rollback point: remove only the dead top-bar "入住" button while keeping the sidebar and pages stable.
|
||||
- Adding a chart library increases client bundle size. Rollback point: limit chart usage to one focused client component and keep tables as the fallback data surface.
|
||||
|
||||
Reference in New Issue
Block a user