177 lines
5.5 KiB
Python
177 lines
5.5 KiB
Python
|
|
"""API routes for Ketten (chains)."""
|
||
|
|
|
||
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
|
|
|
||
|
|
from tracker.api.models import (
|
||
|
|
KetteDetail,
|
||
|
|
KetteKurz,
|
||
|
|
KettenGliedOut,
|
||
|
|
PaginatedKetten,
|
||
|
|
ParteiOut,
|
||
|
|
VorlageKurz,
|
||
|
|
)
|
||
|
|
from tracker.core.graph import get_kette_graph
|
||
|
|
from tracker.db.session import get_connection
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/ketten", tags=["Ketten"])
|
||
|
|
|
||
|
|
|
||
|
|
def _db():
|
||
|
|
conn = get_connection()
|
||
|
|
try:
|
||
|
|
yield conn
|
||
|
|
finally:
|
||
|
|
conn.close()
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("", response_model=PaginatedKetten)
|
||
|
|
def list_ketten(
|
||
|
|
page: int = Query(1, ge=1),
|
||
|
|
page_size: int = Query(50, ge=1, le=200),
|
||
|
|
status: str | None = None,
|
||
|
|
typ: str | None = None,
|
||
|
|
suche: str | None = None,
|
||
|
|
conn=Depends(_db),
|
||
|
|
):
|
||
|
|
"""List Ketten with optional filters."""
|
||
|
|
where_clauses = []
|
||
|
|
params: list = []
|
||
|
|
|
||
|
|
if status:
|
||
|
|
where_clauses.append("k.status = ?")
|
||
|
|
params.append(status)
|
||
|
|
|
||
|
|
if typ:
|
||
|
|
where_clauses.append("k.typ = ?")
|
||
|
|
params.append(typ)
|
||
|
|
|
||
|
|
if suche:
|
||
|
|
where_clauses.append("k.thema LIKE ?")
|
||
|
|
params.append(f"%{suche}%")
|
||
|
|
|
||
|
|
where_sql = ("WHERE " + " AND ".join(where_clauses)) if where_clauses else ""
|
||
|
|
|
||
|
|
total = conn.execute(
|
||
|
|
f"SELECT COUNT(*) as cnt FROM ketten k {where_sql}", params
|
||
|
|
).fetchone()["cnt"]
|
||
|
|
|
||
|
|
offset = (page - 1) * page_size
|
||
|
|
rows = conn.execute(
|
||
|
|
f"""SELECT k.id, k.typ, k.thema, k.status, k.status_seit,
|
||
|
|
k.letzte_aktivitaet, k.vertagungen_count, k.ursprung_id,
|
||
|
|
v.aktenzeichen, v.typ as v_typ, v.betreff, v.datum_eingang,
|
||
|
|
v.ist_verwaltungsvorlage,
|
||
|
|
(SELECT COUNT(*) FROM ketten_glieder kg WHERE kg.kette_id = k.id) as glieder_count
|
||
|
|
FROM ketten k
|
||
|
|
LEFT JOIN vorlagen v ON k.ursprung_id = v.id
|
||
|
|
{where_sql}
|
||
|
|
ORDER BY k.letzte_aktivitaet DESC NULLS LAST, k.id DESC
|
||
|
|
LIMIT ? OFFSET ?""",
|
||
|
|
params + [page_size, offset],
|
||
|
|
).fetchall()
|
||
|
|
|
||
|
|
items = [
|
||
|
|
KetteKurz(
|
||
|
|
id=r["id"],
|
||
|
|
ursprung=VorlageKurz(
|
||
|
|
id=r["ursprung_id"],
|
||
|
|
aktenzeichen=r["aktenzeichen"],
|
||
|
|
typ=r["v_typ"],
|
||
|
|
betreff=r["betreff"],
|
||
|
|
datum_eingang=r["datum_eingang"],
|
||
|
|
ist_verwaltungsvorlage=bool(r["ist_verwaltungsvorlage"]),
|
||
|
|
) if r["ursprung_id"] else None,
|
||
|
|
typ=r["typ"],
|
||
|
|
thema=r["thema"],
|
||
|
|
status=r["status"],
|
||
|
|
status_seit=r["status_seit"],
|
||
|
|
letzte_aktivitaet=r["letzte_aktivitaet"],
|
||
|
|
vertagungen_count=r["vertagungen_count"],
|
||
|
|
glieder_count=r["glieder_count"],
|
||
|
|
)
|
||
|
|
for r in rows
|
||
|
|
]
|
||
|
|
|
||
|
|
return PaginatedKetten(items=items, total=total, page=page, page_size=page_size)
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/{kette_id}", response_model=KetteDetail)
|
||
|
|
def get_kette(kette_id: int, conn=Depends(_db)):
|
||
|
|
"""Get a single Kette with all Glieder."""
|
||
|
|
row = conn.execute(
|
||
|
|
"""SELECT k.id, k.typ, k.thema, k.status, k.status_seit,
|
||
|
|
k.letzte_aktivitaet, k.vertagungen_count, k.ursprung_id,
|
||
|
|
v.aktenzeichen, v.typ as v_typ, v.betreff, v.datum_eingang,
|
||
|
|
v.ist_verwaltungsvorlage
|
||
|
|
FROM ketten k
|
||
|
|
LEFT JOIN vorlagen v ON k.ursprung_id = v.id
|
||
|
|
WHERE k.id = ?""",
|
||
|
|
(kette_id,),
|
||
|
|
).fetchone()
|
||
|
|
|
||
|
|
if not row:
|
||
|
|
raise HTTPException(status_code=404, detail="Kette nicht gefunden")
|
||
|
|
|
||
|
|
# Get Glieder
|
||
|
|
glieder_rows = conn.execute(
|
||
|
|
"""SELECT kg.position, kg.rolle,
|
||
|
|
v.id, v.aktenzeichen, v.typ, v.betreff, v.datum_eingang,
|
||
|
|
v.ist_verwaltungsvorlage
|
||
|
|
FROM ketten_glieder kg
|
||
|
|
JOIN vorlagen v ON kg.vorlage_id = v.id
|
||
|
|
WHERE kg.kette_id = ?
|
||
|
|
ORDER BY kg.position""",
|
||
|
|
(kette_id,),
|
||
|
|
).fetchall()
|
||
|
|
|
||
|
|
glieder = [
|
||
|
|
KettenGliedOut(
|
||
|
|
vorlage=VorlageKurz(
|
||
|
|
id=g["id"],
|
||
|
|
aktenzeichen=g["aktenzeichen"],
|
||
|
|
typ=g["typ"],
|
||
|
|
betreff=g["betreff"],
|
||
|
|
datum_eingang=g["datum_eingang"],
|
||
|
|
ist_verwaltungsvorlage=bool(g["ist_verwaltungsvorlage"]),
|
||
|
|
),
|
||
|
|
position=g["position"],
|
||
|
|
rolle=g["rolle"],
|
||
|
|
)
|
||
|
|
for g in glieder_rows
|
||
|
|
]
|
||
|
|
|
||
|
|
# Antragsteller des Ursprungs
|
||
|
|
antragsteller = []
|
||
|
|
if row["ursprung_id"]:
|
||
|
|
antragsteller_rows = conn.execute("""
|
||
|
|
SELECT p.id, p.kuerzel, p.name, p.farbe
|
||
|
|
FROM antragsteller a
|
||
|
|
JOIN parteien p ON a.partei_id = p.id
|
||
|
|
WHERE a.vorlage_id = ?
|
||
|
|
""", (row["ursprung_id"],)).fetchall()
|
||
|
|
antragsteller = [ParteiOut(**dict(a)) for a in antragsteller_rows]
|
||
|
|
|
||
|
|
# Graph/Perlenschnur data
|
||
|
|
graph = get_kette_graph(conn, kette_id)
|
||
|
|
|
||
|
|
return KetteDetail(
|
||
|
|
id=row["id"],
|
||
|
|
ursprung=VorlageKurz(
|
||
|
|
id=row["ursprung_id"],
|
||
|
|
aktenzeichen=row["aktenzeichen"],
|
||
|
|
typ=row["v_typ"],
|
||
|
|
betreff=row["betreff"],
|
||
|
|
datum_eingang=row["datum_eingang"],
|
||
|
|
ist_verwaltungsvorlage=bool(row["ist_verwaltungsvorlage"]),
|
||
|
|
) if row["ursprung_id"] else None,
|
||
|
|
typ=row["typ"],
|
||
|
|
thema=row["thema"],
|
||
|
|
status=row["status"],
|
||
|
|
status_seit=row["status_seit"],
|
||
|
|
letzte_aktivitaet=row["letzte_aktivitaet"],
|
||
|
|
vertagungen_count=row["vertagungen_count"],
|
||
|
|
glieder=glieder,
|
||
|
|
antragsteller=antragsteller,
|
||
|
|
graph=graph,
|
||
|
|
)
|