623 lines
44 KiB
Markdown
623 lines
44 KiB
Markdown
# Full Integration Refactor Plan
|
||
|
||
## Purpose
|
||
|
||
This document is the active cross-application backlog for integrating `frontend/`, `backend/`, and the PostgreSQL database.
|
||
|
||
It must track only unfinished integration work and hard implementation rules. Completed migration history belongs in the feature-specific docs under `backend/docs/`, `frontend/docs/`, and supporting audit docs.
|
||
|
||
When an item is completed, remove it from this plan and update the owning feature documentation instead of adding a completed checklist entry here.
|
||
|
||
## Current Application Shape
|
||
|
||
- `frontend/` is the only product frontend.
|
||
- `backend/` is the source of truth for auth, tenant ownership, roles, permissions, users, staff profiles, campuses, persisted workflows, content catalogs, email, files, and database access.
|
||
- `ref-frontend/` is a temporary generated reference. It is not runtime code and must be deleted after all needed endpoint-contract reference value is exhausted.
|
||
|
||
## Non-Negotiable Rules
|
||
|
||
- No frontend persisted workflow may use mock, sample, seed, or fallback records.
|
||
- No frontend runtime code may import backend seed files.
|
||
- Frontend runtime constants may contain only UI config, route/module metadata, query keys, timing values, style tokens, static UI labels, and intentionally static product copy.
|
||
- Backend seeds are for database seeding only.
|
||
- Frontend seed data is allowed only in tests or `frontend/src/test-seeds/`.
|
||
- Backend owns tenant scoping and permissions. Frontend route hiding is UX only.
|
||
- All frontend backend calls go through typed modules in `frontend/src/shared/api/`.
|
||
- New frontend workflows must follow `View -> Business Logic -> API/Data Access -> Backend`.
|
||
- View components must not import `frontend/src/shared/api/`.
|
||
- Business logic must not import view components.
|
||
- API modules must not import business or view code.
|
||
- All imports use the `@` alias.
|
||
- No `any`, unsafe casts, disabled TypeScript rules, disabled ESLint rules, compatibility bypasses, silent failures, or legacy re-export surfaces.
|
||
- Frontend auth must use backend-owned HttpOnly cookies only. Access and refresh tokens must never be stored in frontend browser storage.
|
||
- Secrets live only in backend `.env` or deployment environment variables. Frontend env values must be public browser-safe values only.
|
||
- New backend modules must include migration/model/service/route, tenant and role enforcement, docs, and focused verification.
|
||
- Every changed workflow must update the relevant docs before it is considered complete.
|
||
|
||
## Current Verification Baseline
|
||
|
||
Frontend current baseline:
|
||
|
||
- `npm run typecheck` passes.
|
||
- `npm run lint` passes.
|
||
- `npm run test` passes with 51 files and 198 tests.
|
||
- `npm run build` passes.
|
||
- `npm run test:e2e` passes with 4 backend-free Playwright smoke tests.
|
||
- `npm run test:e2e:content` exists for backend-seeded content catalog integration tests. It requires backend migrations, backend seeders, and a running backend server.
|
||
|
||
Backend current baseline:
|
||
|
||
- Runtime code is 100% TypeScript + native ESM; the JS->TS / CJS->ESM migration is complete.
|
||
- `npm run typecheck` (`tsc --noEmit`) passes.
|
||
- `npm run lint` (`eslint .`) passes with no broad ignores.
|
||
- `npm test` (`node --test` via `tsx`) passes: 2 files, 15 tests (error-handler + import-boundaries).
|
||
- `npm run verify` (typecheck + lint + test) is the combined gate and is green.
|
||
- `npm run build` (`tsc` + `tsc-alias -f` + email-template asset copy) produces a runnable `dist/`.
|
||
- Migrations/seeders run via Umzug (`npm run db:migrate`, `npm run db:seed`); a run against the configured local database is still pending (Workstream 1).
|
||
|
||
## Active Workstreams
|
||
|
||
The workstreams are numbered in dependency/execution order and grouped into five phases. Earlier phases unblock later ones; within a phase, items may run in parallel unless a dependency is stated. Renumbered from the previous flat backlog: the former Role Model Decision, Product Onboarding Contract, and Permission-Based Frontend Authorization workstreams are now merged into Workstream 3 (RBAC); the former standalone seed-verification step is merged into Workstream 4.
|
||
|
||
**Phase 1 — Foundation (everything else depends on a backend that boots, migrates, and serves routes).**
|
||
|
||
### 1. Backend Migration And Runtime Boot
|
||
|
||
Status: open.
|
||
|
||
Problem:
|
||
|
||
Backend product modules exist, but the configured local database migration/seed run has not been verified as a passing gate in this plan.
|
||
|
||
Required work:
|
||
|
||
1. Run `npm run db:migrate` against the configured local database.
|
||
2. Run `npm run db:seed` (current seed set; the final RBAC seed content is delivered in Workstream 4, which re-verifies this step).
|
||
3. Start backend with the documented env file.
|
||
4. Verify public content catalog routes, auth routes, and product module routes respond.
|
||
5. Record exact failing migration/seed/runtime errors if any.
|
||
|
||
Acceptance criteria:
|
||
|
||
- `npm run db:migrate` passes.
|
||
- `npm run db:seed` passes.
|
||
- Backend starts without generated default secrets.
|
||
- `GET /api/public/content-catalog/:contentType` works for the required seeded content catalog types.
|
||
- Any remaining backend runtime blocker is captured as a specific follow-up with file/error references.
|
||
|
||
**Phase 2 — Identity, Tenancy & Access Core (the security spine: tenant isolation, the role/permission model, the seeded fixtures, and the finalized protected API surface). Workstreams 5–7 finalize what is public, what exists, and what is permission-gated, so they belong with the access model rather than after it.**
|
||
|
||
### 2. Tenant Boundary Audit And Tests
|
||
|
||
Status: open and high priority. Co-designed with Workstream 3: the campus/organization scoping here and the `campusId`-on-`users` model in §3.1 are the same scoping layer.
|
||
|
||
Problem:
|
||
|
||
Generated backend code has partial tenant scoping. Multi-tenant correctness cannot rely on frontend filtering.
|
||
|
||
Required work:
|
||
|
||
1. Audit all tenant-owned generated and product routes for organization scoping.
|
||
2. ~~Resolve inconsistent `organizationsId` versus `organizationId` usage~~ — done: unified on `organizationId` for all models (renamed the `users.organizationsId` column via migration `20260609000000-rename-users-organizationsid-to-organizationid`, updated db/api scoping, the auth DTO, and the frontend `CurrentUser` type). Verify the tenant-scoping `where` now correctly targets each model's `organizationId`.
|
||
3. Ensure create/update/delete paths cannot accept another tenant's organization or campus.
|
||
4. Ensure list/count/autocomplete endpoints are tenant-scoped.
|
||
5. Add backend tests proving cross-tenant records are not visible or mutable.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Tenant isolation tests cover users, campuses, staff, students, attendance, messages, documents, and product module tables.
|
||
- A non-global user cannot read or mutate another tenant's data.
|
||
- Campus-scoped users cannot mutate another campus unless their backend permission explicitly allows it.
|
||
|
||
### 3. RBAC: Role Hierarchy, Scoped Provisioning, And Guards
|
||
|
||
Status: open and high priority. Umbrella workstream for the platform's user/role model. **Merged here:** the former Role Model Decision (role-model choice), the former Product Onboarding Contract (provisioning, §3.4), and the former Permission-Based Frontend Authorization (frontend guards, §3.6). Consumes the existing permission engine in `middlewares/check-permissions.ts`.
|
||
|
||
Decisions recorded (owner-approved):
|
||
|
||
- Role model: **first-class persisted roles** in the `roles` table, each carrying a `scope`, with preset permission sets and code-enforced relational constraints. The `GENERATED_ROLE_TO_PRODUCT_ROLE` / `STAFF_TYPE_TO_PRODUCT_ROLE` mappings and the 5-value `productRole` are retired.
|
||
- Frontend: **permission-based guards plus an authentication guard**; forbidden direct-URL access redirects to the 404 page; menu items the user cannot access are hidden.
|
||
|
||
#### 3.0 Current state (analysis, verified)
|
||
|
||
- Roles today (`backend/src/db/seeders/20200430130760-user-roles.ts`, `backend/src/shared/constants/roles.ts`): 7 generated roles (`Super Administrator`, `Administrator`, `Platform Owner`, `Tenant Director`, `Campus Manager`, `Academic Coordinator`, `Finance Officer`) + `Public` + a `User` default-role name referenced in code. `globalAccess: true` is set only on `Super Administrator` and `Administrator`. These map down to 5 `productRole` values (`teacher | para | office | director | superintendent`).
|
||
- Authorization engine: `middlewares/check-permissions.ts` grants a request when (1) `currentUser.id === req.params.id || req.body?.id` (self-access), (2) a `custom_permissions` row matches, or (3) the effective role (assigned `app_role`, else cached `Public`) has the `${METHOD}_${ENTITY}` permission. CRUD routers wire `checkCrudPermissions(entity)`. This engine is sound and is kept.
|
||
- Permission catalog: CRUD × 27 entities + `READ_API_DOCS` + `CREATE_SEARCH`. **Product modules are not represented** (no permission exists for the FRAME, walkthrough, quizzes, director-dashboard, attendance-fill, ESA/vocational/community pages, classrooms, etc.).
|
||
- Multi-tenancy: `organizationId` on most models (unified, per Workstream 2). Campus link for a user is only indirect, via the `staff` row's `campusId`; `staff_type` is `teacher | admin | support`. There is **no `campusId` on `users`** and no first-class concept of director/owner/student/guardian.
|
||
- Seeders: **no organization row is seeded at all**, and the seeded campuses (`shared/constants/campuses.ts`, 6 rows) have **no `organizationId`**. `20200430130760` assigns roles by raw SQL: `super_admin@flatlogic.com`→SuperAdmin, `admin@flatlogic.com`→Administrator, `client@hello.com`→PlatformOwner, `john@doe.com`→TenantDirector. **This contradicts the product spec**, which states `admin@flatlogic.com` (`SEED_ADMIN_EMAIL`) is the super admin. No staff profiles are seeded.
|
||
- Frontend: `ModuleRouteGuard` + sidebar filter gate purely on the 5-value `productRole` (`shared/constants/appData.ts` `MODULES[].roles`, `shared/constants/moduleRoutes.ts`). `CurrentUser.permissions` exists but is unused. There is **no authentication guard** (an unauthenticated user reaching the shell is not redirected to `/login`), and `canUserRoleAccessModuleRoute` returns `true` for unknown paths (forbidden routes fall through to the catch-all rather than failing at the guard). Only `/login` and `*` (404) are public; there is no signup route.
|
||
|
||
#### 3.1 Target role hierarchy and scope
|
||
|
||
Eleven roles across five scopes (guest is the unauthenticated case, not a stored user):
|
||
|
||
1. `super_admin` — scope `system`. Unlimited across the whole platform.
|
||
2. `system_admin` — scope `system`. Unlimited platform-wide **except** creating/deleting `super_admin` or other `system_admin`.
|
||
3. `owner` — scope `organization`. Unlimited inside its own company.
|
||
4. `superintendent` — scope `organization`. Unlimited inside the company **except** creating/deleting `owner` or other `superintendent`, and **except** deleting the company profile.
|
||
5. `director` — scope `campus`. Unlimited inside its assigned campus; creates campus users and classrooms; grants extra per-user permissions.
|
||
6. `office_manager` — scope `campus`. Reads all campus pages **except** the director dashboard; takes quizzes; leaves read receipts; may fill the attendance page.
|
||
7. `teacher` — scope `campus`. Reads all campus pages except the director dashboard.
|
||
8. `support_staff` — scope `campus`. Reads all campus pages except the director dashboard. (Replaces the current `para` value.)
|
||
9. `student` — scope `external`. Reads only `/community-partnerships`, `/vocational-opportunities`, `/esa-funding`.
|
||
10. `guardian` — scope `external`. Same external pages as `student`.
|
||
11. `guest` — unauthenticated. Public routes only (`/login`, `/signup`, error page); any other URL redirects to 404.
|
||
|
||
Required work:
|
||
|
||
1. Add a `scope` column to the `roles` model/migration: enum `system | organization | campus | external | guest`. Keep `globalAccess` for the two system roles (it already short-circuits tenant filtering in the CRUD layer).
|
||
2. Replace the seeded role set with these 11 (plus keep `Public` as the unauthenticated fallback mapped to `guest`). Write a data migration mapping legacy roles → new roles (`Super Administrator`→`super_admin`, `Administrator`→`system_admin`, `Platform Owner`→`owner`, `Tenant Director`→`superintendent`, `Campus Manager`→`director`, `Academic Coordinator`→`teacher`, `Finance Officer`→`office_manager`; `para`/`support` HR type → `support_staff`).
|
||
3. Retire `GENERATED_ROLE_TO_PRODUCT_ROLE`, `STAFF_TYPE_TO_PRODUCT_ROLE`, and `PRODUCT_ROLE_VALUES`; the role name + scope is now the source of truth. Replace `productRole` in the `/api/auth/me` DTO with `role: { name, scope }` (keep a stable string the frontend can switch on).
|
||
4. Add a campus link for campus-scoped users. Recommended: a nullable `campusId` on `users` (authorization scope), independent of the HR `staff` row. `organizationId` remains the tenant key; `campusId` is required for `director`/`office_manager`/`teacher`/`support_staff` and optional for `student`/`guardian`; null for `system`/`organization` scopes. (Same scoping layer as Workstream 2.)
|
||
|
||
#### 3.2 Permission catalog expansion
|
||
|
||
The frontend can only gate on permissions the backend actually checks, so every guarded page/action needs a permission name.
|
||
|
||
1. Keep the CRUD × entity permissions; add `classrooms` (alias of `classes` if the same table, otherwise a new entity) to CRUD.
|
||
2. Add product-feature permissions for the module routes and special actions, named in the existing `${VERB}_${THING}` style — e.g. `READ_DIRECTOR_DASHBOARD`, `FILL_ATTENDANCE`, `TAKE_QUIZ`, `ACK_READ_RECEIPT`, `READ_COMMUNITY_PARTNERSHIPS`, `READ_VOCATIONAL_OPPORTUNITIES`, `READ_ESA_FUNDING`, plus one per remaining campus module page.
|
||
3. Define the **role → permission preset matrix** (the "predefined permissions per role" from the spec) and seed it into `rolesPermissionsPermissions`. Directors additionally hand out per-user `custom_permissions`.
|
||
4. Document the matrix in `backend/docs/auth-profile.md` (or a new `backend/docs/rbac.md`).
|
||
|
||
#### 3.3 Relational role constraints (the "except …" rules)
|
||
|
||
The constraints are about *who the target is*, not just *what verb on what entity*, so a flat `${METHOD}_${ENTITY}` permission cannot express them. Implement a policy layer enforced in the **service layer** (authoritative), with a middleware in front for early rejection:
|
||
|
||
1. On user create/update/delete, compare actor scope+role against the target's role: `system_admin` cannot create/delete `super_admin` or `system_admin`; `superintendent` cannot create/delete `owner` or `superintendent`; campus roles cannot escalate above their scope; a director may only create/manage users within its own `campusId`.
|
||
2. On organization delete, require `super_admin`/`system_admin`/`owner` (a `superintendent` is blocked).
|
||
3. Cross-scope tenancy: every actor may only act within its `organizationId` (and `campusId` for campus scope) unless `globalAccess`.
|
||
4. Fix the self-access bypass security hole: `currentUser.id === req.body?.id` (`middlewares/check-permissions.ts`, the self-access branch) lets any user pass a guard by putting their own id in the request body; restrict self-access to read/update of the caller's own profile only.
|
||
|
||
#### 3.4 Scoped provisioning / onboarding contract (former Product Onboarding Contract)
|
||
|
||
Auto-provisioning chain, all backend-owned, each step sending an invitation email with a login link (`services/email/list/invitation.ts` already exists). No-go rules carried over from the former onboarding workstream: do not implement frontend self-registration; do not build profile creation/editing UI ahead of the backend contract; do not treat the generated `signup`/`profile` endpoints as the product contract; no temporary compatibility paths.
|
||
|
||
1. `super_admin`/`system_admin` create an `owner` user → **auto-create the company (organization)** and link the owner; both start minimal (owner has only email+password; company has only the owner link).
|
||
2. `owner`/`superintendent` create superintendents, other org users, and campuses; assign a `director` to a campus.
|
||
3. `director` creates campus users (`teacher`, `support_staff`, `office_manager`, `student`, `guardian`) and classrooms, and grants extra `custom_permissions`.
|
||
4. Every created user receives a login-link email; on first login they land on their own editable profile.
|
||
5. Define which profile fields each role may self-edit vs. which require a higher role.
|
||
|
||
#### 3.5 Backend guard coverage audit
|
||
|
||
1. Confirm **every** route mounts authentication + `checkCrudPermissions`/`checkPermissions` (cross-reference Workstream 5 public-route audit so only intended routes are public).
|
||
2. Add the §3.3 relational policy to the user, organization, campus, staff, and classroom write paths.
|
||
3. Add the product-feature permission checks (§3.2) to the feature routes (FRAME, walkthrough, quizzes, attendance, communications, content-catalog, etc.).
|
||
4. Ensure list/count/autocomplete are tenant- and campus-scoped (ties to Workstream 2).
|
||
|
||
#### 3.6 Frontend guards (former Permission-Based Frontend Authorization)
|
||
|
||
Context: the backend already authorizes every request by **permission**, not role; the user DTO already carries `permissions: string[]`, but the frontend ignores it and gates on the 5-value `productRole`. The goal is to gate UI affordances (routes/menu items/buttons/request triggers) by **permission** — using the same `${METHOD}_${ENTITY}` names the backend checks — while the backend stays the sole source of truth (frontend gating is UX-only and must never be the only enforcement).
|
||
|
||
1. **AuthGuard.** Wrap the shell: unauthenticated → redirect to `/login`; only `/login`, `/signup`, and the error page are reachable without a session.
|
||
2. **Expose permissions in the auth contract.** Confirm `GET /api/auth/me` and the sign-in response include the resolved effective `permissions: string[]` (role permissions ∪ `custom_permissions`); merge `custom_permissions` if not already; document the field in `backend/docs/auth-profile.md`.
|
||
3. **Shared permission vocabulary.** Add a typed catalog on the frontend (`frontend/src/shared/auth/permissions.ts`) mirroring backend `${METHOD}_${ENTITY}` names; keep it as `shared/constants`-style UI config; do not import backend code.
|
||
4. **Permission selector/hook.** Implement `hasPermission/hasAnyPermission/hasAllPermissions` in `frontend/src/business/auth/` (pure functions over `CurrentUser.permissions`) plus a `usePermissions()` hook.
|
||
5. **Gate routes by permission.** Replace `productRole`-based `canUserRoleAccessModuleRoute`/`getAccessibleModules` with permission checks; each `MODULES[]` entry declares the permission(s) it requires. Keep a role concept only where genuinely role-specific (e.g. dashboards), not for resource access.
|
||
6. **Forbidden direct-URL access redirects to the 404 page** (change `canUserRoleAccessModuleRoute`'s unknown-path `return true` and the guard's redirect target); inaccessible sidebar items are hidden.
|
||
7. **Encode the role-specific page sets.** `office_manager` sees all campus pages except the director dashboard (plus quiz/read-receipt/attendance-fill affordances); `teacher`/`support_staff` see all campus pages except the director dashboard; `student`/`guardian` see only `/community-partnerships`, `/vocational-opportunities`, `/esa-funding`; `director`/`superintendent`/`owner` see their full scope; `guest` sees only public routes.
|
||
8. **Gate affordances + handle 403.** Hide/disable create/edit/delete triggers by the matching permission (e.g. hide "Add campus" without `CREATE_CAMPUSES`); surface backend `forbidden` responses through one handler (toast + no crash) so UI/permission drift degrades gracefully.
|
||
9. **Roles/permissions admin UI.** Let an admin/director create a role, attach/detach permissions, and assign `custom_permissions` to a user — backed by the existing `roles`/`permissions`/`users` endpoints.
|
||
10. **Docs.** Update `frontend/docs/frontend-architecture.md` and `backend/docs/auth-profile.md` to describe the permission-based frontend model and the `${METHOD}_${ENTITY}` contract.
|
||
|
||
#### 3.7 Tests
|
||
|
||
1. Backend: relational-constraint tests (`system_admin` cannot delete `super_admin`; `superintendent` cannot delete `owner`/`superintendent` or the company; director scoped to its campus), tenant/campus isolation, and the provisioning chain (owner-create auto-creates company).
|
||
2. Frontend: per-role route-guard tests (each role can/cannot open the right pages), sidebar-visibility tests, `hasPermission`/selector unit tests, unauthenticated→`/login`, and forbidden-URL→404.
|
||
3. Backend-seeded authenticated e2e using the Workstream 4 fixtures (feeds Workstream 8) proving UI affordances match backend enforcement for at least one CRUD entity.
|
||
|
||
Acceptance criteria:
|
||
|
||
- The 11 roles exist as first-class scoped roles; the legacy mapping and 5-value `productRole` are gone.
|
||
- Backend is the sole authority: every route enforces authentication + permission + (where relational) the §3.3 policy; removing a frontend check never grants real access (backend still returns 403).
|
||
- The "except …" constraints hold in tests for `system_admin`, `superintendent`, and `director`.
|
||
- Frontend route/menu/affordance visibility is permission-driven; unauthenticated users are redirected to `/login`; forbidden direct URLs land on 404; guest sees only public routes.
|
||
- An admin can create a role, assign permissions, and the change is reflected in both backend enforcement and frontend UI for affected users.
|
||
- Owner-creation auto-creates the company; each provisioning step emails a login link.
|
||
- Backend and frontend `typecheck`/`lint`/`test` pass; the Workstream 4 seed runs clean and backs an authenticated e2e.
|
||
|
||
### 4. RBAC Seed Data And Fixtures
|
||
|
||
Status: open — depends on Workstream 3 (run after the role/permission/model changes land). Implements the spec's seed requirement: a preset company with one campus and staff covering every role, plus one test user per role. Also re-verifies the `npm run db:seed` gate from Workstream 1 against the final seed content.
|
||
|
||
Required work:
|
||
|
||
1. **Reconcile the admin identity with the spec.** Make `admin@flatlogic.com` (`SEED_ADMIN_EMAIL`) the **`super_admin`** (the spec's superadmin), not `Administrator`. Add a distinct `system_admin` seed user. Repurpose or delete the leftover template users (`john@doe.com`, `client@hello.com`, `super_admin@flatlogic.com`) so the seed set is exactly the role fixtures below.
|
||
2. **Seed one organization (company)** and **one campus** linked to it (give the existing `campuses` seed rows an `organizationId`, or seed a dedicated test campus).
|
||
3. **Seed 10 users, one per stored role** — `super_admin`, `system_admin`, `owner`, `superintendent`, `director`, `office_manager`, `teacher`, `support_staff`, `student`, `guardian` — each with `app_roleId` set, `organizationId` set (except the two `system` users), and `campusId` set for the campus-scoped and external users as applicable. (`guest` = no user.) Passwords come from `SEED_ADMIN_PASSWORD`/`SEED_USER_PASSWORD` (no plaintext in the repo); test credentials live only in ignored local env or a dedicated test-seed config (per Workstream 8).
|
||
4. **Seed staff profiles** for the campus staff roles (`director`, `office_manager`, `teacher`, `support_staff`) so the campus has staff covering every staff role, each linked to the org, the campus, and its user.
|
||
5. Make the seed **idempotent** and reversible (clean `down`), and ordered after the role/permission seeder.
|
||
6. Re-verify the seeded data: one company, one campus under it, staff with all campus roles, and exactly one loginable user per role. Add a seed/integration check.
|
||
|
||
Acceptance criteria:
|
||
|
||
- `npm run db:seed` produces exactly one company, one campus under that company, staff covering every campus role, and one user per role with correct role/org/campus links.
|
||
- `admin@flatlogic.com` is the `super_admin` and can log in; each seeded role can log in and lands on the access surface defined in §3.6.
|
||
- No template leftover users remain; no plaintext credentials are committed.
|
||
|
||
### 5. Public Backend Route Audit
|
||
|
||
Status: open. Feeds Workstream 3 §3.5 (guard coverage): the guard audit can only assert "every non-public route is authenticated" once the public set is fixed.
|
||
|
||
Problem:
|
||
|
||
Public routes must be intentionally public. This includes public content catalog routes and any generated/template public integrations.
|
||
|
||
Required work:
|
||
|
||
1. List all routes without auth middleware.
|
||
2. Mark each as public-by-design or requiring auth.
|
||
3. Add auth where required.
|
||
4. Document intentionally public routes.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Every unauthenticated backend route is documented.
|
||
- No tenant-owned data is exposed through accidental public routes.
|
||
|
||
### 6. API Surface Coverage And Dead-Endpoint Decision
|
||
|
||
Status: open (analysis complete; decision pending). Belongs with the access core: the permission catalog (§3.2) and guard audit (§3.5) must cover exactly the endpoints that survive this classification.
|
||
|
||
Problem:
|
||
|
||
The backend exposes the full Flatlogic-generated CRUD surface for all 39 models plus
|
||
template auth/file/search routes, but the product frontend only consumes a small set of
|
||
custom feature endpoints. The unused surface is dead code and attack surface; it must be
|
||
either pruned or intentionally wired.
|
||
|
||
Method (how this was established):
|
||
|
||
- Enumerated every backend route from `backend/src/routes/*` plus its mount prefix in `backend/src/index.ts`.
|
||
- Enumerated every frontend HTTP call. All frontend HTTP goes through `frontend/src/shared/api/*` over `httpClient` (`fetch`); there are no stray `fetch`/`axios`/`XMLHttpRequest` calls elsewhere, so the frontend call list is exhaustive.
|
||
|
||
Findings:
|
||
|
||
- **Frontend is fully wired:** every `shared/api` method targets a real, mounted backend endpoint. There are **0 orphan/broken frontend calls**.
|
||
- **Backend is only partially consumed:** of ~278 endpoints, the frontend uses **~35 (~13%)**; **~243 (~87%) have no frontend consumer.**
|
||
- The mismatch is one-directional: the frontend calls nothing extra; the backend carries a large unused layer.
|
||
|
||
Consumed endpoints (the only wired surface — 35):
|
||
|
||
- `auth` (4 of 16): `POST /api/auth/signin/local`, `GET /api/auth/me`, `POST /api/auth/refresh`, `POST /api/auth/signout`.
|
||
- `campuses` (1): `GET /api/public/campuses` (the authenticated `/api/campuses` CRUD is NOT used).
|
||
- `content-catalog` (6): `GET /api/public/content-catalog/:contentType`, `GET /api/content-catalog`, `GET /api/content-catalog/:contentType`, `POST /api/content-catalog`, `PUT /api/content-catalog/:contentType`, `DELETE /api/content-catalog/:contentType`.
|
||
- `campus_attendance` (4): `GET /configs`, `PUT /configs/:campusKey`, `GET /summaries`, `PUT /summaries/:campusKey/:date`.
|
||
- `communications` (4): `GET /parent-messages`, `POST /parent-messages`, `GET /events`, `POST /events`.
|
||
- `frame_entries` (3): `GET /`, `POST /`, `PUT /:id`.
|
||
- `personality_quiz_results` (3): `GET /me`, `PUT /me`, `GET /distribution`.
|
||
- `safety_quiz_results` (2): `GET /`, `POST /`.
|
||
- `staff_attendance` (2): `GET /records`, `GET /summary`.
|
||
- `user_progress` (3): `GET /`, `POST /`, `DELETE /by-item`.
|
||
- `walkthrough_checkins` (3): `GET /`, `POST /`, `DELETE /:id`.
|
||
|
||
Unused backend endpoints (no frontend consumer):
|
||
|
||
1. **Generic CRUD template — 25 route groups × 9 endpoints = 225, none called:**
|
||
`academic_years`, `assessments`, `assessment_results`, `attendance_records`,
|
||
`attendance_sessions`, `campuses` (the `/api/campuses` CRUD), `classes`,
|
||
`class_enrollments`, `class_subjects`, `fee_plans`, `grades`, `guardians`,
|
||
`invoices`, `message_recipients`, `messages`, `organizations`, `payments`,
|
||
`permissions`, `roles`, `staff`, `students`, `subjects`, `timetable_periods`,
|
||
`timetables`, `users`.
|
||
Each exposes the identical shape: `POST /`, `POST /bulk-import`, `PUT /:id`,
|
||
`DELETE /:id`, `POST /deleteByIds`, `GET /`, `GET /count`, `GET /autocomplete`,
|
||
`GET /:id`.
|
||
2. **`auth` extras — 12, not called:** `POST /api/auth/signup`, `PUT /api/auth/profile`,
|
||
`PUT /api/auth/password-reset`, `PUT /api/auth/password-update`,
|
||
`POST /api/auth/send-password-reset-email`,
|
||
`POST /api/auth/send-email-address-verification-email`,
|
||
`PUT /api/auth/verify-email`, `GET /api/auth/email-configured`,
|
||
`GET /api/auth/signin/google` (+ `/callback`), `GET /api/auth/signin/microsoft` (+ `/callback`).
|
||
3. **`file` — 2, not called:** `GET /api/file/download`, `POST /api/file/upload/:table/:field`
|
||
(the frontend never uploads; `DocumentMutationDto` has no `file`).
|
||
4. **`search` — 1, not called:** `GET /api/search`.
|
||
|
||
**Decision (owner, recorded):** the generic CRUD layer is **WIRE — kept, not
|
||
pruned.** These ~24 groups are not dead code; they will be used and integrated
|
||
with the frontend later (likely modeled on `ref-frontend`). No generic CRUD group
|
||
is to be removed. The `auth`/`file`/`search` extras (items 2–4 below) remain
|
||
individually decision-gated.
|
||
|
||
Required work:
|
||
|
||
1. Generic CRUD groups: **WIRE** (decided above) — build the frontend that uses
|
||
them (e.g. real `students`/`staff`/`guardians`/`invoices` management) and bring
|
||
each group up to the target backend architecture. Do **not** prune them.
|
||
2. `auth` extras: keep `signup`/password-reset/verify-email only if onboarding/recovery is
|
||
in scope (see Workstream 3 §3.4 provisioning); otherwise prune. OAuth callbacks tie to Workstream 15.
|
||
3. `file`: keep only if document/avatar upload is on the roadmap (ties to Workstream 7);
|
||
otherwise prune.
|
||
4. `search`: prune unless a search UI is planned.
|
||
5. After any prune, re-run backend `typecheck`/`lint`, and regenerate
|
||
`database-schema.md` only if models change.
|
||
|
||
Cross-references: Workstream 7 (file upload), 5 (public route audit), 15 (OAuth), 3 (RBAC / permission-based authorization).
|
||
|
||
Acceptance criteria:
|
||
|
||
- Every backend endpoint is classified as **used**, **prune**, or **wire (planned)**, with no "unclassified" remainder.
|
||
- Pruned routes are removed cleanly (route + service + db/api + permission wiring) with `typecheck`/`lint` green and no dangling imports.
|
||
- The frontend remains fully wired (0 orphan calls) after changes.
|
||
- This document reflects the final surface.
|
||
|
||
Performance hardening applied (owner chose **wire**, not prune, for the generic CRUD layer — it will back near-term management UIs modeled on `ref-frontend`):
|
||
|
||
- `findBy` in all 24 generic-CRUD `db/api/*.ts` now loads its associations via a single `Promise.all` instead of sequential awaited getters (detail-endpoint latency drops from sum to max of the association queries). `users.findBy` was done earlier.
|
||
- List pagination now has shared defaults via `@/shared/constants/pagination` (`resolvePagination`, default page size 10 to match the `ref-frontend` grids, capped at 100). Applied to every generic-CRUD `findAll` and to the feature lists (`user_progress`, `safety_quiz_results`, `walkthrough_checkins`, `frame_entries`, `content_catalog`, `communications` parent-messages/events, `campus_attendance` configs), which were reverted from `findAll` back to `findAndCountAll` so `count` is the true total for the pager. `staff_attendance /records` and `campus_attendance /summaries` keep their pre-existing per-endpoint limits.
|
||
- Fixed a latent CRUD tenant-scoping bug: routes and `db/api` create/update read `currentUser.organization?.id` (singular), but `findBy` only ever populates `organizations` (plural) + the `organizationId` scalar, so that read was always `undefined` (non-global creates silently set no organization). All 22 `db/api` + 24 route reads now use `currentUser.organizationId`; the dead 3-field fallback term was dropped from the 4 feature services; and the non-existent `organization?: { id }` field was removed from the `CurrentUser` type so the mistake cannot recur.
|
||
- The redundant existence-check `findBy` in the 22 CRUD `update` services was removed; they now rely on `DbApi.update` returning `null` (avoids loading every association just to validate existence). `remove` already had no pre-check.
|
||
- The per-request `UsersDBApi.findBy` (passport JWT strategy → `req.currentUser`, read by every guarded route) was collapsed from `findOne` + 4 parallel association getters + a `getPermissions()` into a **single** eager-loaded query (`findOne` + `include` of `app_role`+`permissions`, `staff_user`, `custom_permissions`, `organizations`). Its returned `app_role` now carries `permissions`, and `middlewares/check-permissions.ts` was reordered to read that eager-loaded `permissions` array before falling back to `getPermissions()` — removing the extra per-request permissions query. Same returned shape/fields as before (`AuthenticatedUser`); ~6–7 queries per request → 1. The cached `Public` role still works (its record carries `permissions`).
|
||
- `AuthService.currentUserProfile` (the `GET /me`, signin and refresh responses) now uses a dedicated `UsersDBApi.findProfileById` — a single eager-loaded query (`findByPk` + scoped `include`/`attributes`) returning only the columns and relations the profile DTO reads — instead of the heavy generic `findBy` (1 `findOne` + parallel association getters + `getPermissions`) plus a separate `getCampus`. Required idiomatic `NonAttribute` association declarations on the `Users`/`Roles`/`Staff` models so the include is type-safe without casts. The per-request passport `findBy` that populates `req.currentUser` is unchanged (it is the auth gate read by every guard); `/me` still performs that auth fetch plus this one lean profile query.
|
||
|
||
### 7. File Upload And Download Permissions
|
||
|
||
Status: open. Permission-gated surface; aligns with Workstream 3 §3.2/§3.5 and Workstream 6 (`file` extras).
|
||
|
||
Problem:
|
||
|
||
Backend file and document routes exist. Product file workflows need explicit permission verification before new upload/download UI is added.
|
||
|
||
Required work:
|
||
|
||
1. Audit upload permissions.
|
||
2. Audit download permissions.
|
||
3. Verify tenant and document ownership checks.
|
||
4. Add typed frontend upload client only for approved workflows.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Unauthorized users cannot access another tenant's files.
|
||
- Upload/download behavior is documented and tested before new UI is added.
|
||
|
||
**Phase 3 — Verification & Quality (locks in correctness once the access model and fixtures exist).**
|
||
|
||
### 8. Backend-Seeded Authenticated E2E
|
||
|
||
Status: depends on Workstream 4 fixtures and Workstream 3 guards.
|
||
|
||
Problem:
|
||
|
||
Current Playwright coverage includes backend-free smoke tests and backend-seeded content catalog tests. Authenticated persisted workflows need backend-seeded users, roles, campuses, staff profiles, and known credentials.
|
||
|
||
Required prerequisites:
|
||
|
||
1. Product onboarding/profile contract (Workstream 3 §3.4).
|
||
2. Backend-seeded auth fixtures (Workstream 4).
|
||
3. Tenant-scoped campus/staff fixtures (Workstream 4).
|
||
4. Stable test credentials stored only in ignored local env or dedicated test seed config.
|
||
|
||
Required workflows after prerequisites:
|
||
|
||
1. Login to dashboard.
|
||
2. Director creates or edits a FRAME entry and sees it after reload.
|
||
3. Staff completes QBS quiz and director/superintendent sees compliance.
|
||
4. Office enters campus attendance and superintendent sees aggregate.
|
||
5. Director submits walkthrough and sees summary update.
|
||
6. Staff marks a sign learned and progress persists after reload.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Authenticated e2e tests use backend seeds, not frontend mock data.
|
||
- Tests do not require production secrets.
|
||
- Tests are documented and repeatable.
|
||
|
||
### 9. API Documentation Hardening
|
||
|
||
Status: open. Run after the route surface is finalized (Workstreams 3, 5, 6) so the docs describe the real endpoints.
|
||
|
||
Problem:
|
||
|
||
Markdown docs exist for migrated modules, but Swagger/OpenAPI coverage for product-specific and cookie-session endpoints is incomplete.
|
||
|
||
Required work:
|
||
|
||
1. Document `/api/auth/signin/local`.
|
||
2. Document `/api/auth/refresh`.
|
||
3. Document `/api/auth/signout`.
|
||
4. Document `/api/auth/me`.
|
||
5. Document product module endpoints that are not covered by generated Swagger output.
|
||
6. Document response and error shapes per endpoint.
|
||
|
||
Acceptance criteria:
|
||
|
||
- API docs match actual route payloads and response shapes.
|
||
- Cookie auth behavior is explicit.
|
||
- Frontend API contract tests remain aligned with docs.
|
||
|
||
### 10. Accessibility Test Coverage
|
||
|
||
Status: open.
|
||
|
||
Required work:
|
||
|
||
1. Add axe/Playwright accessibility checks for login.
|
||
2. Add axe/Playwright checks for dashboard.
|
||
3. Add checks for sidebar navigation.
|
||
4. Add checks for modal dialogs.
|
||
5. Add checks for forms with validation.
|
||
6. Add checks for tables/reports.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Accessibility tests run in a documented command.
|
||
- Critical violations block completion of the refactor.
|
||
|
||
**Phase 4 — Product Contracts (each gated by a customer/provider decision; independent of one another and parallelizable once the access core is in place).**
|
||
|
||
### 11. Policy And Safety Acknowledgment Persistence
|
||
|
||
Status: open pending product contract.
|
||
|
||
Problem:
|
||
|
||
Policy content is document-backed, but policy/protocol acknowledgments are not yet persisted.
|
||
|
||
Required decisions:
|
||
|
||
1. Which policies/protocols require acknowledgment.
|
||
2. Which roles must acknowledge.
|
||
3. Whether acknowledgments are per document version.
|
||
4. Who can report acknowledgment status.
|
||
|
||
Required work after decision:
|
||
|
||
1. Add acknowledgment backend model/migration/service/route.
|
||
2. Enforce tenant and role scope.
|
||
3. Add frontend typed API and business workflow.
|
||
4. Add report views only if required.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Acknowledgments survive reload.
|
||
- Acknowledgment status is tenant-scoped.
|
||
- Unauthorized roles cannot view individual acknowledgment records.
|
||
|
||
### 12. Attendance Source Contracts
|
||
|
||
Status: partially open.
|
||
|
||
Current state:
|
||
|
||
- Campus attendance daily aggregate summaries are implemented for the current UI.
|
||
- Staff attendance snapshot/reporting is read-only.
|
||
- Student/class attendance source-of-truth workflow is not defined.
|
||
|
||
Open decisions:
|
||
|
||
1. Whether campus attendance aggregates are manually entered, imported, or derived from student attendance records.
|
||
2. Which external or internal source owns staff attendance writes/imports.
|
||
3. Whether student-level attendance UI is required.
|
||
|
||
Required work after decision:
|
||
|
||
1. Add write/import endpoints only after source contract exists.
|
||
2. Keep derived summaries server-side if summaries are derived.
|
||
3. Add backend tests for source-of-truth calculations.
|
||
4. Add frontend workflows only after backend contracts exist.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Attendance source of truth is documented.
|
||
- UI values can be traced to backend records or server-side derivation.
|
||
- No frontend-only attendance source remains in persisted workflows.
|
||
|
||
### 13. Generated Audio Provider Contract
|
||
|
||
Status: open pending provider decision.
|
||
|
||
Problem:
|
||
|
||
Classroom timer uses built-in Web Audio sounds. AI-generated audio UI is intentionally not exposed because no backend audio provider contract exists.
|
||
|
||
No-go rules:
|
||
|
||
- Do not add generated audio UI.
|
||
- Do not call external audio providers from frontend runtime.
|
||
- Do not add frontend API keys or provider secrets.
|
||
|
||
Required work after decision:
|
||
|
||
1. Define backend provider contract.
|
||
2. Keep provider secrets in backend env.
|
||
3. Add backend service with native provider errors where required.
|
||
4. Add typed frontend API and explicit loading/error states.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Generated audio is backend-mediated.
|
||
- No provider secret reaches the browser.
|
||
- Provider failures remain visible.
|
||
|
||
**Phase 5 — Operations, Modernization & Cleanup (low coupling; can run any time after Phase 2, scheduled last to avoid mixing with the access-model changes).**
|
||
|
||
### 14. Refresh Token Maintenance
|
||
|
||
Status: open.
|
||
|
||
Problem:
|
||
|
||
Cookie auth and refresh rotation exist, but scheduled cleanup for expired/revoked refresh-token rows remains unresolved.
|
||
|
||
Required work:
|
||
|
||
1. Define refresh-token retention period.
|
||
2. Add cleanup job or operational command.
|
||
3. Ensure cleanup is observable.
|
||
4. Document operational usage.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Expired and revoked refresh-token rows are cleaned after the approved retention window.
|
||
- Cleanup failures are visible and not silent.
|
||
- Auth behavior remains unchanged for valid sessions.
|
||
|
||
### 15. OAuth Provider Strategy Modernization
|
||
|
||
Status: open. Deferred until after the backend TypeScript/ESM migration (now complete), to keep auth-flow changes separate from the language/module migration.
|
||
|
||
Problem:
|
||
|
||
Social login uses `passport-google-oauth2` (0.2.0, last published 2022) and `passport-microsoft` (2.1.0). The Google strategy is low-maintenance and should be modernized, ideally consolidating both providers on a single maintained OAuth/OIDC library.
|
||
|
||
Required work:
|
||
|
||
1. Choose target: minimal swap to `passport-google-oauth20`, or consolidate both providers on `openid-client`.
|
||
2. Replace the Google (and optionally Microsoft) passport strategy in `backend/src/auth/auth.ts`.
|
||
3. Keep the existing cookie-based session and JWT issuance unchanged.
|
||
4. Verify callback URLs, scopes, and the social-signup user flow end to end.
|
||
5. Update auth docs (`backend/docs/auth-profile.md`, `cookie-auth.md`).
|
||
|
||
Acceptance criteria:
|
||
|
||
- Google and Microsoft sign-in work end to end with the chosen library.
|
||
- No deprecated/unmaintained OAuth strategy remains in dependencies.
|
||
- Cookie/JWT auth behavior is unchanged for existing sessions.
|
||
- This change is isolated from the language/module migration (separate PR/task).
|
||
|
||
### 16. `ref-frontend/` Removal
|
||
|
||
Status: open.
|
||
|
||
Required work:
|
||
|
||
1. Confirm no active docs require `ref-frontend/` for endpoint contract reference.
|
||
2. Confirm no scripts import or run `ref-frontend/`.
|
||
3. Delete `ref-frontend/`.
|
||
4. Update root docs and any setup docs.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Only `frontend/` and `backend/` remain as active application code.
|
||
- No docs describe `ref-frontend/` as needed for normal development.
|
||
|
||
## Phased Execution Order
|
||
|
||
Run in workstream-number order unless the user explicitly reprioritizes. The phases are the dependency gates; within a phase, items may proceed in parallel.
|
||
|
||
1. **Phase 1 — Foundation:** Workstream 1 (backend migrates, boots, serves routes).
|
||
2. **Phase 2 — Identity, Tenancy & Access Core:** Workstream 2 (tenant isolation) → Workstream 3 (RBAC: roles, permissions, constraints, provisioning, guards) → Workstream 4 (RBAC seed/fixtures), with Workstream 5 (public route audit), Workstream 6 (API surface classify/wire), and Workstream 7 (file permissions) finalizing the protected surface alongside Workstream 3.
|
||
3. **Phase 3 — Verification & Quality:** Workstream 8 (authenticated seeded e2e) → Workstream 9 (API docs) → Workstream 10 (accessibility).
|
||
4. **Phase 4 — Product Contracts (customer-gated, parallel):** Workstreams 11 (acknowledgments), 12 (attendance source), 13 (generated audio).
|
||
5. **Phase 5 — Operations, Modernization & Cleanup:** Workstreams 14 (refresh-token cleanup), 15 (OAuth modernization), 16 (`ref-frontend/` removal).
|
||
|
||
## Definition Of Done
|
||
|
||
The integration refactor is complete only when all of the following are true:
|
||
|
||
- Backend migrations and seeders pass on the configured local database.
|
||
- Backend lint passes without broad ignores.
|
||
- Backend tenant isolation tests prove cross-tenant data is not visible or mutable.
|
||
- The 11-role scoped hierarchy (Workstream 3) is enforced backend-side, including the relational "except …" constraints for `system_admin`, `superintendent`, and `director`; the legacy generated-role mapping and 5-value `productRole` are removed.
|
||
- Backend role/permission tests cover product staff, director, and superintendent paths.
|
||
- Frontend gating is permission-driven with an authentication guard; unauthenticated users redirect to `/login`, forbidden direct URLs land on 404, and guest sees only public routes.
|
||
- The RBAC seed (Workstream 4) provisions one company, one campus with staff covering every campus role, and one loginable user per role, with `admin@flatlogic.com` as the super admin.
|
||
- Every unauthenticated backend route is documented as public-by-design.
|
||
- Product onboarding contract is implemented per Workstream 3 §3.4 or explicitly excluded from release scope.
|
||
- Every visible frontend workflow is backed by a typed backend API contract.
|
||
- No frontend runtime workflow depends on mock, sample, seed, or fallback records for persisted behavior.
|
||
- Frontend `typecheck`, `lint`, `test`, `build`, `test:e2e`, and documented seeded e2e suites pass in their required environments.
|
||
- Backend auth/API docs match actual route behavior.
|
||
- Required accessibility checks pass.
|
||
- `ref-frontend/` is deleted or a dated, explicit exception explains why it is still needed.
|