July 3rd - Review and optimize codes
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
> **Audience:** AI assistants and developers working on this codebase.
|
||||
> **Purpose:** Authoritative reference for architecture, conventions, gotchas, and decisions.
|
||||
> **Last reviewed:** June 2026 (Phase 19 complete + mobile API gap-fill Phases A–E + customer UI refinements + Phase 22 comment visibility + Phase 23 support chat/tickets + inspector performance Excel export + inspection list filters + customer issue logging + AI chatbot + dashboard grouped sections + issues/inspections PDF export + date/ID filters + Reports expansion Phases R1–R4 + Phase 24 issue_created notify defaults + Phase 25 inspection GPS + Phase 26 issue vendor fields + Phase 27 facility score alerts + **MT-0 through MT-8 complete; self-service signup; trial enforcement; billing emails; invoice history; superadmin billing controls; per-tenant backup CLI; health dashboard; fail2ban brute-force protection; welcome email on signup; dunning sequence day-3/7/14; MT-9 iOS pending**)
|
||||
> **Last reviewed:** July 2026 (doc-reconciliation pass — verified against code on disk. Adds previously-undocumented phase28 notify-fix, phase29 broadcasts, phase30–32 device registry; `broadcast` + `devices` + `api_devices` blueprints; Broadcast + DeviceRegistration models; corrected MT-8 billing status to DONE; resolved the device-registration collision (rule 84 — removed duplicate `api_devices` blueprint + `DeviceRegistration` model, consolidated on `DeviceToken`). Prior: Phase 19 + mobile API Phases A–E + Phase 22 comment visibility + Phase 23 support chat/tickets + inspector Excel export + inspection list filters + customer issue logging + AI chatbot + dashboard grouped sections + issues/inspections PDF export + Reports R1–R4 + Phase 24 notify defaults + Phase 25 GPS + Phase 26 vendor fields + Phase 27 score alerts + **MT-0 through MT-8 complete; self-service signup; trial enforcement; billing emails; invoice history; superadmin billing controls; per-tenant backup CLI; health dashboard; fail2ban; welcome email; dunning day-3/7/14; MT-9 iOS pending**)
|
||||
|
||||
---
|
||||
|
||||
@@ -400,10 +400,26 @@ audit_logs: id, user_id (nullable), username (snapshot), user_role (snapshot),
|
||||
```
|
||||
api_refresh_tokens: id, user_id, token_hash (SHA-256, unique), device_id, device_name,
|
||||
created_at, expires_at, revoked
|
||||
api_device_tokens: id, user_id, device_id, apns_token, device_name, app_version, registered_at
|
||||
api_device_tokens: id, user_id, device_id, apns_token, device_name, app_version, registered_at,
|
||||
ios_version (phase31/32), last_seen_at (phase31/32)
|
||||
UniqueConstraint(user_id, device_id)
|
||||
```
|
||||
|
||||
`api_device_tokens` (model `DeviceToken`) is the canonical device registry read by the admin Devices page (`/admin/devices`).
|
||||
|
||||
### Broadcast (phase29)
|
||||
|
||||
```
|
||||
broadcasts: id, title VARCHAR(255), body TEXT, target_roles JSON (list[str]),
|
||||
sent_by_id (FK→users SET NULL), sent_at DATETIME, recipient_count INT
|
||||
```
|
||||
|
||||
Admin composes a message at `/admin/broadcast`; `broadcast.send` writes one `Notification` row per active user in the targeted roles (`event_type='admin_broadcast'`) plus one `broadcasts` audit row. iOS picks the notifications up on its existing `GET /api/v1/notifications?since=` poll — no APNs/FCM required.
|
||||
|
||||
### ~~DeviceRegistration~~ (phase30 — REMOVED, see rule 84)
|
||||
|
||||
The `DeviceRegistration` model and the duplicate `api_devices` blueprint were **deleted** (rule 84 resolution). Device tracking is consolidated on `DeviceToken` / `api_device_tokens` via the canonical `POST /api/v1/devices/register` handler in `app/api/auth.py`. The `device_registrations` **table is dropped** by phase31/32 on existing tenants; the baseline (`0003_add_user_active`) still creates it, so freshly-bootstrapped tenants carry a harmless empty orphan table that nothing reads or writes.
|
||||
|
||||
---
|
||||
|
||||
## 6. Role & Permission Matrix
|
||||
@@ -457,8 +473,10 @@ api_device_tokens: id, user_id, device_id, apns_token, device_name, app_version
|
||||
| `reports` | `/reports` | index, facility report, scorecard, CSV/PDF/Excel export, issues-aging, sla-compliance, followup-closure, facility summary PDF |
|
||||
| `scheduled_reports` | `/scheduled-reports` | CRUD + manual trigger (accessible via Reports sub-nav) |
|
||||
| `support` | `/support` | `GET /chat`, `POST /chat/message` (AJAX→Groq), `POST /tickets`, `GET /my-tickets`, `GET/POST /my-tickets/<id>`, `GET /admin/tickets`, `GET/POST /admin/tickets/<id>` |
|
||||
| `broadcast` | `/admin/broadcast` | `GET /` (compose + history), `POST /send` — admin-only push to iOS via Notification rows (phase29) |
|
||||
| `devices` | `/admin/devices` | `GET /` (registered device list), `POST /notify` — notify users on outdated app versions (reads `api_device_tokens`) |
|
||||
| `api` | `/api/v1` | parent blueprint |
|
||||
| `api_auth` | `/api/v1` | `/auth/login`, `/auth/refresh`, `/auth/logout`, `/auth/me`, `/devices/register` |
|
||||
| `api_auth` | `/api/v1` | `/auth/login`, `/auth/refresh`, `/auth/logout`, `/auth/me`, `/devices/register` (device tracking + APNs token → `api_device_tokens`) |
|
||||
| `api_facilities` | `/api/v1` | `/facilities`, `/facilities/<id>/areas` |
|
||||
| `api_templates` | `/api/v1` | `/templates`, `/templates/<id>` |
|
||||
| `api_inspections` | `/api/v1` | `GET /inspections`, `POST /inspections`, `PATCH /inspections/<id>` |
|
||||
@@ -677,9 +695,9 @@ EVENT_SCORE_ALERT = 'score_alert' ← Phase 27
|
||||
| critical | 4h | 3h |
|
||||
| high | 24h | 18h |
|
||||
| medium | 72h | 54h |
|
||||
| low | 168h | 126h |
|
||||
| low | 120h | 90h |
|
||||
|
||||
`issue.sla_notified` prevents duplicate cron notifications.
|
||||
At-risk is computed as `AT_RISK_THRESHOLD` (0.75) × the window in `app/utils/sla.py` — it is not a stored constant. Source of truth is `SLA_HOURS` in [app/utils/sla.py](app/utils/sla.py) (`critical=4, high=24, medium=72, low=120`). `issue.sla_notified` prevents duplicate cron notifications.
|
||||
|
||||
---
|
||||
|
||||
@@ -744,10 +762,30 @@ limiter = Limiter(
|
||||
→ phase25_inspection_gps
|
||||
→ phase26_issue_vendor
|
||||
→ phase27_score_alerts
|
||||
→ ... → phase32_device_token_columns
|
||||
→ phase33_tenant_settings ← HEAD
|
||||
→ phase28_fix_inspection_notify
|
||||
→ phase29_broadcasts
|
||||
→ phase30_device_registry
|
||||
→ phase31_device_registry
|
||||
→ phase32_device_token_columns
|
||||
→ phase33_tenant_settings ← HEAD
|
||||
```
|
||||
|
||||
### phase28_fix_inspection_notify
|
||||
|
||||
Data-only. Resets `notification_matrix` rows for `('inspection_completed', role)` where role ∈ `('admin','director','customer')` back to `enabled=1` (they had been inadvertently disabled). No schema change; `UPDATE` on missing rows is a no-op. No `downgrade`.
|
||||
|
||||
### phase29_broadcasts
|
||||
|
||||
Creates the `broadcasts` table (admin-composed push messages to iOS). Uses `CREATE TABLE IF NOT EXISTS` — safe to re-run. See §5 "Broadcast" model and the `broadcast` blueprint.
|
||||
|
||||
### phase30/31/32 — device registry (⚠ inconsistent, see rule 84)
|
||||
|
||||
- **phase30** created a `device_registrations` table.
|
||||
- **phase31** reversed course: added `ios_version` + `last_seen_at` columns to the existing `api_device_tokens` table (phase7) and **dropped** `device_registrations`. Its `ADD COLUMN IF NOT EXISTS` never actually executed on the LT box (MySQL recorded the revision without applying the DDL).
|
||||
- **phase32** re-applies the `api_device_tokens` column adds using proper `INFORMATION_SCHEMA` existence checks, and again drops `device_registrations` if present.
|
||||
|
||||
**Net end-state:** device tracking lives on `api_device_tokens` (model `DeviceToken`), which the admin Devices page reads and the single `POST /api/v1/devices/register` handler (`app/api/auth.py`) writes. The `device_registrations` table / `DeviceRegistration` model was removed in the rule 84 resolution; the baseline still creates the (now-unused) table, so fresh tenants carry a harmless empty orphan. See rule 84.
|
||||
|
||||
### Fresh DB provisioning (multi-tenant)
|
||||
|
||||
**Never use `flask db upgrade` on an empty database.** Fourteen of the phase migrations are not idempotent (no `INFORMATION_SCHEMA` guards) and will fail on a fresh DB that already has the baseline schema. Use the provisioner instead:
|
||||
@@ -1196,6 +1234,7 @@ set -a; . /etc/jqc/control.env; set +a
|
||||
| 74 | **`bootstrap_tenant()` for fresh DBs; `upgrade_tenant()` for incremental** | Fourteen phase migrations are unguarded. Running the full chain on an empty DB (from the baseline) causes duplicate-column errors. `bootstrap_tenant()` runs the baseline to HEAD then stamps — phases skipped. `upgrade_tenant()` is for phase33+ incremental upgrades on already-provisioned DBs. |
|
||||
| 75 | **`delete_tenant()` + `_add_domains()` are retry-safe** | `_add_domains` uses `_upsert_domain()` (delete-then-insert) to handle orphan rows from failed partial runs. `delete_tenant()` also purges by derived domain string, not only by `tenant_id`, catching orphans whose parent tenant row was rolled back. |
|
||||
| 76 | **`register-tenant-zero` never bootstraps and never drops the DB** | `register_tenant_zero()` only reads the existing head, inserts control rows, and maps domains. `delete_tenant()` on tenant-zero must never use `--drop-db` — the guard checks `db_name == db_name_for(slug)` and refuses non-provisioner-named DBs (LT's DB name is `jqc_lt`, not `jqc_lts`). |
|
||||
| 84 | **Device registration is consolidated on `DeviceToken` / `api_device_tokens` — one handler only** | RESOLVED. There is exactly one `POST /api/v1/devices/register`, in `app/api/auth.py` (blueprint `api_auth`); it upserts `DeviceToken` (device_id, device_name, app_version, ios_version, apns_token, last_seen_at) which the admin Devices page reads. The former duplicate `api_devices` blueprint (`app/api/devices.py`) and the orphaned `DeviceRegistration` model / `device_registrations` table were **deleted** — that path wrote to a table phase31/32 drop. Do not reintroduce a second `/devices/register` route or a `device_registrations`-backed model. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user