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

Remove a user from the participation allowlist.

def revoke_email(self, competition_id: str, email: str) -> None:
76    def revoke_email(self, competition_id: str, email: str) -> None:
77        """Withdraw an invitation still waiting for its person."""
78        resp = self._http.request(
79            "DELETE", f"/admin/competitions/{competition_id}/invites/by-email/{email}"
80        )
81        _raise_for_status(resp)

Withdraw an invitation still waiting for its person.