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)
class AdminCompetitionInvitesResource:
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).

AdminCompetitionInvitesResource(http: ctfy.sdk.base.BaseHttpClient)
16    def __init__(self, http: BaseHttpClient) -> None:
17        self._http = http
def list( self, competition_id: str, offset: int = 0, limit: int = 50) -> ctfy.sdk._helpers.PagedList[ctfy.server.models.CompetitionInviteInfo]:
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.

def invite( self, competition_id: str, *, user_id: str = '', email: str = '') -> ctfy.server.models.CompetitionInviteInfo:
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.

def invite_bulk( self, competition_id: str, emails: str) -> ctfy.server.models.BulkInviteResponse:
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.

def revoke(self, competition_id: str, user_id: str) -> None:
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.

def revoke_email(self, competition_id: str, email: str) -> None:
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.