ctfy.sdk.admin_resources.competition_invites
client.admin.competition_invites — private-competition
participation allowlist (admin).
1"""``client.admin.competition_invites`` — private-competition 2participation allowlist (admin).""" 3 4from __future__ import annotations 5 6from ctfy.sdk._helpers import PagedList, _extract_items, _raise_for_status 7from ctfy.sdk.base import BaseHttpClient 8from ctfy.server.models import BulkInviteResponse, CompetitionInviteInfo 9 10 11class AdminCompetitionInvitesResource: 12 """Invite / revoke users on a private competition's participation 13 allowlist. Managed by any competition admin (global or per-comp).""" 14 15 def __init__(self, http: BaseHttpClient) -> None: 16 self._http = http 17 18 def list( 19 self, competition_id: str, offset: int = 0, limit: int = 50 20 ) -> PagedList[CompetitionInviteInfo]: 21 """Everyone invited to participate in one competition.""" 22 resp = self._http.request( 23 "GET", 24 f"/admin/competitions/{competition_id}/invites", 25 params={"offset": offset, "limit": limit}, 26 ) 27 _raise_for_status(resp) 28 return _extract_items(resp.json(), CompetitionInviteInfo) 29 30 def invite( 31 self, competition_id: str, *, user_id: str = "", email: str = "" 32 ) -> CompetitionInviteInfo: 33 """Invite someone to participate. 34 35 Identify the target by ``user_id`` or ``email``. An ``email`` 36 with no account yet is recorded against the address and claimed 37 when its owner first signs in with a verified address — the 38 returned row carries ``pending_signup=True`` in that case. A 39 ``user_id`` that matches nothing is still a 404: an id names an 40 account or nothing. 41 """ 42 resp = self._http.request( 43 "POST", 44 f"/admin/competitions/{competition_id}/invites", 45 json={"user_id": user_id, "email": email}, 46 ) 47 _raise_for_status(resp) 48 return CompetitionInviteInfo.model_validate(resp.json()) 49 50 def invite_bulk(self, competition_id: str, emails: str) -> BulkInviteResponse: 51 """Invite a pasted list of addresses in one call. 52 53 ``emails`` is the raw blob — newline / comma / semicolon 54 separated. Splitting happens server-side so every client agrees 55 on the rules. The response classifies each address as 56 ``invited`` / ``pending`` / ``already`` / ``invalid``. 57 """ 58 resp = self._http.request( 59 "POST", 60 f"/admin/competitions/{competition_id}/invites/bulk", 61 json={"emails": emails}, 62 ) 63 _raise_for_status(resp) 64 return BulkInviteResponse.model_validate(resp.json()) 65 66 def revoke(self, competition_id: str, user_id: str) -> None: 67 """Remove a user from the participation allowlist.""" 68 resp = self._http.request( 69 "DELETE", f"/admin/competitions/{competition_id}/invites/{user_id}" 70 ) 71 _raise_for_status(resp) 72 73 def revoke_email(self, competition_id: str, email: str) -> None: 74 """Withdraw an invitation still waiting for its person.""" 75 resp = self._http.request( 76 "DELETE", f"/admin/competitions/{competition_id}/invites/by-email/{email}" 77 ) 78 _raise_for_status(resp)
12class AdminCompetitionInvitesResource: 13 """Invite / revoke users on a private competition's participation 14 allowlist. Managed by any competition admin (global or per-comp).""" 15 16 def __init__(self, http: BaseHttpClient) -> None: 17 self._http = http 18 19 def list( 20 self, competition_id: str, offset: int = 0, limit: int = 50 21 ) -> PagedList[CompetitionInviteInfo]: 22 """Everyone invited to participate in one competition.""" 23 resp = self._http.request( 24 "GET", 25 f"/admin/competitions/{competition_id}/invites", 26 params={"offset": offset, "limit": limit}, 27 ) 28 _raise_for_status(resp) 29 return _extract_items(resp.json(), CompetitionInviteInfo) 30 31 def invite( 32 self, competition_id: str, *, user_id: str = "", email: str = "" 33 ) -> CompetitionInviteInfo: 34 """Invite someone to participate. 35 36 Identify the target by ``user_id`` or ``email``. An ``email`` 37 with no account yet is recorded against the address and claimed 38 when its owner first signs in with a verified address — the 39 returned row carries ``pending_signup=True`` in that case. A 40 ``user_id`` that matches nothing is still a 404: an id names an 41 account or nothing. 42 """ 43 resp = self._http.request( 44 "POST", 45 f"/admin/competitions/{competition_id}/invites", 46 json={"user_id": user_id, "email": email}, 47 ) 48 _raise_for_status(resp) 49 return CompetitionInviteInfo.model_validate(resp.json()) 50 51 def invite_bulk(self, competition_id: str, emails: str) -> BulkInviteResponse: 52 """Invite a pasted list of addresses in one call. 53 54 ``emails`` is the raw blob — newline / comma / semicolon 55 separated. Splitting happens server-side so every client agrees 56 on the rules. The response classifies each address as 57 ``invited`` / ``pending`` / ``already`` / ``invalid``. 58 """ 59 resp = self._http.request( 60 "POST", 61 f"/admin/competitions/{competition_id}/invites/bulk", 62 json={"emails": emails}, 63 ) 64 _raise_for_status(resp) 65 return BulkInviteResponse.model_validate(resp.json()) 66 67 def revoke(self, competition_id: str, user_id: str) -> None: 68 """Remove a user from the participation allowlist.""" 69 resp = self._http.request( 70 "DELETE", f"/admin/competitions/{competition_id}/invites/{user_id}" 71 ) 72 _raise_for_status(resp) 73 74 def revoke_email(self, competition_id: str, email: str) -> None: 75 """Withdraw an invitation still waiting for its person.""" 76 resp = self._http.request( 77 "DELETE", f"/admin/competitions/{competition_id}/invites/by-email/{email}" 78 ) 79 _raise_for_status(resp)
Invite / revoke users on a private competition's participation allowlist. Managed by any competition admin (global or per-comp).
19 def list( 20 self, competition_id: str, offset: int = 0, limit: int = 50 21 ) -> PagedList[CompetitionInviteInfo]: 22 """Everyone invited to participate in one competition.""" 23 resp = self._http.request( 24 "GET", 25 f"/admin/competitions/{competition_id}/invites", 26 params={"offset": offset, "limit": limit}, 27 ) 28 _raise_for_status(resp) 29 return _extract_items(resp.json(), CompetitionInviteInfo)
Everyone invited to participate in one competition.
31 def invite( 32 self, competition_id: str, *, user_id: str = "", email: str = "" 33 ) -> CompetitionInviteInfo: 34 """Invite someone to participate. 35 36 Identify the target by ``user_id`` or ``email``. An ``email`` 37 with no account yet is recorded against the address and claimed 38 when its owner first signs in with a verified address — the 39 returned row carries ``pending_signup=True`` in that case. A 40 ``user_id`` that matches nothing is still a 404: an id names an 41 account or nothing. 42 """ 43 resp = self._http.request( 44 "POST", 45 f"/admin/competitions/{competition_id}/invites", 46 json={"user_id": user_id, "email": email}, 47 ) 48 _raise_for_status(resp) 49 return CompetitionInviteInfo.model_validate(resp.json())
Invite someone to participate.
Identify the target by user_id or email. An email
with no account yet is recorded against the address and claimed
when its owner first signs in with a verified address — the
returned row carries pending_signup=True in that case. A
user_id that matches nothing is still a 404: an id names an
account or nothing.
51 def invite_bulk(self, competition_id: str, emails: str) -> BulkInviteResponse: 52 """Invite a pasted list of addresses in one call. 53 54 ``emails`` is the raw blob — newline / comma / semicolon 55 separated. Splitting happens server-side so every client agrees 56 on the rules. The response classifies each address as 57 ``invited`` / ``pending`` / ``already`` / ``invalid``. 58 """ 59 resp = self._http.request( 60 "POST", 61 f"/admin/competitions/{competition_id}/invites/bulk", 62 json={"emails": emails}, 63 ) 64 _raise_for_status(resp) 65 return BulkInviteResponse.model_validate(resp.json())
Invite a pasted list of addresses in one call.
emails is the raw blob — newline / comma / semicolon
separated. Splitting happens server-side so every client agrees
on the rules. The response classifies each address as
invited / pending / already / invalid.
67 def revoke(self, competition_id: str, user_id: str) -> None: 68 """Remove a user from the participation allowlist.""" 69 resp = self._http.request( 70 "DELETE", f"/admin/competitions/{competition_id}/invites/{user_id}" 71 ) 72 _raise_for_status(resp)
Remove a user from the participation allowlist.
74 def revoke_email(self, competition_id: str, email: str) -> None: 75 """Withdraw an invitation still waiting for its person.""" 76 resp = self._http.request( 77 "DELETE", f"/admin/competitions/{competition_id}/invites/by-email/{email}" 78 ) 79 _raise_for_status(resp)
Withdraw an invitation still waiting for its person.