# 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> = | ({ 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.