Commit Graph
10 Commits
Author SHA1 Message Date
root b7f36ac3b1 no-mistakes(review): Harden tech-performance tests, null comparator, and pending label 2026-09-10 04:07:31 +00:00
root 43800345c1 feat(dashboard): technician performance report + real technician names
Wahab customer request (relay #748 v3 next-wave).

3a. Fix 'Tech #N' display:
- FM dashboard's "Technician Workload" card was built from
  `Tech #${t.assigned_to}`; it now reads Ticket.assigned_technician_name
  (falling back to 'Unassigned', matching /tickets).
- Template sweep confirms no other `Tech #` placeholder exists.

3b. Technician performance dashboard:
- New bearer-gated GET /api/tickets/tech-performance aggregating existing
  ticket columns only (no schema change): per-technician total assigned,
  completed, in progress, escalated, cancelled, pending, open tasks,
  completion rate, and created_at -> closed_at resolution time with explicit
  counts for finished tasks lacking a timestamp. Rows key on user id so
  same-name technicians stay separate; labels are real names. Fleet totals
  and an unassigned-ticket count are included.
- New /dashboard/tech-performance page (FM + CEO nav and links) with sortable
  table, completion bars and empty states; same-origin vendored assets only.
- Bucket map, service, schemas and tests in app/services/ticket.py,
  app/schemas/ticket.py, tests/test_tech_performance.py.

Tests: 111 passed (101 existing + 10 new).
2026-09-10 04:00:19 +00:00
root a6eb799efa feat(whatsapp,branding): WhatsApp demo-number wiring + Denya logo assets
WhatsApp demo path (relay #748):
- WHATSAPP_DEMO_TO config under the WhatsApp section (env-based, .env-only;
  .env.example keeps an empty placeholder; real numbers never enter source).
- build_demo_webhook_payload() in app/routers/whatsapp.py builds the Meta
  demo payload from it (fails closed when unset), so the webhook round trip
  logs from_number = demo number (surfaces in GET /api/whatsapp/mock-log) and
  the auto-reply targets the same number.
- tests/test_whatsapp_demo_number.py: default empty + never committed in
  tracked files, payload builder from/to, 200/403/401 gates unchanged.

Branding (logo-assets-v1, sha256-verified, same-origin app/static/branding):
- Login header uses h96 full lockup; logged-in topbar (base.html) uses h48 on
  a light chip (logo ink is ~2:1 vs the dark nav); favicons 32x32 + 16x16 in
  <head>. img-src 'self' data: blob: already allows /static/branding/*.
- tests/test_branding_assets.py: page placement + same-origin serving + CSP.
- AGENTS.md synced.
2026-09-09 19:32:21 +00:00
root 40f1c0ecf6 fix(csp): add 'unsafe-eval' to script-src so Alpine.js initializes
Alpine 3.17.2's CDN build compiles every x-data/x-show/x-text expression
with new Function(), which the strict P0 CSP (script-src 'self'
'unsafe-inline') blocked. Every Alpine directive threw "Evaluating a
string as JavaScript violates ... 'unsafe-eval' is not an allowed
source", Alpine never initialized, and the loading overlay
(x-show="loading" in base.html) stayed visible forever on /login and
every Alpine-driven page.

Add 'unsafe-eval' to script-src (Alpine's documented CSP requirement for
its runtime); everything else in the header is unchanged. Regression test
asserts the /login CSP header carries 'unsafe-eval' inside script-src.

Verified live: headless chromium (playwright build 1243) shows zero
CSP/eval console errors after the fix, with Alpine applying
style="display:none" to the loading overlay; the pre-fix header produces
the Alpine Expression Error spam and leaves the overlay visible.
2026-09-09 15:45:52 +00:00
root 0d79a582f6 no-mistakes(document): drop stale hand-copied test count from HARDENING.md 2026-09-09 13:06:19 +00:00
root 4e8b96ed0a fix(whatsapp,roles): self-heal legacy whatsapp_log schema; map underscore role aliases
CT115 (Mumuni relay #747) — two live-instance defects after PR #12:

1. GET /api/whatsapp/mock-log 500'd with a valid admin token:
   'no such column: whatsapp_log.message_text'. The model gained
   message_text/wa_message_id/ticket_number (and dropped command) in
   4afdc36 with no migration, so legacy DBs keep the (command, ...) shape.
   ensure_legacy_schema (startup, app/main.py) now adds the three missing
   columns idempotently and backfills legacy command bodies into
   message_text before dropping the obsolete NOT NULL command column, so
   both the mock-log read path and the ORM write path work on healed DBs.
   New producer/consumer regression (tests/test_whatsapp_log_legacy_heal.py)
   reproduces the exact OperationalError, then asserts 200 + data.

2. ROLE_ALIASES gap: underscore legacy roles (cs_rep, cs_manager,
   fm_dispatcher) were not mapped, so normalize_legacy_user_roles could not
   converge rows like user 18 (test@denya.com, role 'cs_rep') and the
   frontend stranded them on /tickets. Added the underscore aliases; tests
   assert normalize_role('cs_rep') == 'CS Rep' and a cs_rep row converges
   and authenticates.
2026-09-09 12:52:07 +00:00
root 49b26926cf no-mistakes(document): Align vendor asset upgrade naming with committed files 2026-09-09 01:44:56 +00:00
root e627f50f66 no-mistakes(document): Document P0 auth batch; reconcile HARDENING statuses; lint fixes 2026-09-08 12:48:47 +00:00
root f1b68dd1b7 no-mistakes(review): Normalize legacy emails to prevent case-based login lockout 2026-09-08 12:33:01 +00:00
root 3ae1062d65 P0 security batch: admin-only user mgmt, unified role model, login rate limiting, webhook secret, security headers, pagination caps
- Remove POST /api/auth/register (404); no sign-up UI; users are admin-managed
- Add admin-only POST/PATCH/DELETE /api/auth/users (forced canonical roles,
  self-lockout + reference guards)
- Unify role model in app/core/roles.py; reject unknown roles at creation and
  at login/JWT validation; startup normalizes unambiguous legacy aliases
- Login rate limiting ~5 fails/15 min per IP+email -> 429 (in-process, tunable)
- WhatsApp webhook requires X-Webhook-Secret; fail-closed when env unset;
  GET handshake uses constant-time verify token (403 on mismatch)
- GET /api/whatsapp/mock-log now requires auth
- Security headers middleware: X-Frame-Options DENY, nosniff, CSP on HTML,
  HSTS behind TLS
- Pagination: limit alias for page_size, hard cap enforced, both -> 422
2026-09-08 10:36:16 +00:00