- Refactored club join requests, memberships, and clubs routers to utilize TenantContext for authentication and authorization, enhancing security and consistency. - Updated session handling to replace direct session dictionary access with TenantContext, improving code clarity and maintainability. - Ensured proper role and profile ID retrieval from TenantContext in various endpoints, streamlining access control for club management functionalities.
721 lines
24 KiB
Python
721 lines
24 KiB
Python
"""
|
||
Club & Organization Management Endpoints for Shinkan Jinkendo
|
||
|
||
Handles CRUD operations for clubs, divisions, and training groups.
|
||
"""
|
||
from typing import Any, List, Optional
|
||
from fastapi import APIRouter, HTTPException, Depends, Query
|
||
|
||
from db import get_db, get_cursor, r2d
|
||
from tenant_context import TenantContext, get_tenant_context
|
||
from club_tenancy import (
|
||
assert_club_member,
|
||
can_manage_club_org,
|
||
can_plan_in_club,
|
||
club_ids_for_profile,
|
||
is_platform_admin,
|
||
)
|
||
|
||
router = APIRouter(prefix="/api", tags=["clubs"])
|
||
|
||
|
||
# ── List Clubs ────────────────────────────────────────────────────────
|
||
@router.get("/clubs")
|
||
def list_clubs(
|
||
status: Optional[str] = Query(default=None),
|
||
tenant: TenantContext = Depends(get_tenant_context),
|
||
):
|
||
"""
|
||
Vereine: für normale Nutzer nur Mitgliedschaft-Vereine; Plattform-Admins sehen alle.
|
||
"""
|
||
role = tenant.global_role
|
||
profile_id = tenant.profile_id
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
query = "SELECT * FROM clubs"
|
||
params: List[Any] = []
|
||
conds = []
|
||
|
||
if not is_platform_admin(role):
|
||
cids = club_ids_for_profile(cur, profile_id)
|
||
if not cids:
|
||
return []
|
||
conds.append("id IN (" + ",".join(["%s"] * len(cids)) + ")")
|
||
params.extend(sorted(cids))
|
||
|
||
if status:
|
||
conds.append("status = %s")
|
||
params.append(status)
|
||
|
||
if conds:
|
||
query += " WHERE " + " AND ".join(conds)
|
||
|
||
query += " ORDER BY name"
|
||
|
||
cur.execute(query, params)
|
||
rows = cur.fetchall()
|
||
return [r2d(r) for r in rows]
|
||
|
||
|
||
# ── Öffentliches Vereinsverzeichnis (Registrierung / Antrag ohne Mitgliedschaft) ──
|
||
@router.get("/clubs/public-directory")
|
||
def public_club_directory():
|
||
"""Aktive Vereine zur Auswahl bei Registrierung oder Beitrittsantrag (nur id, name, Kürzel)."""
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
cur.execute(
|
||
"""
|
||
SELECT id, name, abbreviation
|
||
FROM clubs
|
||
WHERE status = 'active'
|
||
ORDER BY name
|
||
"""
|
||
)
|
||
return [r2d(r) for r in cur.fetchall()]
|
||
|
||
|
||
# ── Aktive Mitglieder für Trainer-/Co-Trainer-Auswahl (jeder Vereinsmitglied) ──
|
||
@router.get("/clubs/{club_id}/members/directory")
|
||
def club_members_directory(club_id: int, tenant: TenantContext = Depends(get_tenant_context)):
|
||
profile_id = tenant.profile_id
|
||
role = tenant.global_role
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
if not is_platform_admin(role):
|
||
assert_club_member(cur, profile_id, club_id)
|
||
cur.execute("SELECT 1 FROM clubs WHERE id = %s", (club_id,))
|
||
if not cur.fetchone():
|
||
raise HTTPException(404, "Verein nicht gefunden")
|
||
cur.execute(
|
||
"""
|
||
SELECT p.id, p.name, p.email
|
||
FROM club_members cm
|
||
INNER JOIN profiles p ON p.id = cm.profile_id
|
||
WHERE cm.club_id = %s AND cm.status = 'active'
|
||
ORDER BY COALESCE(p.name, ''), p.email
|
||
""",
|
||
(club_id,),
|
||
)
|
||
return [r2d(r) for r in cur.fetchall()]
|
||
|
||
|
||
# ── Get Club ──────────────────────────────────────────────────────────
|
||
@router.get("/clubs/{club_id}")
|
||
def get_club(club_id: int, tenant: TenantContext = Depends(get_tenant_context)):
|
||
"""Get club by ID with divisions and groups – nur Mitglied oder Plattform-Admin."""
|
||
profile_id = tenant.profile_id
|
||
role = tenant.global_role
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
if not is_platform_admin(role):
|
||
assert_club_member(cur, profile_id, club_id)
|
||
|
||
# Get club
|
||
cur.execute("SELECT * FROM clubs WHERE id = %s", (club_id,))
|
||
club = cur.fetchone()
|
||
|
||
if not club:
|
||
raise HTTPException(404, "Verein nicht gefunden")
|
||
|
||
club = r2d(club)
|
||
|
||
# Get divisions
|
||
cur.execute(
|
||
"""
|
||
SELECT * FROM divisions
|
||
WHERE club_id = %s
|
||
ORDER BY name
|
||
""",
|
||
(club_id,),
|
||
)
|
||
club["divisions"] = [r2d(r) for r in cur.fetchall()]
|
||
|
||
# Get training groups
|
||
cur.execute(
|
||
"""
|
||
SELECT g.*,
|
||
p.name as trainer_name
|
||
FROM training_groups g
|
||
LEFT JOIN profiles p ON g.trainer_id = p.id
|
||
WHERE g.club_id = %s
|
||
ORDER BY g.weekday, g.time_start
|
||
""",
|
||
(club_id,),
|
||
)
|
||
club["training_groups"] = [r2d(r) for r in cur.fetchall()]
|
||
|
||
return club
|
||
|
||
|
||
# ── Create Club ───────────────────────────────────────────────────────
|
||
@router.post("/clubs")
|
||
def create_club(data: dict, tenant: TenantContext = Depends(get_tenant_context)):
|
||
"""Neuen Verein anlegen – nur Plattform-Admin; Pflicht: primary_admin_profile_id (Hauptverwalter:in)."""
|
||
role = tenant.global_role
|
||
if not is_platform_admin(role):
|
||
raise HTTPException(403, "Nur Plattform-Administratoren dürfen neue Vereine anlegen")
|
||
|
||
name = data.get("name")
|
||
primary_admin_profile_id = data.get("primary_admin_profile_id")
|
||
if not name:
|
||
raise HTTPException(400, "Name ist Pflichtfeld")
|
||
if not primary_admin_profile_id:
|
||
raise HTTPException(400, "primary_admin_profile_id ist Pflichtfeld (Hauptverwalter:in)")
|
||
|
||
try:
|
||
aid = int(primary_admin_profile_id)
|
||
except (TypeError, ValueError):
|
||
raise HTTPException(400, "primary_admin_profile_id ungültig")
|
||
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
cur.execute("SELECT id FROM profiles WHERE id = %s", (aid,))
|
||
if not cur.fetchone():
|
||
raise HTTPException(404, "Profil für Hauptverwalter nicht gefunden")
|
||
|
||
cur.execute(
|
||
"""
|
||
INSERT INTO clubs (name, abbreviation, description, status, primary_admin_profile_id)
|
||
VALUES (%s, %s, %s, %s, %s)
|
||
RETURNING id
|
||
""",
|
||
(
|
||
name,
|
||
data.get("abbreviation"),
|
||
data.get("description"),
|
||
data.get("status", "active"),
|
||
aid,
|
||
),
|
||
)
|
||
|
||
club_id = cur.fetchone()["id"]
|
||
|
||
cur.execute(
|
||
"""
|
||
INSERT INTO club_members (profile_id, club_id, status)
|
||
VALUES (%s, %s, 'active')
|
||
ON CONFLICT (profile_id, club_id)
|
||
DO UPDATE SET status = 'active', updated_at = NOW()
|
||
RETURNING id
|
||
""",
|
||
(aid, club_id),
|
||
)
|
||
cm_id = cur.fetchone()["id"]
|
||
for rc in ("club_admin", "trainer"):
|
||
cur.execute(
|
||
"""
|
||
INSERT INTO club_member_roles (club_member_id, role_code)
|
||
VALUES (%s, %s)
|
||
ON CONFLICT (club_member_id, role_code) DO NOTHING
|
||
""",
|
||
(cm_id, rc),
|
||
)
|
||
|
||
conn.commit()
|
||
|
||
return get_club(club_id, tenant)
|
||
|
||
|
||
# ── Update Club ───────────────────────────────────────────────────────
|
||
@router.put("/clubs/{club_id}")
|
||
def update_club(club_id: int, data: dict, tenant: TenantContext = Depends(get_tenant_context)):
|
||
"""Verein bearbeiten – Plattform-Admin oder Vereinsadmin."""
|
||
profile_id = tenant.profile_id
|
||
role = tenant.global_role
|
||
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
|
||
# Check existence
|
||
cur.execute("SELECT id FROM clubs WHERE id = %s", (club_id,))
|
||
if not cur.fetchone():
|
||
raise HTTPException(404, "Verein nicht gefunden")
|
||
|
||
if not can_manage_club_org(cur, profile_id, club_id, role):
|
||
raise HTTPException(403, "Keine Berechtigung für diesen Verein")
|
||
|
||
# Nur Plattform-Admin darf primary_admin_profile_id ändern
|
||
if "primary_admin_profile_id" in data and data["primary_admin_profile_id"] is not None:
|
||
if not is_platform_admin(role):
|
||
raise HTTPException(403, "Nur Plattform-Admins dürfen den Hauptverwalter ändern")
|
||
|
||
# Update
|
||
cur.execute(
|
||
"""
|
||
UPDATE clubs SET
|
||
name = COALESCE(%s, name),
|
||
abbreviation = COALESCE(%s, abbreviation),
|
||
description = COALESCE(%s, description),
|
||
status = COALESCE(%s, status),
|
||
primary_admin_profile_id = COALESCE(%s, primary_admin_profile_id),
|
||
updated_at = NOW()
|
||
WHERE id = %s
|
||
""",
|
||
(
|
||
data.get("name"),
|
||
data.get("abbreviation"),
|
||
data.get("description"),
|
||
data.get("status"),
|
||
data.get("primary_admin_profile_id"),
|
||
club_id,
|
||
),
|
||
)
|
||
|
||
conn.commit()
|
||
|
||
return get_club(club_id, tenant)
|
||
|
||
|
||
# ── Delete Club ───────────────────────────────────────────────────────
|
||
@router.delete("/clubs/{club_id}")
|
||
def delete_club(club_id: int, tenant: TenantContext = Depends(get_tenant_context)):
|
||
"""Delete club (superadmin only)."""
|
||
role = tenant.global_role
|
||
if role != 'superadmin':
|
||
raise HTTPException(403, "Nur Superadmins dürfen Vereine löschen")
|
||
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
|
||
# Check existence
|
||
cur.execute("SELECT id FROM clubs WHERE id = %s", (club_id,))
|
||
if not cur.fetchone():
|
||
raise HTTPException(404, "Verein nicht gefunden")
|
||
|
||
# Delete (CASCADE handles divisions and groups)
|
||
cur.execute("DELETE FROM clubs WHERE id = %s", (club_id,))
|
||
conn.commit()
|
||
|
||
return {"ok": True}
|
||
|
||
|
||
# ── List Divisions ────────────────────────────────────────────────────
|
||
@router.get("/divisions")
|
||
def list_divisions(
|
||
club_id: Optional[int] = Query(default=None),
|
||
tenant: TenantContext = Depends(get_tenant_context),
|
||
):
|
||
"""Sparten – ohne Admin-Rechte nur in eigenen Vereinen sichtbar."""
|
||
profile_id = tenant.profile_id
|
||
role = tenant.global_role
|
||
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
|
||
mine = club_ids_for_profile(cur, profile_id)
|
||
if not is_platform_admin(role) and not mine:
|
||
return []
|
||
|
||
query = """
|
||
SELECT d.*, c.name as club_name
|
||
FROM divisions d
|
||
LEFT JOIN clubs c ON d.club_id = c.id
|
||
"""
|
||
where = []
|
||
params = []
|
||
|
||
if club_id is not None:
|
||
where.append("d.club_id = %s")
|
||
params.append(club_id)
|
||
if not is_platform_admin(role) and club_id not in mine:
|
||
return []
|
||
|
||
if not is_platform_admin(role):
|
||
where.append(
|
||
"d.club_id IN (" + ",".join(["%s"] * len(mine)) + ")"
|
||
)
|
||
params.extend(sorted(mine))
|
||
|
||
if where:
|
||
query += " WHERE " + " AND ".join(where)
|
||
|
||
query += " ORDER BY d.name"
|
||
|
||
cur.execute(query, params)
|
||
rows = cur.fetchall()
|
||
return [r2d(r) for r in rows]
|
||
|
||
|
||
# ── Create Division ───────────────────────────────────────────────────
|
||
@router.post("/divisions")
|
||
def create_division(data: dict, tenant: TenantContext = Depends(get_tenant_context)):
|
||
"""Create new division – Vereinsadmin / Plattform-Admin."""
|
||
profile_id = tenant.profile_id
|
||
role = tenant.global_role
|
||
|
||
club_id = data.get("club_id")
|
||
name = data.get("name")
|
||
|
||
if not club_id or not name:
|
||
raise HTTPException(400, "club_id und name sind Pflichtfelder")
|
||
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
|
||
if not can_manage_club_org(cur, profile_id, int(club_id), role):
|
||
raise HTTPException(403, "Keine Berechtigung, Sparten in diesem Verein anzulegen")
|
||
|
||
# Check club exists
|
||
cur.execute("SELECT id FROM clubs WHERE id = %s", (club_id,))
|
||
if not cur.fetchone():
|
||
raise HTTPException(404, "Verein nicht gefunden")
|
||
|
||
# Insert
|
||
cur.execute(
|
||
"""
|
||
INSERT INTO divisions (club_id, name, focus_area)
|
||
VALUES (%s, %s, %s)
|
||
RETURNING id
|
||
""",
|
||
(
|
||
club_id,
|
||
name,
|
||
data.get("focus_area"),
|
||
),
|
||
)
|
||
|
||
division_id = cur.fetchone()["id"]
|
||
conn.commit()
|
||
|
||
# Return created division
|
||
cur.execute(
|
||
"""
|
||
SELECT d.*, c.name as club_name
|
||
FROM divisions d
|
||
LEFT JOIN clubs c ON d.club_id = c.id
|
||
WHERE d.id = %s
|
||
""",
|
||
(division_id,),
|
||
)
|
||
return r2d(cur.fetchone())
|
||
|
||
|
||
# ── Update Division ───────────────────────────────────────────────────
|
||
@router.put("/divisions/{division_id}")
|
||
def update_division(division_id: int, data: dict, tenant: TenantContext = Depends(get_tenant_context)):
|
||
"""Update division – Vereinsadmin / Plattform-Admin."""
|
||
profile_id = tenant.profile_id
|
||
role = tenant.global_role
|
||
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
|
||
cur.execute("SELECT id, club_id FROM divisions WHERE id = %s", (division_id,))
|
||
div = cur.fetchone()
|
||
if not div:
|
||
raise HTTPException(404, "Sparte nicht gefunden")
|
||
|
||
if not can_manage_club_org(cur, profile_id, div["club_id"], role):
|
||
raise HTTPException(403, "Keine Berechtigung")
|
||
|
||
cur.execute(
|
||
"""
|
||
UPDATE divisions SET
|
||
name = %s,
|
||
focus_area = %s,
|
||
updated_at = NOW()
|
||
WHERE id = %s
|
||
""",
|
||
(
|
||
data.get("name"),
|
||
data.get("focus_area"),
|
||
division_id,
|
||
),
|
||
)
|
||
|
||
conn.commit()
|
||
|
||
cur.execute(
|
||
"""
|
||
SELECT d.*, c.name as club_name
|
||
FROM divisions d
|
||
LEFT JOIN clubs c ON d.club_id = c.id
|
||
WHERE d.id = %s
|
||
""",
|
||
(division_id,),
|
||
)
|
||
return r2d(cur.fetchone())
|
||
|
||
|
||
# ── Delete Division ───────────────────────────────────────────────────
|
||
@router.delete("/divisions/{division_id}")
|
||
def delete_division(division_id: int, tenant: TenantContext = Depends(get_tenant_context)):
|
||
"""Delete division – Vereinsadmin / Plattform-Admin."""
|
||
profile_id = tenant.profile_id
|
||
role = tenant.global_role
|
||
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
|
||
cur.execute("SELECT id, club_id FROM divisions WHERE id = %s", (division_id,))
|
||
div = cur.fetchone()
|
||
if not div:
|
||
raise HTTPException(404, "Sparte nicht gefunden")
|
||
|
||
if not can_manage_club_org(cur, profile_id, div["club_id"], role):
|
||
raise HTTPException(403, "Keine Berechtigung")
|
||
|
||
cur.execute("DELETE FROM divisions WHERE id = %s", (division_id,))
|
||
conn.commit()
|
||
|
||
return {"ok": True}
|
||
|
||
|
||
# ── List Training Groups ──────────────────────────────────────────────
|
||
@router.get("/groups")
|
||
def list_training_groups(
|
||
club_id: Optional[int] = Query(default=None),
|
||
division_id: Optional[int] = Query(default=None),
|
||
status: Optional[str] = Query(default=None),
|
||
tenant: TenantContext = Depends(get_tenant_context),
|
||
):
|
||
"""
|
||
Trainingsgruppen – ohne Plattform-Admin nur in eigenen Vereinen.
|
||
"""
|
||
profile_id = tenant.profile_id
|
||
role = tenant.global_role
|
||
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
|
||
mine = club_ids_for_profile(cur, profile_id)
|
||
if not is_platform_admin(role) and not mine:
|
||
return []
|
||
|
||
query = """
|
||
SELECT g.*,
|
||
c.name as club_name,
|
||
d.name as division_name,
|
||
p.name as trainer_name
|
||
FROM training_groups g
|
||
LEFT JOIN clubs c ON g.club_id = c.id
|
||
LEFT JOIN divisions d ON g.division_id = d.id
|
||
LEFT JOIN profiles p ON g.trainer_id = p.id
|
||
"""
|
||
|
||
where = []
|
||
params = []
|
||
|
||
if club_id is not None:
|
||
where.append("g.club_id = %s")
|
||
params.append(club_id)
|
||
if not is_platform_admin(role) and club_id not in mine:
|
||
return []
|
||
|
||
if division_id is not None:
|
||
where.append("g.division_id = %s")
|
||
params.append(division_id)
|
||
|
||
if status:
|
||
where.append("g.status = %s")
|
||
params.append(status)
|
||
|
||
if not is_platform_admin(role):
|
||
where.append("g.club_id IN (" + ",".join(["%s"] * len(mine)) + ")")
|
||
params.extend(sorted(mine))
|
||
|
||
if where:
|
||
query += " WHERE " + " AND ".join(where)
|
||
|
||
query += " ORDER BY g.weekday, g.time_start"
|
||
|
||
cur.execute(query, params)
|
||
rows = cur.fetchall()
|
||
return [r2d(r) for r in rows]
|
||
|
||
|
||
# ── Get Training Group ────────────────────────────────────────────────
|
||
@router.get("/groups/{group_id}")
|
||
def get_training_group(group_id: int, tenant: TenantContext = Depends(get_tenant_context)):
|
||
"""Trainingsgruppe – nur mit Vereinszugriff."""
|
||
profile_id = tenant.profile_id
|
||
role = tenant.global_role
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
|
||
cur.execute(
|
||
"""
|
||
SELECT g.*,
|
||
c.name as club_name,
|
||
d.name as division_name,
|
||
p.name as trainer_name
|
||
FROM training_groups g
|
||
LEFT JOIN clubs c ON g.club_id = c.id
|
||
LEFT JOIN divisions d ON g.division_id = d.id
|
||
LEFT JOIN profiles p ON g.trainer_id = p.id
|
||
WHERE g.id = %s
|
||
""",
|
||
(group_id,),
|
||
)
|
||
|
||
group = cur.fetchone()
|
||
|
||
if not group:
|
||
raise HTTPException(404, "Trainingsgruppe nicht gefunden")
|
||
|
||
cid = group["club_id"]
|
||
if not is_platform_admin(role):
|
||
assert_club_member(cur, profile_id, cid)
|
||
|
||
return r2d(group)
|
||
|
||
|
||
# ── Create Training Group ─────────────────────────────────────────────
|
||
@router.post("/groups")
|
||
def create_training_group(data: dict, tenant: TenantContext = Depends(get_tenant_context)):
|
||
"""Trainingsgruppe anlegen – Mitglied mit Planungs-/Admin-Rolle im Verein."""
|
||
profile_id = tenant.profile_id
|
||
role = tenant.global_role
|
||
if role not in ("admin", "superadmin", "trainer", "user"):
|
||
raise HTTPException(403, "Keine Berechtigung, Trainingsgruppen anzulegen")
|
||
|
||
club_id = data.get("club_id")
|
||
name = data.get("name")
|
||
|
||
if not club_id or not name:
|
||
raise HTTPException(400, "club_id und name sind Pflichtfelder")
|
||
|
||
trainer_id = data.get("trainer_id")
|
||
if trainer_id in (None, "", 0) and role in ("trainer", "user"):
|
||
trainer_id = profile_id
|
||
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
|
||
if not can_plan_in_club(cur, profile_id, int(club_id), role):
|
||
raise HTTPException(403, "Keine Berechtigung für diesen Verein")
|
||
|
||
# Check club exists
|
||
cur.execute("SELECT id FROM clubs WHERE id = %s", (club_id,))
|
||
if not cur.fetchone():
|
||
raise HTTPException(404, "Verein nicht gefunden")
|
||
|
||
# Insert
|
||
cur.execute(
|
||
"""
|
||
INSERT INTO training_groups (
|
||
club_id, division_id, name, focus, level, age_group,
|
||
weekday, time_start, time_end, location,
|
||
trainer_id, co_trainer_ids, status
|
||
) VALUES (
|
||
%s, %s, %s, %s, %s, %s,
|
||
%s, %s, %s, %s,
|
||
%s, %s, %s
|
||
) RETURNING id
|
||
""",
|
||
(
|
||
club_id,
|
||
data.get("division_id"),
|
||
name,
|
||
data.get("focus"),
|
||
data.get("level"),
|
||
data.get("age_group"),
|
||
data.get("weekday"),
|
||
data.get("time_start"),
|
||
data.get("time_end"),
|
||
data.get("location"),
|
||
trainer_id,
|
||
data.get("co_trainer_ids"),
|
||
data.get("status", "active"),
|
||
),
|
||
)
|
||
|
||
group_id = cur.fetchone()["id"]
|
||
conn.commit()
|
||
|
||
return get_training_group(group_id, tenant)
|
||
|
||
|
||
# ── Update Training Group ─────────────────────────────────────────────
|
||
@router.put("/groups/{group_id}")
|
||
def update_training_group(group_id: int, data: dict, tenant: TenantContext = Depends(get_tenant_context)):
|
||
"""Update training group – Vereinsadmin, Plattform-Admin oder zugewiesene Trainer."""
|
||
profile_id = tenant.profile_id
|
||
role = tenant.global_role
|
||
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
|
||
cur.execute(
|
||
"SELECT trainer_id, co_trainer_ids, club_id FROM training_groups WHERE id = %s",
|
||
(group_id,),
|
||
)
|
||
row = cur.fetchone()
|
||
if not row:
|
||
raise HTTPException(404, "Trainingsgruppe nicht gefunden")
|
||
|
||
co_trainers = row["co_trainer_ids"] or []
|
||
club_id = row["club_id"]
|
||
|
||
allowed = role in ("admin", "superadmin")
|
||
if not allowed:
|
||
allowed = can_manage_club_org(cur, profile_id, club_id, role)
|
||
if not allowed:
|
||
allowed = row["trainer_id"] == profile_id or profile_id in co_trainers
|
||
|
||
if not allowed:
|
||
raise HTTPException(403, "Keine Berechtigung")
|
||
|
||
cur.execute(
|
||
"""
|
||
UPDATE training_groups SET
|
||
name = %s,
|
||
division_id = %s,
|
||
focus = %s,
|
||
level = %s,
|
||
age_group = %s,
|
||
weekday = %s,
|
||
time_start = %s,
|
||
time_end = %s,
|
||
location = %s,
|
||
trainer_id = %s,
|
||
co_trainer_ids = %s,
|
||
status = %s,
|
||
updated_at = NOW()
|
||
WHERE id = %s
|
||
""",
|
||
(
|
||
data.get("name"),
|
||
data.get("division_id"),
|
||
data.get("focus"),
|
||
data.get("level"),
|
||
data.get("age_group"),
|
||
data.get("weekday"),
|
||
data.get("time_start"),
|
||
data.get("time_end"),
|
||
data.get("location"),
|
||
data.get("trainer_id"),
|
||
data.get("co_trainer_ids"),
|
||
data.get("status"),
|
||
group_id,
|
||
),
|
||
)
|
||
|
||
conn.commit()
|
||
|
||
return get_training_group(group_id, tenant)
|
||
|
||
|
||
# ── Delete Training Group ─────────────────────────────────────────────
|
||
@router.delete("/groups/{group_id}")
|
||
def delete_training_group(group_id: int, tenant: TenantContext = Depends(get_tenant_context)):
|
||
"""Delete training group – Vereinsadmin oder Plattform-Admin."""
|
||
profile_id = tenant.profile_id
|
||
role = tenant.global_role
|
||
|
||
with get_db() as conn:
|
||
cur = get_cursor(conn)
|
||
|
||
cur.execute("SELECT id, club_id FROM training_groups WHERE id = %s", (group_id,))
|
||
row = cur.fetchone()
|
||
if not row:
|
||
raise HTTPException(404, "Trainingsgruppe nicht gefunden")
|
||
|
||
if not can_manage_club_org(cur, profile_id, row["club_id"], role):
|
||
raise HTTPException(403, "Keine Berechtigung")
|
||
|
||
cur.execute("DELETE FROM training_groups WHERE id = %s", (group_id,))
|
||
conn.commit()
|
||
|
||
return {"ok": True}
|