July 6 - 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)
|
||||
> **Last reviewed:** July 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 + Phase 28 inspection-notify fix + Phase 29 admin broadcasts + Phases 30–32 device registry consolidation + ProxyFix reverse-proxy fix)
|
||||
|
||||
---
|
||||
|
||||
@@ -133,8 +133,13 @@ lt_janitorial_quality_control/
|
||||
│ └── utils/
|
||||
├── migrations/
|
||||
│ └── versions/
|
||||
│ └── phase23_support_tickets.py ← HEAD
|
||||
│ └── phase32_device_token_columns.py ← HEAD
|
||||
└── ...
|
||||
|
||||
Note: `app/routes/broadcast.py` + `app/models/broadcast.py` (admin broadcasts) and
|
||||
`app/routes/devices.py` (admin device registry, reads `api_device_tokens`) are also
|
||||
part of the tree — see §7. Device registration on the API side lives in
|
||||
`app/api/auth.py` only (there is no `app/api/devices.py`).
|
||||
```
|
||||
|
||||
---
|
||||
@@ -317,10 +322,22 @@ 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,
|
||||
ios_version, registered_at, last_seen_at ← ios_version + last_seen_at added phase32
|
||||
UniqueConstraint(user_id, device_id)
|
||||
```
|
||||
|
||||
`api_device_tokens` is the **single** source of truth for device tracking. It is upserted by `POST /api/v1/devices/register` (in `app/api/auth.py`) on every app foreground and read by the admin Devices page (`/admin/devices`). The earlier `device_registrations` table / `DeviceRegistration` model was removed — see §17 phase30–32.
|
||||
|
||||
### Broadcast
|
||||
|
||||
```
|
||||
broadcasts: id, title VARCHAR(255), body TEXT, target_roles (JSON list of role strings),
|
||||
sent_by_id (FK→users SET NULL), sent_at DATETIME, recipient_count INT
|
||||
```
|
||||
|
||||
Admin-authored broadcast messages. Sending a broadcast fans out one `Notification` row per targeted user; the iPad picks them up through its existing `GET /api/v1/notifications?since=...` poll — **no dedicated broadcast API endpoint exists**. `recipient_count` snapshots how many notifications were created. Managed at `/admin/broadcast` (see §7 `broadcast` blueprint).
|
||||
|
||||
---
|
||||
|
||||
## 6. Role & Permission Matrix
|
||||
@@ -374,6 +391,8 @@ 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; fans out one Notification per targeted user) |
|
||||
| `devices` | `/admin/devices` | `GET /` (device list from `api_device_tokens`), `POST /notify` (admin-only) |
|
||||
| `api` | `/api/v1` | parent blueprint |
|
||||
| `api_auth` | `/api/v1` | `/auth/login`, `/auth/refresh`, `/auth/logout`, `/auth/me`, `/devices/register` |
|
||||
| `api_facilities` | `/api/v1` | `/facilities`, `/facilities/<id>/areas` |
|
||||
@@ -649,7 +668,12 @@ phase1_projects_roles → phase6_features → phase7_mobile_api → phase8_notif
|
||||
→ phase24_notify_defaults
|
||||
→ phase25_inspection_gps
|
||||
→ phase26_issue_vendor
|
||||
→ phase27_score_alerts ← HEAD
|
||||
→ phase27_score_alerts
|
||||
→ phase28_fix_inspection_notify
|
||||
→ phase29_broadcasts
|
||||
→ phase30_device_registry
|
||||
→ phase31_device_registry
|
||||
→ phase32_device_token_columns ← HEAD
|
||||
```
|
||||
|
||||
### phase21_performance_indexes
|
||||
@@ -706,7 +730,25 @@ Displayed in `issues/view.html` and editable via `IssueForm` (`form.html`). Staf
|
||||
|
||||
Creates `facility_score_alerts` table. Used by `send_score_alerts()` in `sla.py` for 24-hour deduplication of score-drop notifications. Uses table existence check — safe to re-run.
|
||||
|
||||
**Deploy order for phases 24–27:**
|
||||
### phase28_fix_inspection_notify
|
||||
|
||||
Data-only migration. Resets the `notification_matrix` rows for `('inspection_completed', 'director')`, `('inspection_completed', 'admin')`, and `('inspection_completed', 'customer')` to `enabled=True`, matching `MATRIX_DEFAULTS`. These had been inadvertently disabled (likely via an accidental checkbox save on the matrix page), suppressing inspection-completed notifications from both web and mobile API. No schema change.
|
||||
|
||||
### phase29_broadcasts
|
||||
|
||||
Creates the `broadcasts` table backing the admin broadcast feature (see §5 `Broadcast` and §7 `broadcast` blueprint). Uses table existence check — safe to re-run.
|
||||
|
||||
### phase30–32_device_registry (consolidation — read as a unit)
|
||||
|
||||
These three migrations are the history of a **false start** in device tracking. Net effect after all three: the app tracks devices exclusively in **`api_device_tokens`** (`DeviceToken` model); the short-lived `device_registrations` table and its `DeviceRegistration` model no longer exist.
|
||||
|
||||
- **phase30_device_registry** — created a separate `device_registrations` table (the abandoned approach).
|
||||
- **phase31_device_registry** — drops `device_registrations` (`DROP TABLE IF EXISTS`).
|
||||
- **phase32_device_token_columns** — the live path. Adds `ios_version VARCHAR(20)` and `last_seen_at DATETIME` to `api_device_tokens` (phase31's ALTERs were recorded-but-never-executed, so phase32 re-applies them via `INFORMATION_SCHEMA` existence checks) and drops the orphaned `device_registrations` table if still present. Safe to re-run.
|
||||
|
||||
**The dead `DeviceRegistration` model, `app/api/devices.py` endpoint, and `api_devices` blueprint were removed (July 2026).** They defined a *second* `POST /api/v1/devices/register` that was shadowed at routing time by the `api_auth` copy and would have crashed anyway (it queried the dropped `device_registrations` table). Device registration now has a single implementation: `register_device()` in `app/api/auth.py`, writing to `api_device_tokens`. Do not reintroduce a competing device model or a duplicate register route.
|
||||
|
||||
**Deploy order for phases 24–32:**
|
||||
```bash
|
||||
flask db upgrade
|
||||
sudo systemctl restart gunicorn
|
||||
@@ -946,6 +988,10 @@ timeout = 30
|
||||
- `client_max_body_size 50M`
|
||||
- Passes `X-Forwarded-For`
|
||||
|
||||
### ProxyFix (reverse-proxy awareness)
|
||||
|
||||
`create_app()` wraps `app.wsgi_app` in `werkzeug.middleware.proxy_fix.ProxyFix(x_for=1, x_proto=1, x_host=1)`. Nginx terminates TLS and forwards over loopback, so without this every request's `remote_addr` is `127.0.0.1`. That would collapse all Flask-Limiter keys into one shared bucket (rate limits become global instead of per-client) and make `url_for(_external=True)` emit `http://` links. `x_for=1` trusts exactly one proxy hop (our own Nginx) — do not increase it unless another trusted proxy is added in front, or clients can spoof `X-Forwarded-For` and defeat rate limiting.
|
||||
|
||||
### Recommended Cron Schedule
|
||||
```bash
|
||||
0 7 * * * curl -s -X POST https://your-domain.com/notifications/send-digest \
|
||||
@@ -1026,6 +1072,8 @@ timeout = 30
|
||||
| 68 | **Support ticket customer replies revert status from `answered` → `open`** | When a customer posts a follow-up on an answered ticket, the route sets `ticket.status = 'open'` so admins see it in their open queue. Admin must manually close or re-answer. |
|
||||
| 69 | **Customer issue create: `assigned_to` field hidden, `facility_id` scoped to `get_customer_scope()`** | `issues.create()` detects `role == 'customer'`, scopes facilities to the customer's assigned set, sets `staff = []` for the assigned_to dropdown, and hides the field in `form.html`. `IssueForm.facility_id.choices` must still include all active facilities so POST validation passes. |
|
||||
| 70 | **`notify()` does NOT commit — caller must `db.session.commit()` after all `notify()` calls** | `notify()` adds a `Notification` row to the session but leaves the commit to the caller. The support helpers (`_notify_admins_new_ticket`, `_notify_customer_reply`, `_notify_admins_customer_reply`) each call `db.session.commit()` after the `notify()` loop. |
|
||||
| 71 | **`ProxyFix` must wrap `app.wsgi_app` in `create_app()`** | Behind Nginx, `remote_addr` is `127.0.0.1` for every request without it, collapsing all Flask-Limiter keys into one bucket (global instead of per-client rate limiting). `x_for=1` trusts exactly one proxy hop. See §19. |
|
||||
| 72 | **Device registration has exactly ONE implementation — `register_device()` in `app/api/auth.py` → `api_device_tokens`** | A second `POST /api/v1/devices/register` (`app/api/devices.py` + `DeviceRegistration` model) was removed July 2026. It was shadowed by the `api_auth` route at routing time and queried the dropped `device_registrations` table. Do not reintroduce a competing device model or duplicate register route. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user