- Frontend (sport2.html): refreshDashNositelji() koristi /api/dashboard/top-primatelji
umjesto /v2/potpore/by-year (koji je za 2025 vraćao samo 1 agregirani redak).
Dropdown proširen na "Sve godine" + 2021..2026. Dodana kolona "Platitelj".
- Backend (pgz_sport_api.py): top-primatelji endpoint sada parsira napomena
'doc_id=N' i JOIN-a pgz_sport.dokumenti za pdf_url; godina<=0 → sve godine;
dodane kolone vrsta + pdf_url + doc_title.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1) HNS direct link u research_links: za sportaš s profile_url/source_url
(npr. https://semafor.hns.family/igraci/X/...) generira [⭐DIRECT] link na vrhu liste,
umjesto generic Google search. _research_links sada prima row dict.
2) Avatar cache buster: applyMeToHeader dodaje ?t=Date.now() na sve avatar img tagove.
Avatar upload handler dodatno persistira novi avatar_url u localStorage.pgz_user
tako da preživi page refresh + cross-page navigacije.
3) Logo home link: <div class='logo'> → <a href='/' class='logo'> u app.html i sport2.html.
Klik na PGŽ SPORT logo vodi na public portal.
4) Klub → Sportaši drill-down: u klub Info tabu dodan button
'👥 Vidi sportaše ovog kluba (N)' koji prebacuje na k-clan tab.
Plus '🌐 Službena stranica' link kad klub ima web.
5) Smarter klub enrichment:
- URL validacija (skip placeholder strings poput 'godisnjak_zspgz_2025')
- Domain candidate guesser (slug → 16 candidate URLs s common HR TLD-ovima i sport prefix-ima)
- Parallel HEAD probe (8 threads, 10s budget) — first 200 + name token match wins
- Subpage scrape (/kontakt, /uprava, /o-nama, /o-klubu, /predsjednik) za richer evidence
- HNK Orijent (id 3766) test: pogađa https://www.orijent.hr/, predlaže web+email+telefon+opis
E2E verified:
- 9/9 sidebar URL-ova → 200
- /users/me/gdpr-export → 200 (28KB JSON)
- /users/me/request-deletion → 200 (DB row pgz_sport.gdpr_erasure_requests)
- /enrich/klub/3766 → 4 proposed fields (web, email, telefon, opis)
- HNS sportaš research_links: ⭐ HNS profil DIRECT link na vrhu
Backend: routers/enrich_router.py
Frontend: static/app.html, static/sport2.html
Backups: _backups/sprint_1777940670/
Tag: R7-demo-ready
Backend (already in master via parallel commits):
- enrich_router enrich_apply now returns
{status, applied_count, applied_fields, applied, after} so the toast
doesn't have to count manually.
- POST /api/v2/enrich/bulk runs preview+apply on N random under-enriched
rows of one kind, returns aggregate {processed, fields_total, items}.
- Worker dashboard: GET /api/v2/enrich/worker/status (heartbeat,
paused, last_cycle, confidence_threshold, fields_24h, recent),
POST /worker/pause, /worker/run-now, /worker/confidence.
- enrichment_worker honours Redis flags: cc:pgz-enricher:pause,
:run_now, :confidence (live override). Heartbeat + last_cycle JSON +
rolling fields_24h counter all written to Redis.
- pgz_sport_api JWT middleware now whitelists /api/v2/enrich/* under
_PUBLIC_MUTATING_PREFIXES so the demo UI works without a bearer.
Frontend (this commit):
- Reusable toast(msg, type, duration) helper with success/error/info/warn,
slide-up animation, auto-dismiss.
- Diff modal now has explicit ❌ Odustani + 💾 SPREMI IZMJENE buttons;
enrichApply consumes applied_count + applied_fields and surfaces them
in a multi-line success toast.
- '✨ Obogati sve (50)' button in klubovi + sportasi list toolbars; calls
enrichBulk() which posts to /v2/enrich/bulk with confirm dialog and
reload-on-success.
- Audit page renders a live Enrichment Worker card: heartbeat badge,
last-cycle stats, fields-24h, recent enrich-write rows, Pauziraj/
Nastavi/Pokreni-odmah buttons, confidence slider 0..1. Auto-refreshes
every 10s while the audit page is open.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#1 JWT middleware extended:
- Was: /api/admin/* only
- Now: any POST/PUT/PATCH/DELETE under /api/* requires Bearer JWT
- Whitelist (still anonymous): /api/auth/login, /refresh, /forgot-password,
/password/reset, /reset-password, /setup-password, /google;
/api/gdpr/consent; any path ending /avatar
- 14 mutating endpoints verified to return 401 without token
#2 Avatar upload demo mode (routers/clan_panel_router.py):
- Anonymous → returns {demo_mode:true, slika_url:null,
message:'Demo mode — slika nije spremljena. Prijavite se za pravu pohranu.'},
no FS write, no DB write
- Authenticated (valid JWT, allowed role) → real save as before
- Auth check now uses auth.auth_v2.decode_token (proper secret + revocation)
instead of the broken local _resolve_role
#3 Mock mailer (auth/mailer.py):
- send_email writes RFC 822 .eml to /tmp/pgz_mailbox + appends to INDEX.jsonl
- send_password_reset, send_invite helpers with HR text + HTML alt
- Real SMTP active when PGZ_SMTP_HOST is set (env-driven, off by default)
- forgot-password and admin invite both call mailer; audit logs mail status
#5 Rate limiting on /api/auth/login:
- Per-user: 5 wrong attempts → 5-minute DB-backed lockout
(was 5 → 15 min). Configurable via PGZ_LOGIN_LOCK_THRESHOLD/MINUTES.
- Per-IP: 10 fails / 5-min sliding window in-memory → HTTP 429
Configurable via PGZ_LOGIN_IP_THRESHOLD/WINDOW_SEC. Successful
login clears the IP counter.
- Failed attempts respond '(N/5) — račun je zaključan na 5 minuta'
- New audit actions: login.ratelimit.ip; login.fail meta now
includes fails count, locked, lock_minutes
#4 Live test report: 46/46 across 6 demo users — login, JWT gate on 14
mutating endpoints, public path whitelist, demo-mode avatar +
real save, forgot-password e-mail to mailbox, no-leak unknown email,
5-fail lockout, 423 during lockout, audit coverage.
- routers/audit_seal_router.py exposes:
POST /api/audit/seal (record + seal an audit event)
GET /api/audit/seal/list (recent seals for UI)
GET /api/audit/seal/{id} (single seal + onchain receipt cross-check)
- pgz_sport_api.py mounts the router under /api.
- sport2.html: new 'Audit log' nav item (🔒) and full page that surfaces
wallet, chain, live/pending mode, count, and a table of every sealed
event with polygonscan.com tx links.
- Verified end-to-end: sealing 'sufinanciranje.approved' for klub 3 lands
in pgz_sport.polygon_seals (pending mode — no POLYGON_PRIVKEY in env).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- enrichEntity() now renders {current, proposed} as a diff table with a
checkbox per field (defaults to checked).
- 'Označi sve' / 'Poništi sve' / '💾 Spremi izmjene' buttons.
- enrichApply() POSTs selected fields to /v2/enrich/{kind}/{id}/apply
with the cached source list, then refreshes the entity panel and
re-runs preview so the now-saved values are visible inline.
- Toast '✓ Spremljeno N polja u bazu' confirms the write.
- '✓ Obogaćeno YYYY-MM-DD' badge surfaces metadata.enriched_at.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
M1 (default centar):
- Augment /api/v1/presenter/graph-real with synthetic 'pgz-savez-nogometni' anchor
(PGŽ gold, size 40), connected to top 3 person + top 3 entity nodes
- centerMrezaOnAnchor() called 1.5s after render and via "🎯 Centar (PGŽ)" button
M2 (autocomplete):
- Backend GET /api/v2/search/suggest?q=&type=person|club|company
Searches pgz_sport.klubovi, pgz_sport.savezi, pgz_sport.clanovi,
civic.persons, civic.entities; returns 20 results max
- Frontend: 3 inputs get keydown+input handlers, dropdown UI under each
Enter → first suggestion, click → suggestion, blur → close
- centerMrezaOnSuggestion: finds existing node by label, or injects new node
+ edge from anchor and re-renders
M3 (forensic enrich):
- Backend POST /api/v2/forensic/findings/{id}/enrich
Extract person name from entities_involved or title regex,
hit hr.wikipedia.org REST summary, persist into raw_data.enrichment
- Frontend: forensicEnrichBlock + customFindingEnrichBlock added to alert
panel and custom-finding panel (Liverić). Custom uses direct Wikipedia
fetch since they're not in DB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- geocode_objekti_v2.py + DB updates (Kastav, Rujevica, Platak, Petehovac, Crikvenica, Krk hand-curated)
- Maps URL → /maps/search/?api=1 format for proper pin
- Dashboard: year selector for nositelji, click → klub/PDF panel; top savezi clickable
- Universal sort (asc/desc) on Savezi/Klubovi/Sportaši/Objekti/Manifestacije/Financije
- Card↔Table toggle on Financije
- Manifestacije: source_url direct open, Google fallback
- Forenzika: severity/tip filter, search, run-scan, Liverić PEP custom findings + DB alerts
- Enrich endpoint /api/v2/enrich/{kind}/{id} + button on savez/klub/sportaš panels
- New 'Mreža' section: D3 force graph from /api/v1/presenter/graph-real
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>