ctfy.sdk.admin_resources.recap
client.admin.recap — a competition's recap for its organisers (#3023):
the figures, what the organisers stated for it, publishing it, and the
report email to the organising committee.
1"""``client.admin.recap`` — a competition's recap for its organisers (#3023): 2the figures, what the organisers stated for it, publishing it, and the 3report email to the organising committee.""" 4 5from __future__ import annotations 6 7from collections.abc import Sequence 8from typing import Any 9 10from ctfy.core.state.models import RecapMailLanguage 11from ctfy.sdk._helpers import _raise_for_status 12from ctfy.sdk.base import BaseHttpClient 13from ctfy.server.models import ( 14 AwardBandInfo, 15 CompetitionRecapMail, 16 CompetitionRecapMailUpdate, 17 CompetitionRecapSettings, 18 CompetitionRecapSettingsUpdate, 19 Recap, 20 RecapMailPreview, 21 RecapSendInfo, 22) 23 24 25class AdminRecapResource: 26 """Read and run one competition's recap. For its organisers (global admins 27 and the event's own competition admins).""" 28 29 def __init__(self, http: BaseHttpClient) -> None: 30 self._http = http 31 32 def get(self, competition_id: str) -> Recap: 33 """The recap now: reach, contest, integrity, platform and what needs 34 attention. The figures are the public board's (frozen, anonymised).""" 35 resp = self._http.request("GET", f"/admin/competitions/{competition_id}/recap") 36 _raise_for_status(resp) 37 return Recap.model_validate(resp.json()) 38 39 def settings(self, competition_id: str) -> CompetitionRecapSettings: 40 """The registration target, previous edition, prize tiers and whether 41 the recap is published.""" 42 resp = self._http.request("GET", f"/admin/competitions/{competition_id}/recap-settings") 43 _raise_for_status(resp) 44 return CompetitionRecapSettings.model_validate(resp.json()) 45 46 def update_settings( 47 self, 48 competition_id: str, 49 *, 50 registration_target: int | None = None, 51 previous_edition_id: str | None = None, 52 award_bands: Sequence[AwardBandInfo] | None = None, 53 published: bool | None = None, 54 ) -> CompetitionRecapSettings: 55 """Set any of them; ``None`` leaves a field as stored. 56 57 ``previous_edition_id=""`` clears the link. ``published=True`` is 58 refused with ``recap_not_ended`` (409) until the event has ended; 59 ``False`` withdraws it in any phase. 60 """ 61 body = CompetitionRecapSettingsUpdate( 62 registration_target=registration_target, 63 previous_edition_id=previous_edition_id, 64 award_bands=None if award_bands is None else list(award_bands), 65 recap_published=published, 66 ) 67 resp = self._http.request( 68 "PUT", 69 f"/admin/competitions/{competition_id}/recap-settings", 70 json=body.model_dump(mode="json", exclude_none=True), 71 ) 72 _raise_for_status(resp) 73 return CompetitionRecapSettings.model_validate(resp.json()) 74 75 def mail(self, competition_id: str) -> CompetitionRecapMail: 76 """The report email's switch, language, recipients (masked), the 77 report due now and the last sends.""" 78 resp = self._http.request("GET", f"/admin/competitions/{competition_id}/recap-mail") 79 _raise_for_status(resp) 80 return CompetitionRecapMail.model_validate(resp.json()) 81 82 def update_mail( 83 self, 84 competition_id: str, 85 *, 86 enabled: bool | None = None, 87 language: RecapMailLanguage | None = None, 88 add_recipients: Sequence[str] = (), 89 remove_recipients: Sequence[str] = (), 90 ) -> CompetitionRecapMail: 91 """Switch the email on or off, choose its language, add addresses 92 whole and remove them by the ``id`` :meth:`mail` lists.""" 93 body = CompetitionRecapMailUpdate( 94 enabled=enabled, 95 language=language, 96 add_recipients=list(add_recipients) or None, 97 remove_recipients=list(remove_recipients) or None, 98 ) 99 payload: dict[str, Any] = body.model_dump(mode="json", exclude_none=True) 100 resp = self._http.request( 101 "PUT", f"/admin/competitions/{competition_id}/recap-mail", json=payload 102 ) 103 _raise_for_status(resp) 104 return CompetitionRecapMail.model_validate(resp.json()) 105 106 def preview_mail(self, competition_id: str) -> RecapMailPreview: 107 """The mail :meth:`send_mail` would send, rendered now. Its link is a 108 placeholder: a preview mints no credential.""" 109 resp = self._http.request("GET", f"/admin/competitions/{competition_id}/recap-mail/preview") 110 _raise_for_status(resp) 111 return RecapMailPreview.model_validate(resp.json()) 112 113 def send_mail(self, competition_id: str) -> RecapSendInfo: 114 """Mail the recap to every recipient now. 409 ``recap_mail_no_recipients`` 115 or ``recap_mail_too_soon`` (``detail.retry_after_s``).""" 116 resp = self._http.request("POST", f"/admin/competitions/{competition_id}/recap-mail/send") 117 _raise_for_status(resp) 118 return RecapSendInfo.model_validate(resp.json()) 119 120 def revoke_links(self, competition_id: str) -> CompetitionRecapMail: 121 """Stop every read-only link this competition's emails have carried.""" 122 resp = self._http.request( 123 "POST", f"/admin/competitions/{competition_id}/recap-mail/revoke-links" 124 ) 125 _raise_for_status(resp) 126 return CompetitionRecapMail.model_validate(resp.json())
26class AdminRecapResource: 27 """Read and run one competition's recap. For its organisers (global admins 28 and the event's own competition admins).""" 29 30 def __init__(self, http: BaseHttpClient) -> None: 31 self._http = http 32 33 def get(self, competition_id: str) -> Recap: 34 """The recap now: reach, contest, integrity, platform and what needs 35 attention. The figures are the public board's (frozen, anonymised).""" 36 resp = self._http.request("GET", f"/admin/competitions/{competition_id}/recap") 37 _raise_for_status(resp) 38 return Recap.model_validate(resp.json()) 39 40 def settings(self, competition_id: str) -> CompetitionRecapSettings: 41 """The registration target, previous edition, prize tiers and whether 42 the recap is published.""" 43 resp = self._http.request("GET", f"/admin/competitions/{competition_id}/recap-settings") 44 _raise_for_status(resp) 45 return CompetitionRecapSettings.model_validate(resp.json()) 46 47 def update_settings( 48 self, 49 competition_id: str, 50 *, 51 registration_target: int | None = None, 52 previous_edition_id: str | None = None, 53 award_bands: Sequence[AwardBandInfo] | None = None, 54 published: bool | None = None, 55 ) -> CompetitionRecapSettings: 56 """Set any of them; ``None`` leaves a field as stored. 57 58 ``previous_edition_id=""`` clears the link. ``published=True`` is 59 refused with ``recap_not_ended`` (409) until the event has ended; 60 ``False`` withdraws it in any phase. 61 """ 62 body = CompetitionRecapSettingsUpdate( 63 registration_target=registration_target, 64 previous_edition_id=previous_edition_id, 65 award_bands=None if award_bands is None else list(award_bands), 66 recap_published=published, 67 ) 68 resp = self._http.request( 69 "PUT", 70 f"/admin/competitions/{competition_id}/recap-settings", 71 json=body.model_dump(mode="json", exclude_none=True), 72 ) 73 _raise_for_status(resp) 74 return CompetitionRecapSettings.model_validate(resp.json()) 75 76 def mail(self, competition_id: str) -> CompetitionRecapMail: 77 """The report email's switch, language, recipients (masked), the 78 report due now and the last sends.""" 79 resp = self._http.request("GET", f"/admin/competitions/{competition_id}/recap-mail") 80 _raise_for_status(resp) 81 return CompetitionRecapMail.model_validate(resp.json()) 82 83 def update_mail( 84 self, 85 competition_id: str, 86 *, 87 enabled: bool | None = None, 88 language: RecapMailLanguage | None = None, 89 add_recipients: Sequence[str] = (), 90 remove_recipients: Sequence[str] = (), 91 ) -> CompetitionRecapMail: 92 """Switch the email on or off, choose its language, add addresses 93 whole and remove them by the ``id`` :meth:`mail` lists.""" 94 body = CompetitionRecapMailUpdate( 95 enabled=enabled, 96 language=language, 97 add_recipients=list(add_recipients) or None, 98 remove_recipients=list(remove_recipients) or None, 99 ) 100 payload: dict[str, Any] = body.model_dump(mode="json", exclude_none=True) 101 resp = self._http.request( 102 "PUT", f"/admin/competitions/{competition_id}/recap-mail", json=payload 103 ) 104 _raise_for_status(resp) 105 return CompetitionRecapMail.model_validate(resp.json()) 106 107 def preview_mail(self, competition_id: str) -> RecapMailPreview: 108 """The mail :meth:`send_mail` would send, rendered now. Its link is a 109 placeholder: a preview mints no credential.""" 110 resp = self._http.request("GET", f"/admin/competitions/{competition_id}/recap-mail/preview") 111 _raise_for_status(resp) 112 return RecapMailPreview.model_validate(resp.json()) 113 114 def send_mail(self, competition_id: str) -> RecapSendInfo: 115 """Mail the recap to every recipient now. 409 ``recap_mail_no_recipients`` 116 or ``recap_mail_too_soon`` (``detail.retry_after_s``).""" 117 resp = self._http.request("POST", f"/admin/competitions/{competition_id}/recap-mail/send") 118 _raise_for_status(resp) 119 return RecapSendInfo.model_validate(resp.json()) 120 121 def revoke_links(self, competition_id: str) -> CompetitionRecapMail: 122 """Stop every read-only link this competition's emails have carried.""" 123 resp = self._http.request( 124 "POST", f"/admin/competitions/{competition_id}/recap-mail/revoke-links" 125 ) 126 _raise_for_status(resp) 127 return CompetitionRecapMail.model_validate(resp.json())
Read and run one competition's recap. For its organisers (global admins and the event's own competition admins).
33 def get(self, competition_id: str) -> Recap: 34 """The recap now: reach, contest, integrity, platform and what needs 35 attention. The figures are the public board's (frozen, anonymised).""" 36 resp = self._http.request("GET", f"/admin/competitions/{competition_id}/recap") 37 _raise_for_status(resp) 38 return Recap.model_validate(resp.json())
The recap now: reach, contest, integrity, platform and what needs attention. The figures are the public board's (frozen, anonymised).
40 def settings(self, competition_id: str) -> CompetitionRecapSettings: 41 """The registration target, previous edition, prize tiers and whether 42 the recap is published.""" 43 resp = self._http.request("GET", f"/admin/competitions/{competition_id}/recap-settings") 44 _raise_for_status(resp) 45 return CompetitionRecapSettings.model_validate(resp.json())
The registration target, previous edition, prize tiers and whether the recap is published.
47 def update_settings( 48 self, 49 competition_id: str, 50 *, 51 registration_target: int | None = None, 52 previous_edition_id: str | None = None, 53 award_bands: Sequence[AwardBandInfo] | None = None, 54 published: bool | None = None, 55 ) -> CompetitionRecapSettings: 56 """Set any of them; ``None`` leaves a field as stored. 57 58 ``previous_edition_id=""`` clears the link. ``published=True`` is 59 refused with ``recap_not_ended`` (409) until the event has ended; 60 ``False`` withdraws it in any phase. 61 """ 62 body = CompetitionRecapSettingsUpdate( 63 registration_target=registration_target, 64 previous_edition_id=previous_edition_id, 65 award_bands=None if award_bands is None else list(award_bands), 66 recap_published=published, 67 ) 68 resp = self._http.request( 69 "PUT", 70 f"/admin/competitions/{competition_id}/recap-settings", 71 json=body.model_dump(mode="json", exclude_none=True), 72 ) 73 _raise_for_status(resp) 74 return CompetitionRecapSettings.model_validate(resp.json())
Set any of them; None leaves a field as stored.
previous_edition_id="" clears the link. published=True is
refused with recap_not_ended (409) until the event has ended;
False withdraws it in any phase.
76 def mail(self, competition_id: str) -> CompetitionRecapMail: 77 """The report email's switch, language, recipients (masked), the 78 report due now and the last sends.""" 79 resp = self._http.request("GET", f"/admin/competitions/{competition_id}/recap-mail") 80 _raise_for_status(resp) 81 return CompetitionRecapMail.model_validate(resp.json())
The report email's switch, language, recipients (masked), the report due now and the last sends.
83 def update_mail( 84 self, 85 competition_id: str, 86 *, 87 enabled: bool | None = None, 88 language: RecapMailLanguage | None = None, 89 add_recipients: Sequence[str] = (), 90 remove_recipients: Sequence[str] = (), 91 ) -> CompetitionRecapMail: 92 """Switch the email on or off, choose its language, add addresses 93 whole and remove them by the ``id`` :meth:`mail` lists.""" 94 body = CompetitionRecapMailUpdate( 95 enabled=enabled, 96 language=language, 97 add_recipients=list(add_recipients) or None, 98 remove_recipients=list(remove_recipients) or None, 99 ) 100 payload: dict[str, Any] = body.model_dump(mode="json", exclude_none=True) 101 resp = self._http.request( 102 "PUT", f"/admin/competitions/{competition_id}/recap-mail", json=payload 103 ) 104 _raise_for_status(resp) 105 return CompetitionRecapMail.model_validate(resp.json())
Switch the email on or off, choose its language, add addresses
whole and remove them by the id mail() lists.
107 def preview_mail(self, competition_id: str) -> RecapMailPreview: 108 """The mail :meth:`send_mail` would send, rendered now. Its link is a 109 placeholder: a preview mints no credential.""" 110 resp = self._http.request("GET", f"/admin/competitions/{competition_id}/recap-mail/preview") 111 _raise_for_status(resp) 112 return RecapMailPreview.model_validate(resp.json())
The mail send_mail() would send, rendered now. Its link is a
placeholder: a preview mints no credential.
114 def send_mail(self, competition_id: str) -> RecapSendInfo: 115 """Mail the recap to every recipient now. 409 ``recap_mail_no_recipients`` 116 or ``recap_mail_too_soon`` (``detail.retry_after_s``).""" 117 resp = self._http.request("POST", f"/admin/competitions/{competition_id}/recap-mail/send") 118 _raise_for_status(resp) 119 return RecapSendInfo.model_validate(resp.json())
Mail the recap to every recipient now. 409 recap_mail_no_recipients
or recap_mail_too_soon (detail.retry_after_s).
121 def revoke_links(self, competition_id: str) -> CompetitionRecapMail: 122 """Stop every read-only link this competition's emails have carried.""" 123 resp = self._http.request( 124 "POST", f"/admin/competitions/{competition_id}/recap-mail/revoke-links" 125 ) 126 _raise_for_status(resp) 127 return CompetitionRecapMail.model_validate(resp.json())
Stop every read-only link this competition's emails have carried.