""" Delt autorisasjonssjekk: to distinkte spørsmål, to funksjoner. 1. `user_is_team_captain` -- "kan brukeren sette opp/fjerne/låse LAGETS oppstilling?" Brukt av matches.py sin add_participant/remove_participant/ lock_lineup, FØR noen match_participant-rad i det hele tatt finnes for brukeren selv (kylling-og-egg: man kan ikke kreve at brukeren ALLEREDE er deltaker for å få lov til å LEGGE TIL deltakere). 2026-07-19 (Brukerroller-runden): byttet fra "hvem som helst rostret på laget" til kaptein (`team_roster.is_captain = true`) ELLER org-eier/admin -- kapteinen er nå en reell autorisasjonsrolle, ikke bare et visningsmerke. Se ARCHITECTURE_DECISIONS.md for hele begrunnelsen. Bevisst unntak: har laget INGEN utpekt kaptein ennå, godtas enhver rostret spiller i stedet for å låse laget helt ute -- vanlig tidlig i en turnering før noen har rukket å utpeke en kaptein (og en reell risiko funnet i eksisterende produksjonsdata: langt fra alle roster-rader har en kaptein i dag). Har laget FØRST fått en kaptein (`update_roster_entry` håndhever nå "kun én kaptein per lag"), er det utelukkende den som gjelder -- ingen andre rostrede spillere. 2. `user_is_match_participant` -- "kan brukeren føre/korrigere score for DENNE spesifikke matchen?" Brukt av scoring.py. Krever en faktisk match_participant-rad for brukeren i akkurat denne matchen (valgfritt begrenset til én side via `team_side`) -- IKKE bare rostret på laget, og UAVHENGIG av kapteinmerket (å være kaptein gir ikke i seg selv rett til å føre score for en match man selv ikke spiller). Begge har samme org-eier/admin-fallback, av samme grunn som tidligere (2026-07-18-runden): uten den kunne INGEN sette opp eller føre noe før minst én spiller hadde logget inn og blitt koblet til sin player-rad. Bevisst INGEN unntak for at organisatoren selv er rostret på MOTSTANDERLAGET i samme turnering -- vurdert og avvist tidligere: TeeCup er et tillitsbasert verktøy for klubber/vennegjenger, ikke en sikkerhetsgrense mot en fiendtlig organisator (som uansett har full administrativ tilgang), og et slikt unntak ville skapt en reell låsning. """ from asyncpg import Connection async def is_org_admin(conn: Connection, organization_id: str, user_id: str) -> bool: # organization_membership har ingen RLS-policy (se auth.get_authorized_org # sin egen kommentar) -- filtrert eksplisitt på organization_id her, trygt # på samme tilkobling uansett hvilken app.current_org som er satt. result = await conn.fetchval( """ SELECT EXISTS ( SELECT 1 FROM organization_membership WHERE organization_id = $1 AND user_id = $2 AND role IN ('owner', 'admin') ) """, organization_id, user_id, ) return bool(result) async def user_is_team_captain( conn: Connection, organization_id: str, team_id: str, user_id: str ) -> bool: has_captain = await conn.fetchval( "SELECT EXISTS (SELECT 1 FROM team_roster WHERE team_id = $1 AND is_captain = true)", team_id, ) if has_captain: may_act = await conn.fetchval( """ SELECT EXISTS ( SELECT 1 FROM team_roster tr JOIN player p ON p.id = tr.player_id WHERE tr.team_id = $1 AND p.user_id = $2 AND tr.is_captain = true ) """, team_id, user_id, ) else: may_act = await conn.fetchval( """ SELECT EXISTS ( SELECT 1 FROM team_roster tr JOIN player p ON p.id = tr.player_id WHERE tr.team_id = $1 AND p.user_id = $2 ) """, team_id, user_id, ) if may_act: return True return await is_org_admin(conn, organization_id, user_id) async def user_is_match_participant( conn: Connection, organization_id: str, match_id: str, user_id: str, team_side: str | None = None, ) -> bool: is_participant = await conn.fetchval( """ SELECT EXISTS ( SELECT 1 FROM match_participant mp JOIN team_roster tr ON tr.id = mp.team_roster_id JOIN player p ON p.id = tr.player_id WHERE mp.match_id = $1 AND p.user_id = $2 AND ($3::text IS NULL OR mp.team_side::text = $3) ) """, match_id, user_id, team_side, ) if is_participant: return True return await is_org_admin(conn, organization_id, user_id) async def user_is_rostered_on_team(conn: Connection, team_id: str, user_id: str) -> bool: """Kun for lag-chat (ADR-025 Beslutning C) -- BEVISST INGEN org-admin- fallback, ulikt de to funksjonene over. «Det hemmelige rommet» er ekte privat: kun spillere faktisk rostret på laget, aldri organisatoren, uansett org-rolle. Ikke gjenbruk denne for autorisasjon utenfor chat.""" return bool( await conn.fetchval( """ SELECT EXISTS ( SELECT 1 FROM team_roster tr JOIN player p ON p.id = tr.player_id WHERE tr.team_id = $1 AND p.user_id = $2 ) """, team_id, user_id, ) )