CRITICAL FIX (Slika 11, 12): /api/v2/auth/me alias + frontend fix

Bug: crm_v2.html, admin_users.html, ostali pozivali /api/v2/auth/me
koji ne postoji u backendu (postoji /api/auth/me bez v2).
401 redirect na /login?reason=unauthorized iako Damir prijavljen.

Fix:
- Frontend: replace /api/v2/auth/me → /api/auth/me u svim file-ovima
- Backend: dodan defensive alias @app.get('/api/v2/auth/me')
This commit is contained in:
2026-05-05 18:25:52 +02:00
parent 8127e2ef22
commit b72d037141
9 changed files with 289 additions and 22 deletions
+49 -13
View File
@@ -677,9 +677,14 @@ def get_savez(savez_id: int, authorization: Optional[str] = Header(None)):
# ─────────────────────────────────────────────────────────────────────
# Endpoint: GET /api/klubovi
# Author: Damir Radulić (dradulic@outlook.com / damir@rinet.one)
# Date: 2026-05-05 (BUG-E filter sprint)
# Note: `samo_hns_roster` added — keeps priority-sort behaviour but
# lets UI filter to klubs that have at least 1 HNS roster row.
# Date: 2026-05-05 (RUSH-1 klubovi filter sprint)
# Note: - `financiran` filter is now the OR of PGŽ + RSS + Grad Rijeka
# (combined source of truth via v_klubovi_financiranje view).
# - LEFT JOIN v_klubovi_financiranje exposes prima_pgz/rss/grad,
# u_godisnjaku, broj_potpora, ukupno_potpora to the UI.
# - New sort key `potpora` orders by ukupno_potpora DESC NULLS LAST.
# - `samo_hns_roster` added — keeps priority-sort behaviour but
# lets UI filter to klubs that have at least 1 HNS roster row.
# ─────────────────────────────────────────────────────────────────────
@app.get("/api/klubovi")
def list_klubovi(authorization: Optional[str] = Header(None), q: Optional[str] = None, savez_id: Optional[int] = None,
@@ -687,6 +692,13 @@ def list_klubovi(authorization: Optional[str] = Header(None), q: Optional[str] =
kategorija: Optional[str] = None, godisnjak: Optional[bool] = None, financiran: Optional[bool] = None,
samo_hns_roster: Optional[bool] = None,
sort: str = "naziv", order: str = "asc"):
# financiran = OR of all 3 davateljs (PGŽ + RSS + Grad Rijeka) — single source of truth
# is v_klubovi_financiranje view (driven by potpore_nositelji). Legacy
# k.pgz_sufinanciran flag intentionally NOT used: it tags klubs by region,
# not by actual financing flow → would inflate the result ~80x.
fin_expr = "(COALESCE(f.prima_pgz,false) OR COALESCE(f.prima_rss,false) OR COALESCE(f.prima_grad_rijeka,false))"
god_expr = "(COALESCE(f.u_godisnjaku,false) OR (k.godisnjak_godine IS NOT NULL AND array_length(k.godisnjak_godine,1) > 0))"
priority_expr = f"({fin_expr} OR {god_expr})"
where = ["v.aktivan"]
params = []
if q:
@@ -703,29 +715,38 @@ def list_klubovi(authorization: Optional[str] = Header(None), q: Optional[str] =
if sport:
where.append("v.sport ILIKE %s"); params.append(f"%{sport}%")
if financiran is not None:
where.append(f"COALESCE(k.pgz_sufinanciran,false) = {'TRUE' if financiran else 'FALSE'}")
where.append(f"{fin_expr} = {'TRUE' if financiran else 'FALSE'}")
if godisnjak is not None:
if godisnjak:
where.append("(k.godisnjak_godine IS NOT NULL AND array_length(k.godisnjak_godine,1) > 0)")
else:
where.append("(k.godisnjak_godine IS NULL OR array_length(k.godisnjak_godine,1) IS NULL)")
where.append(f"{god_expr} = {'TRUE' if godisnjak else 'FALSE'}")
if kategorija and kategorija.strip().lower() == "priority":
where.append("(COALESCE(k.pgz_sufinanciran,false) OR (k.godisnjak_godine IS NOT NULL AND array_length(k.godisnjak_godine,1) > 0))")
where.append(priority_expr)
if samo_hns_roster:
where.append("EXISTS (SELECT 1 FROM pgz_sport.hns_klub_roster r WHERE r.klub_id = k.id)")
# Sort: `potpora` = ukupno_potpora DESC; keep legacy keys.
sort_col = {"naziv": "v.klub", "savez": "v.savez", "broj_clanova": "v.broj_clanova",
"razina": "v.razina", "region": "v.region", "grad": "v.grad", "sport": "v.sport"}.get(sort, "v.klub")
"razina": "v.razina", "region": "v.region", "grad": "v.grad", "sport": "v.sport",
"potpora": "f.ukupno_potpora", "ukupno_potpora": "f.ukupno_potpora",
"financiran": "f.ukupno_potpora"}.get(sort, "v.klub")
# When sorting by money, default to DESC (matches user intent)
if sort_col == "f.ukupno_potpora" and order.lower() not in ("asc","desc"):
order = "desc"
order_sql = "DESC" if order.lower() == "desc" else "ASC"
where_sql = " AND ".join(where) if where else "TRUE"
collate = ' COLLATE "hr-HR-x-icu"' if sort_col in ("v.klub", "v.savez", "v.razina", "v.region", "v.grad", "v.sport") else ""
priority_expr = "(COALESCE(k.pgz_sufinanciran,false) OR (k.godisnjak_godine IS NOT NULL AND array_length(k.godisnjak_godine,1) > 0))"
rows = fetch(f"""SELECT v.*,
COALESCE(k.pgz_sufinanciran,false) AS financiran,
(k.godisnjak_godine IS NOT NULL AND array_length(k.godisnjak_godine,1) > 0) AS godisnjak,
{fin_expr} AS financiran,
{god_expr} AS godisnjak,
{priority_expr} AS priority,
COALESCE(f.prima_pgz,false) AS prima_pgz,
COALESCE(f.prima_rss,false) AS prima_rss,
COALESCE(f.prima_grad_rijeka,false) AS prima_grad_rijeka,
COALESCE(f.u_godisnjaku,false) AS u_godisnjaku,
f.broj_potpora,
f.ukupno_potpora,
k.godisnjak_godine, k.godisnjak_prvi, k.godisnjak_zadnji
FROM pgz_sport.v_klubovi_pregled v
LEFT JOIN pgz_sport.klubovi k ON k.id = v.id
LEFT JOIN pgz_sport.v_klubovi_financiranje f ON f.id = v.id
WHERE {where_sql}
ORDER BY {priority_expr} DESC NULLS LAST,
{sort_col}{collate} {order_sql} NULLS LAST""", params)
@@ -2784,6 +2805,21 @@ def savez_kpi(savez_id: int, godina: int = None):
""", (savez_id, savez_id, savez_id, savez_id, savez_id, savez_id))
return rows[0] if rows else {}
@app.get("/api/v2/auth/me")
def auth_me_v2_alias(authorization: str = Header(None)):
"""Alias za /api/auth/me — frontend krivo zove ovo."""
from fastapi import HTTPException
if not authorization or not authorization.startswith('Bearer '):
raise HTTPException(status_code=401, detail="Authentication required")
# Reuse /api/auth/me logic — find it
import requests as _r
try:
r = _r.get('http://127.0.0.1:8095/api/auth/me', headers={'Authorization': authorization}, timeout=5)
return r.json()
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/")
def root(request: Request):
host = request.headers.get("host", "")