589 lines
31 KiB
Markdown
589 lines
31 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
|
||
|
||
### 1. Backend Migration And Runtime Verification
|
||
|
||
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`.
|
||
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.
|
||
|
||
### 2. Tenant Boundary Audit And Tests
|
||
|
||
Status: open and high priority.
|
||
|
||
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. Role Model Decision
|
||
|
||
Status: open.
|
||
|
||
Problem:
|
||
|
||
Backend currently maps generated roles to product roles. It is not yet a final product decision whether this mapping is the supported model or product roles should become first-class persisted backend roles/permissions.
|
||
|
||
Required decision:
|
||
|
||
- Option A: keep generated-role to product-role mapping as the supported model.
|
||
- Option B: create first-class persisted product roles: `Teacher`, `Para`, `Office Manager`, `Director`, `Superintendent`.
|
||
|
||
Required work after decision:
|
||
|
||
1. Document the chosen role model.
|
||
2. Align `/api/auth/me` permissions and product role payload with the chosen model.
|
||
3. Add backend role/permission tests for staff, director, and superintendent paths.
|
||
4. Keep frontend navigation driven by backend role/permissions.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Role model is documented in backend docs.
|
||
- Backend tests prove role access for product modules.
|
||
- No frontend role grants capability that backend does not enforce.
|
||
|
||
### 4. Product Onboarding Contract
|
||
|
||
Status: blocked by customer decision.
|
||
|
||
Problem:
|
||
|
||
Generated auth signup/profile endpoints exist, but they do not define the product workflow for company creation, campus creation, user creation, staff profile creation, role assignment, campus assignment, or profile updates.
|
||
|
||
No-go rules:
|
||
|
||
- Do not implement frontend registration.
|
||
- Do not implement profile creation/editing UI.
|
||
- Do not treat generated signup/profile endpoints as the product onboarding contract.
|
||
- Do not add temporary compatibility paths.
|
||
|
||
Required customer decisions:
|
||
|
||
1. Who creates a company.
|
||
2. Who creates campuses.
|
||
3. Who invites or creates users.
|
||
4. How staff profiles are created and linked to users.
|
||
5. How roles and campuses are assigned.
|
||
6. Which profile fields users may update themselves.
|
||
7. Which profile fields require director/superintendent/admin permissions.
|
||
|
||
Required work after decision:
|
||
|
||
1. Add backend onboarding contract and docs.
|
||
2. Add tenant-scoped backend workflows.
|
||
3. Add frontend typed API modules.
|
||
4. Add frontend business hooks.
|
||
5. Add UI only after backend contract exists.
|
||
6. Add authenticated Playwright workflows using backend-seeded fixtures.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Onboarding workflow is customer-approved and documented.
|
||
- Backend owns all creation, assignment, and permission checks.
|
||
- Frontend only exposes flows backed by typed backend contracts.
|
||
|
||
### 5. 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.
|
||
|
||
### 6. API Documentation Hardening
|
||
|
||
Status: open.
|
||
|
||
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.
|
||
|
||
### 7. 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.
|
||
|
||
### 8. 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.
|
||
|
||
### 9. 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.
|
||
|
||
### 10. File Upload And Download Permissions
|
||
|
||
Status: open.
|
||
|
||
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.
|
||
|
||
### 11. Public Backend Route Audit
|
||
|
||
Status: open.
|
||
|
||
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.
|
||
|
||
### 12. Backend-Seeded Authenticated E2E
|
||
|
||
Status: blocked by onboarding/profile fixtures.
|
||
|
||
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.
|
||
2. Backend-seeded auth fixtures.
|
||
3. Tenant-scoped campus/staff fixtures.
|
||
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.
|
||
|
||
### 13. 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.
|
||
|
||
### 14. `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.
|
||
|
||
### 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. Permission-Based Frontend Authorization
|
||
|
||
Status: open.
|
||
|
||
Problem:
|
||
|
||
The backend already authorizes every request by **permission**, not by role. `backend/src/middlewares/check-permissions.ts` exposes `checkCrudPermissions(entity)`, which derives a permission name `${METHOD}_${ENTITY}` (e.g. `READ_CAMPUSES`, `CREATE_USERS`, `DELETE_ROLES` via `METHOD_MAP`) and grants access when any of the following holds, in order: (1) self-access — `currentUser.id === req.params.id`/`req.body.id`; (2) the user has a direct `custom_permissions` entry with that name; (3) the user's `app_role` (or the `Public` role for unauthenticated/no-role requests) has that permission via `role.getPermissions()`. An admin can therefore create roles and attach permissions (`Roles ↔ Permissions` many-to-many through `RolesDBApi.setPermissions`), plus assign per-user `custom_permissions`.
|
||
|
||
The frontend, however, does **not** gate on permissions. Module/page access is currently decided by `productRole` (`teacher | para | office | director | superintendent`) in `frontend/src/business/app-shell/selectors.ts` (`canAccessModule(modules, moduleId, userRole)`), and per the architecture contract frontend route hiding is **UX-only**. The user DTO already carries a `permissions: string[]` array (built by `getPermissionNames` in `services/auth.ts`), but the frontend ignores it. This makes the frontend authorization model inconsistent with the backend: a role with customized permissions is correctly enforced by the backend but not reflected in what the UI shows or allows.
|
||
|
||
Goal: make the frontend gate UI affordances (routes/menu items/buttons/request triggers) by **permission** — using the same `${METHOD}_${ENTITY}` permission names the backend checks — while keeping the backend as the sole source of truth (frontend gating stays UX-only; it must never be the only enforcement).
|
||
|
||
Required work:
|
||
|
||
1. **Expose permissions reliably in the auth contract.** Confirm `GET /api/auth/me` and the sign-in response include the resolved `permissions: string[]` (effective permissions = role permissions ∪ `custom_permissions`). Add `custom_permissions` to the resolution if not already merged. Document the exact field in `backend/docs/auth-profile.md`.
|
||
2. **Define a shared permission vocabulary.** Add a typed catalog of permission names on the frontend (`frontend/src/shared/auth/permissions.ts`) mirroring backend naming `${METHOD}_${ENTITY}` (READ/CREATE/UPDATE/DELETE × entity). Keep it in `shared/constants`-style UI config; do not import backend code.
|
||
3. **Add a permission selector/hook.** Implement `hasPermission(user, permissionName)` and `hasAnyPermission/hasAllPermissions` in `frontend/src/business/auth/` (pure functions over `CurrentUser.permissions`), plus a `usePermissions()` hook for components. Include the self-access nuance only where relevant (the backend allows self-access regardless of permission).
|
||
4. **Gate routes by permission.** Replace/augment role-based `canAccessModule` with permission-based checks. Each route/module declares the permission(s) it requires; the router redirects/hides when the user lacks them. Keep `productRole` only where a true role concept is still needed (e.g. dashboards), not for resource access.
|
||
5. **Gate UI affordances.** Hide or disable create/edit/delete buttons and other action triggers based on the matching permission (e.g. hide "Add campus" without `CREATE_CAMPUSES`). Avoid firing requests the backend will reject with 403.
|
||
6. **Handle 403 consistently.** Ensure the API layer surfaces backend `forbidden` responses to a single handler (toast + no crash), so permission drift between UI and backend degrades gracefully.
|
||
7. **Admin UI for roles/permissions.** Verify the roles management screens let an admin create a role, attach/detach permissions, and assign `custom_permissions` to a user — backed by the existing `roles`/`permissions`/`users` endpoints.
|
||
8. **Tests.** Unit-test `hasPermission`/selectors with permission fixtures; add route-guard tests proving a user without `READ_X` cannot open module X; add a backend-seeded e2e proving UI affordances match backend enforcement for at least one CRUD entity.
|
||
9. **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.
|
||
|
||
Acceptance criteria:
|
||
|
||
- Frontend route/menu/affordance visibility is driven by the user's effective permissions, using the same names the backend enforces.
|
||
- The backend remains the sole authority: removing a frontend check never grants real access (backend still returns 403).
|
||
- An admin can create a role, assign permissions, and the change is reflected in both backend enforcement and frontend UI for affected users.
|
||
- `productRole`-based gating is removed for resource access (kept only for genuine role-specific UI), with no remaining role↔permission inconsistency.
|
||
- Frontend `typecheck`, `lint`, `test` pass; a seeded e2e proves UI/permission alignment for at least one entity.
|
||
|
||
### 17. API Surface Coverage And Dead-Endpoint Decision
|
||
|
||
Status: open (analysis complete; decision pending).
|
||
|
||
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 4); otherwise prune. OAuth callbacks tie to Workstream 15.
|
||
3. `file`: keep only if document/avatar upload is on the roadmap (ties to Workstream 10);
|
||
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 10 (file upload), 11 (public route audit), 15 (OAuth), 16
|
||
(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.
|
||
|
||
## Strict Implementation Sequence
|
||
|
||
Use this order unless the user explicitly reprioritizes:
|
||
|
||
1. Backend migration and seed verification.
|
||
2. Tenant boundary audit and tests.
|
||
3. Role model decision and enforcement tests.
|
||
4. Public route audit.
|
||
5. API documentation hardening.
|
||
6. Product onboarding after customer decision.
|
||
7. Authenticated backend-seeded e2e after onboarding/profile fixtures exist.
|
||
8. Remaining optional product contracts: acknowledgments, attendance imports, generated audio, file upload/download UI.
|
||
9. Accessibility test coverage.
|
||
10. Remove `ref-frontend/`.
|
||
11. OAuth provider strategy modernization.
|
||
12. Permission-based frontend authorization (align frontend gating with backend permission checks).
|
||
13. API surface coverage decision: classify every endpoint and prune or wire the unused backend layer (Workstream 17).
|
||
|
||
## 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.
|
||
- Backend role/permission tests cover product staff, director, and superintendent paths.
|
||
- Every unauthenticated backend route is documented as public-by-design.
|
||
- Product onboarding contract is customer-approved 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.
|