Files

158 lines
8.6 KiB
Markdown

# Design
## Architecture
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/`: 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 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
The current persistent model is PostgreSQL with Drizzle schema in `modules/core/server/schema.ts` and generated migrations under `drizzle/`.
Important existing tables:
- 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 extends Record<string, unknown>> =
| ({ success: true; reason: string } & T)
| { success: false; reason: string };
```
Existing or expected endpoints:
- `GET /api/auth/bootstrap`: returns whether setup is required.
- `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, 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 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 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. 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
Permissions and role definitions live in the core server/shared type boundary:
- 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 response and writes denied audit entries.
## Admission Transactions
Bed/admission mutations must run inside a Drizzle transaction.
Admit flow:
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
The app should remain an operational workspace: dense, restrained, and action-oriented.
Elder page:
- 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, while APIs still enforce permission checks.
Bed/admission page:
- 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, project UI adapters under `components/ui/*`, and Tailwind-compatible layout.
## Risks and Rollback
- 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.