CC4 sub1: add missing v2 listing/discovery endpoints + fix kategorizirani SQL

- GET /api/v2/klubovi (v2 alias for /api/klubovi listing)
- GET /api/v2/savezi  (v2 alias for /api/savezi listing)
- GET /api/v2/sport[/] (namespace discovery index)
- FIX /api/v2/kategorizirani/list (column alias used in WHERE -> 500 -> 200)

Source audit: _audit/audit_20260505_023639/errors.json
Smoke (anon+auth+public): 200 OK on all five endpoints.
Backup: _backups/r3_cc4/pgz_sport_v2_router.py.bak.1777962063
This commit is contained in:
CC4-Sub1
2026-05-05 08:23:28 +02:00
parent 4fc8327789
commit eb1b49f0db
2 changed files with 146 additions and 1 deletions
+75 -1
View File
@@ -3087,7 +3087,7 @@ import json
# ═══ HOO KATEGORIZIRANI SPORTAŠI ═══
@router.get("/kategorizirani/list")
def list_kategorizirani(kategorija: Optional[str] = None, sport: Optional[str] = None):
where = ["c.kategorija_hoo AS hoo_kategorija IS NOT NULL"]; params = []
where = ["c.hoo_kategorija IS NOT NULL"]; params = []
if kategorija: where.append("c.hoo_kategorija=%s"); params.append(kategorija)
if sport: where.append("LOWER(c.sport)=LOWER(%s)"); params.append(sport)
sql = f"""SELECT c.id, c.ime, c.prezime, c.hoo_kategorija, c.sport,
@@ -5348,3 +5348,77 @@ def graph_3d_iframe(min_orgs: int = 2, top_n: int = 100, sport: str = ""):
return f.read()
return "<h1>3D iframe not found</h1>"
# ═══════════════════════════════════════════════════════════════════════════
# CC4-Sub1 (05.05.2026) — fill audit gaps: /api/v2/{klubovi,savezi,sport/}
# Author: cc4-sub1@rinet.one
# ═══════════════════════════════════════════════════════════════════════════
@router.get("/klubovi")
def v2_klubovi_list(q: Optional[str] = None, savez_id: Optional[int] = None,
sport: Optional[str] = None, grad: Optional[str] = None,
limit: int = 500):
"""v2 alias for /api/klubovi — minimal listing for portal/CRM panels."""
where = ["aktivan"]
params: List[Any] = []
if q:
where.append("(naziv ILIKE %s OR oib ILIKE %s OR sport ILIKE %s)")
params.extend([f"%{q}%", f"%{q}%", f"%{q}%"])
if savez_id:
where.append("savez_id=%s"); params.append(savez_id)
if sport:
where.append("sport ILIKE %s"); params.append(f"%{sport}%")
if grad:
where.append("grad ILIKE %s"); params.append(f"%{grad}%")
params.append(max(1, min(limit, 2000)))
sql = f"""SELECT id, naziv, oib, sport, grad, savez_id,
region, broj_clanova, predsjednik, email, telefon, web
FROM pgz_sport.klubovi
WHERE {' AND '.join(where)}
ORDER BY naziv COLLATE "hr-HR-x-icu"
LIMIT %s"""
rows = db_query(sql, params)
return {"ok": True, "count": len(rows), "rows": rows}
@router.get("/savezi")
def v2_savezi_list(q: Optional[str] = None, razina: Optional[str] = None,
sport: Optional[str] = None, limit: int = 500):
"""v2 alias for /api/savezi — minimal listing."""
where = ["aktivan"]
params: List[Any] = []
if q:
where.append("(naziv ILIKE %s OR sport ILIKE %s)")
params.extend([f"%{q}%", f"%{q}%"])
if razina:
where.append("razina = %s"); params.append(razina)
if sport:
where.append("sport ILIKE %s"); params.append(f"%{sport}%")
params.append(max(1, min(limit, 2000)))
sql = f"""SELECT id, naziv, sport, razina, sjediste_zupanija,
godina_osnutka, predsjednik, email, web,
(SELECT COUNT(*) FROM pgz_sport.klubovi WHERE savez_id=s.id) AS broj_klubova
FROM pgz_sport.savezi s
WHERE {' AND '.join(where)}
ORDER BY naziv COLLATE "hr-HR-x-icu"
LIMIT %s"""
rows = db_query(sql, params)
return {"ok": True, "count": len(rows), "rows": rows}
@router.get("/sport")
@router.get("/sport/")
def v2_sport_index():
"""v2 sport index — discovery endpoint listing sport-related sub-routes."""
return {
"ok": True,
"service": "pgz_sport v2 sport namespace",
"endpoints": {
"GET /api/v2/sport/svi/stats": "all-sports aggregate stats",
"GET /api/v2/sport/{sport_naziv}/pregled": "drill-down per-sport view",
"POST /api/v2/sport/ask": "RAG sport agent (Q&A)",
"POST /api/v2/sport/lawyer": "RAG legal/regulation agent",
"GET /api/v2/sport/objekti": "sport facilities listing",
},
}