Damir Radulic
aca5051418
feat: /api/v2/analiza/* endpoints - sport analytics backend
2026-05-16 00:28:12 +02:00
damir
dd2f7daaf8
CRISIS V3: definitive apiAuth + mobile hamburger + Playwright E2E test
...
apiAuth in app.html:
- Pre-checks JWT exp client-side BEFORE making request
- On expired: clears localStorage + redirects /login?reason=expired
- On 401 from server: clears + redirects /login?reason=unauthorized
- Single-flight redirect via window.__pgz_redirecting flag
login.html:
- Toast for ?reason=expired (red) / ?reason=unauthorized (orange)
app.html mobile:
- Hamburger button injected into topbar (.tb)
- Mobile CSS: sidebar slide-in -280→0, backdrop overlay, full-width drill-down
- toggleMobileSidebar() global function
- @media (max-width:768px) display:inline-flex, sidebar fixed pos
scripts/playwright_e2e.py:
- Desktop test (1280x800): login, JWT persist, profile, logo, logout
- Mobile test (375x812 iPhone X): viewport, login flow, hamburger, no h-scroll
- Output: _audit/playwright_<TS>/results.json + screenshots/*.png
Reproducible: TS=YYYYmmdd_HHMM python3 scripts/playwright_e2e.py
2026-05-05 09:21:39 +02:00
damir
8e136351f9
CRISIS FIX: login flow + mobile responsive + token expiry handling
...
ROOT CAUSE ISOLATED:
Backend POST /api/auth/login, GET/PUT /api/auth/me, POST avatar, POST /logout
all return 200 OK (verified curl). Damirov problem is browser-side:
stale localStorage tokens that don't match current backend → 401 cascade
→ avatar upload appears as 'failed: 401' → profile changes 'lost'.
FIXES:
1. apiAuth() in app.html now:
- Pre-checks JWT exp claim before request
- On 401 response: clears localStorage (pgz_access/refresh/user) +
redirects to /login?reason=unauthorized
- On JWT expired: redirects to /login?reason=expired
2. login.html displays toast for ?reason=expired/unauthorized
3. Mobile responsive CSS (max-width: 768px):
- app.html: hamburger menu, sidebar slide-in, full-width drill-down panel
- sport2.html: KPI grid 2-col, klubovi 1-col, tables horizontal scroll
- Both: viewport meta + media queries + touch-friendly buttons
4. Mobile menu toggle button + backdrop overlay added
VERIFIED E2E (curl):
- POST /auth/login → 200 + JWT
- GET /auth/me → 200 + telefon persisted
- PUT /auth/me → 200, DB row updated
- POST /auth/me/avatar → 200, file saved + avatar_url returned
- POST /auth/logout → 200, token revoked (next /me returns 401)
2026-05-05 09:14:46 +02:00
damir
4fc8327789
R7+ orchestrator + CC3 logo home: combined patches
...
Orchestrator-side:
- routers/img_proxy_router.py: 4xx/5xx → 1x1 transparent PNG (eliminates cascade <img onerror>)
- static/sport2.html: removed standalone three.min.js (3d-force-graph bundles), bumped to 1.73.4
CC3 (before limit hit):
- Logo home link applied to ALL HTML pages (admin.html, admin_users.html, audit.html, crm.html, erp.html, kpi.html, login.html)
- Backups in _backups/*.cc3_pre_logo.$ts
CC4 R3 (before plan mode):
- _backups/r3_cc4/ocr.py.pre_S2.$ts
Audit screenshots (80 pages) committed to _audit/audit_20260505_023639/shots/
2026-05-05 08:20:07 +02:00
damir
67372d6c58
R7: GDPR /users/me/request-deletion alias + remove duplicate profileDeleteAccount
...
- auth/gdpr.py: dodan @me_router.post('/request-deletion') alias
koji proxy-a na request_erasure (Art. 17). Koristi pravi EraseReq pydantic.
- static/app.html: obrisana placeholder profileDeleteAccount funkcija
na liniji 944 (M10 mock alert) — sada samo real implementacija na 1902.
- E2E verified: damir@pgz.hr → POST /users/me/request-deletion → 200,
DB row pgz_sport.gdpr_erasure_requests #1 pending.
Tag: P0-demo-fix
2026-05-05 02:06:34 +02:00
Damir Radulić
8dce58c5f9
CC3: Unified sidebar with external portal links + collapsible icon mode
...
Shared module:
- /static/shared/sidebar.css ← unified CSS (#pgz-sb, .pgz-collapsed, mobile overlay, tooltip)
- /static/shared/sidebar.js ← auto-mounting JS shell + PGZSidebar API
* Auto-renders #pgz-sb na <body> start (data-inline=1 to opt out)
* NAV_EXTERNAL: Prijava, Aplikacija, Administracija, CRM, ERP, KPI, Audit, Public portal
* Toggle (≡) -> localStorage 'sidebarCollapsed' (perzistira preko SVIH stranica)
* Mobile <768px: ≡ burger + ✕ close, body backdrop
* Loads /api/auth/me u footer (avatar/username/uloga); ⎋ logout briše JWT i ide na /login
* data-active="<key>" highlight aktivnog portala
Page integration:
- sport2.html ← inline NAV_EXTERNAL u buildNav() + "Portali" separator (zadrži postojeći sidebar)
- app.html ← inline NAV_EXTERNAL u buildNav() (zadrži role-based interni nav, dopuni Portalima)
- admin.html ← Portali stavke u <aside class="sidebar"> (matching .nav-item style)
- erp.html ← Portali stavke u <aside class="sidebar"> (matching .nav-item style)
- crm.html ← include shared sidebar.css + sidebar.js data-active="crm"
- audit.html ← include shared sidebar.css + sidebar.js data-active="audit"
- kpi.html ← include shared sidebar.css + sidebar.js data-active="kpi"
- login.html ← include shared sidebar.css + sidebar.js data-active="login"
Backups: _backups/{*.cc3_pre_unified_sidebar.*}
Live verified: 8 pages serve HTTP 200; sidebar.css/js HTTP 200; portal markers per page OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-05-05 01:11:24 +02:00
Damir Radulić
bd3773434e
CC2 R4 #6 : real TOTP 2FA (setup + verify + disable + login flow)
...
- auth/auth_v2.py:
- pyotp-based TOTP (RFC 6238, base32 secret, ±30s window)
- new pgz_sport.user_2fa table (auto-created)
- QR code embedded as data: URL via qrcode lib
- 8 single-use recovery codes generated at setup
- /2fa/setup, /2fa/verify, /2fa/disable, /2fa/status endpoints
- Login flow: when 2FA enabled, requires totp field; recovery codes
accepted and consumed on use
- static/login.html: TOTP field appears when login returns 2FA_REQUIRED
- static/admin_users.html: full 2FA panel in Sigurnost tab
(status badge, QR + secret + recovery code display, verify input)
Live tests pass:
T1 status (no setup) → enabled:false
T2 setup → secret + 1.5KB QR PNG + 8 recovery codes
T3 verify wrong code → 401
T4 verify real TOTP → enabled:true
T5 login w/o TOTP after enable → 401 detail=2FA_REQUIRED
T6 login w/ TOTP → 200
2026-05-05 00:50:28 +02:00
Damir Radulić
f5c6570d47
CC2 R4 #2+#5: remove legacy unauth /api/admin/users — close 401 gap
...
The bare @app.get/post('/api/admin/users') decorators in pgz_sport_api.py
were registered before app.include_router(admin_users_router) and shadowed
the JWT-protected M2 routes, leaking user list to anyone.
Removed all three: GET /api/admin/users, POST /api/admin/users,
POST /api/admin/users/{uid}/toggle. The auth.admin_users router now owns
this prefix exclusively and gates every method with require_user.
Verified: no-auth → 401, invalid token → 401, valid Bearer → 200.
2026-05-05 00:44:50 +02:00
Damir Radulić
8fe2478b84
CC2 R3 frontend: login.html + admin_users.html (M1+M2+M10 UI)
...
- static/login.html: dark Palantir-style login with PGŽ branding,
Prijava se / Zaboravljena lozinka, demo account quick-fills,
GDPR cookie banner, autostore tokens (local/session)
- static/admin_users.html: full user-management admin panel:
- Collapsible left sidebar (Pregled, Korisnici, Tenanti, Audit log,
Sigurnost, GDPR, links to ERP/CRM)
- Users table with filters (q, tenant, role, status, limit)
- + Dodaj korisnika modal (CRUD via /api/admin/users/*)
- Suspend / unsuspend / reset-password / delete actions
- Audit log viewer + Security KPIs + GDPR queue
- Self-service: change pwd, export data (Art. 20), erasure request (Art. 17)
- pgz_sport_api.py: /login and /admin/users URL routes
- auth/seed_demo.py: added tajnik@atletski.pgz.hr/Atl2026!,
admin@ak-kvarner.hr/Kvarner2026! demo users
5/5 live tests pass: login JWT, /me, /admin/users, /gdpr/consent, /gdpr/export
Note: existing admin.html (CC4 ERP/OCR work) preserved intact;
admin_users.html is dedicated user-mgmt page linked from sidebar.
2026-05-05 00:20:03 +02:00