ctfy.sdk.admin_resources.users

client.admin.users — user roster + role mutation (admin).

 1"""``client.admin.users`` — user roster + role mutation (admin)."""
 2
 3from __future__ import annotations
 4
 5import builtins
 6
 7from ctfy.sdk._helpers import _raise_for_status
 8from ctfy.sdk.base import BaseHttpClient
 9from ctfy.server.models import AdminUserInfo
10
11
12class AdminUsersResource:
13    """Admin-or-better user listing, lookup and promotion."""
14
15    def __init__(self, http: BaseHttpClient) -> None:
16        self._http = http
17
18    def list(
19        self,
20        offset: int = 0,
21        limit: int = 50,
22        *,
23        q: str = "",
24        role: str = "",
25        sort: str = "",
26        dir: str = "",
27    ) -> builtins.list[AdminUserInfo]:
28        """Users known to the platform, with role + promotion audit.
29
30        ``q`` (matches display name / email / id), ``role``, ``sort``
31        (``name`` / ``email`` / ``created`` / ``last_active`` /
32        ``solves`` / ``attempts``) and ``dir`` (``asc`` / ``desc``) are
33        applied **server-side**, before the page is sliced.
34        """
35        params: dict[str, str | int] = {"offset": offset, "limit": limit}
36        params.update(
37            {k: v for k, v in (("q", q), ("role", role), ("sort", sort), ("dir", dir)) if v}
38        )
39        resp = self._http.request("GET", "/admin/users", params=params)
40        _raise_for_status(resp)
41        return [AdminUserInfo.model_validate(u) for u in resp.json()["items"]]
42
43    def get(self, user_id: str) -> AdminUserInfo:
44        """Admin-or-better: single-user lookup with the audit fields
45        (role, promoted_by, promoted_at, current team summary). Skips
46        the list scan :meth:`list` would otherwise force."""
47        resp = self._http.request("GET", f"/admin/users/{user_id}")
48        _raise_for_status(resp)
49        return AdminUserInfo.model_validate(resp.json())
50
51    def set_role(self, user_id: str, role: str) -> AdminUserInfo:
52        """Promote / demote between ``"user"`` and ``"admin"``.
53
54        Requires **super-admin**. ``super_admin`` role is env-anchored and
55        cannot be changed via API; trying returns 403.
56        """
57        resp = self._http.request(
58            "PATCH",
59            f"/admin/users/{user_id}/role",
60            json={"role": role},
61        )
62        _raise_for_status(resp)
63        return AdminUserInfo.model_validate(resp.json())
class AdminUsersResource:
13class AdminUsersResource:
14    """Admin-or-better user listing, lookup and promotion."""
15
16    def __init__(self, http: BaseHttpClient) -> None:
17        self._http = http
18
19    def list(
20        self,
21        offset: int = 0,
22        limit: int = 50,
23        *,
24        q: str = "",
25        role: str = "",
26        sort: str = "",
27        dir: str = "",
28    ) -> builtins.list[AdminUserInfo]:
29        """Users known to the platform, with role + promotion audit.
30
31        ``q`` (matches display name / email / id), ``role``, ``sort``
32        (``name`` / ``email`` / ``created`` / ``last_active`` /
33        ``solves`` / ``attempts``) and ``dir`` (``asc`` / ``desc``) are
34        applied **server-side**, before the page is sliced.
35        """
36        params: dict[str, str | int] = {"offset": offset, "limit": limit}
37        params.update(
38            {k: v for k, v in (("q", q), ("role", role), ("sort", sort), ("dir", dir)) if v}
39        )
40        resp = self._http.request("GET", "/admin/users", params=params)
41        _raise_for_status(resp)
42        return [AdminUserInfo.model_validate(u) for u in resp.json()["items"]]
43
44    def get(self, user_id: str) -> AdminUserInfo:
45        """Admin-or-better: single-user lookup with the audit fields
46        (role, promoted_by, promoted_at, current team summary). Skips
47        the list scan :meth:`list` would otherwise force."""
48        resp = self._http.request("GET", f"/admin/users/{user_id}")
49        _raise_for_status(resp)
50        return AdminUserInfo.model_validate(resp.json())
51
52    def set_role(self, user_id: str, role: str) -> AdminUserInfo:
53        """Promote / demote between ``"user"`` and ``"admin"``.
54
55        Requires **super-admin**. ``super_admin`` role is env-anchored and
56        cannot be changed via API; trying returns 403.
57        """
58        resp = self._http.request(
59            "PATCH",
60            f"/admin/users/{user_id}/role",
61            json={"role": role},
62        )
63        _raise_for_status(resp)
64        return AdminUserInfo.model_validate(resp.json())

Admin-or-better user listing, lookup and promotion.

AdminUsersResource(http: ctfy.sdk.base.BaseHttpClient)
16    def __init__(self, http: BaseHttpClient) -> None:
17        self._http = http
def list( self, offset: int = 0, limit: int = 50, *, q: str = '', role: str = '', sort: str = '', dir: str = '') -> list[ctfy.server.models.AdminUserInfo]:
19    def list(
20        self,
21        offset: int = 0,
22        limit: int = 50,
23        *,
24        q: str = "",
25        role: str = "",
26        sort: str = "",
27        dir: str = "",
28    ) -> builtins.list[AdminUserInfo]:
29        """Users known to the platform, with role + promotion audit.
30
31        ``q`` (matches display name / email / id), ``role``, ``sort``
32        (``name`` / ``email`` / ``created`` / ``last_active`` /
33        ``solves`` / ``attempts``) and ``dir`` (``asc`` / ``desc``) are
34        applied **server-side**, before the page is sliced.
35        """
36        params: dict[str, str | int] = {"offset": offset, "limit": limit}
37        params.update(
38            {k: v for k, v in (("q", q), ("role", role), ("sort", sort), ("dir", dir)) if v}
39        )
40        resp = self._http.request("GET", "/admin/users", params=params)
41        _raise_for_status(resp)
42        return [AdminUserInfo.model_validate(u) for u in resp.json()["items"]]

Users known to the platform, with role + promotion audit.

q (matches display name / email / id), role, sort (name / email / created / last_active / solves / attempts) and dir (asc / desc) are applied server-side, before the page is sliced.

def get(self, user_id: str) -> ctfy.server.models.AdminUserInfo:
44    def get(self, user_id: str) -> AdminUserInfo:
45        """Admin-or-better: single-user lookup with the audit fields
46        (role, promoted_by, promoted_at, current team summary). Skips
47        the list scan :meth:`list` would otherwise force."""
48        resp = self._http.request("GET", f"/admin/users/{user_id}")
49        _raise_for_status(resp)
50        return AdminUserInfo.model_validate(resp.json())

Admin-or-better: single-user lookup with the audit fields (role, promoted_by, promoted_at, current team summary). Skips the list scan list() would otherwise force.

def set_role(self, user_id: str, role: str) -> ctfy.server.models.AdminUserInfo:
52    def set_role(self, user_id: str, role: str) -> AdminUserInfo:
53        """Promote / demote between ``"user"`` and ``"admin"``.
54
55        Requires **super-admin**. ``super_admin`` role is env-anchored and
56        cannot be changed via API; trying returns 403.
57        """
58        resp = self._http.request(
59            "PATCH",
60            f"/admin/users/{user_id}/role",
61            json={"role": role},
62        )
63        _raise_for_status(resp)
64        return AdminUserInfo.model_validate(resp.json())

Promote / demote between "user" and "admin".

Requires super-admin. super_admin role is env-anchored and cannot be changed via API; trying returns 403.