"""GitHub 组织域的请求 / 响应 schema。""" from __future__ import annotations from pydantic import BaseModel, Field from . import constants as const class GithubOrgCreateRequest(BaseModel): slug: str = Field(..., max_length=const.GITHUB_ORG_SLUG_MAX_LEN) display_name: str = Field(..., max_length=128) description: str | None = None is_community: bool = False offboarding_policy: str = const.OFFBOARDING_POLICY_REMOVE request_enabled: bool = True auto_join_enabled: bool = False default_role: str = const.MEMBERSHIP_ROLE_MEMBER feishu_option_value: str | None = Field(None, max_length=128) integration_id: str | None = None sort_order: int = 0 notes: str | None = None class GithubOrgUpdateRequest(BaseModel): """全部可选 —— PATCH 语义:没给的字段不动。 ``display_name`` 等文本字段区分「没给」与「给了空字符串」:前者 不动,后者对可空列意味着清空。``slug`` 不在这里 —— 改 slug 等于换 一个组织,应该停用旧的、登记新的,而不是就地改名把台账的历史 悄悄挪到另一个组织名下。 """ display_name: str | None = Field(None, max_length=128) description: str | None = None is_community: bool | None = None offboarding_policy: str | None = None request_enabled: bool | None = None auto_join_enabled: bool | None = None default_role: str | None = None feishu_option_value: str | None = Field(None, max_length=128) integration_id: str | None = None sort_order: int | None = None is_active: bool | None = None notes: str | None = None class AutoJoinRuleCreateRequest(BaseModel): org_id: str = Field(..., max_length=64) match_department_path: str | None = Field(None, max_length=512) match_job_title: str | None = Field(None, max_length=128) match_location: str | None = Field(None, max_length=64) is_active: bool = True notes: str | None = None class AutoJoinRuleUpdateRequest(BaseModel): """全可选,PATCH 语义。``org_id`` 不在这里 —— 改目标组织等于换一条 规则,应该删掉重建,否则台账里指向它的成员记录会突然「属于」另一个 组织的规则。""" match_department_path: str | None = Field(None, max_length=512) match_job_title: str | None = Field(None, max_length=128) match_location: str | None = Field(None, max_length=64) is_active: bool | None = None notes: str | None = None class ReconcileRequest(BaseModel): """``apply_removals=False`` = 存量导入模式:只加不删。 默认 True(例行对账)。把默认设成 False 会让手工触发的对账悄悄 变成一个只会让台账变大的操作 —— 而对账存在的一半理由正是收掉 那些已经不在 GitHub 上的记录。 """ apply_removals: bool = True class MembershipRemoveRequest(BaseModel): """管理员手工把一条成员资格移出组织。 ``reason`` 必填。手工移除是这套东西里唯一没有上游凭据的写动作 —— 审批有单号、离职有流程、自动加入有规则,只有它全靠人的判断。事后 有人问「这个人为什么被踢出去了」时,台账上得有个答案。 """ reason: str = Field(..., min_length=2, max_length=255) class MembershipLinkRequest(BaseModel): """把一条「还没认出是谁」的成员资格关联到某个人(或解绑)。 ``person_id=None`` 是解绑 —— 认错了要能改回来。 """ person_id: str | None = Field(None, max_length=64) class MembershipExternalRequest(BaseModel): """确认「这个 handle 不是我们的员工」。 ``note`` 可选但强烈建议:半年后再看这一行,「ByteDance 的人」和一个 空白的确认标记,在可信度上不是一回事。 """ note: str | None = Field(None, max_length=255) class MembershipExternalKindRequest(BaseModel): """确认「这不是我们的员工」时,顺便说清是哪一种。 ``kind`` 不填就是「没说」——那仍然是一次有效的确认(它已经离开待办), 只是分组统计里落在「未区分」。刻意不强制:让人为了点一下确认而先想清 分类,只会让他干脆不点。 """ note: str | None = Field(None, max_length=255) kind: str | None = Field(None, max_length=16) class BotPolicyPatternsRequest(BaseModel): """机器账号的命名策略。 空列表会被拒绝 —— 一个空策略静默地什么都不匹配,而那看起来和 「没有机器账号」一模一样。 """ patterns: list[str] = Field(..., min_length=1) class BotPolicyApplyRequest(BaseModel): """按策略批量标记为机器账号。 ``handles`` 给定时只处理这些(界面上勾选的那几个);不给就处理策略 命中的全部。**刻意要求显式调用**,不做定时自动标记:判定来自命名约定, 而 ``robot-liu`` 这种真名会命中 ``*robot*`` —— 把一个员工标成机器账号, 后果是他离职时不被回收。 """ handles: list[str] | None = None # ``default=None`` 写成关键字而不是位置参数:mypy 的 pydantic 插件不把 # ``Field(None, ...)`` 的位置默认值当默认值,于是 ``BotPolicyApplyRequest()`` # 会被报成缺少必填参数。 note: str | None = Field(default=None, max_length=255) class LabelUpsertRequest(BaseModel): """新建或改一个标签。新建要很容易 —— 否则人就绕过词表去写自由备注了。""" key: str = Field(..., min_length=2, max_length=64) category: str = Field(..., min_length=2, max_length=24) display_zh: str = Field(..., min_length=1, max_length=64) display_en: str = Field(..., min_length=1, max_length=64) color: str = Field(default="default", max_length=16) description: str | None = None sort_order: int = 0 is_active: bool = True class HandleLabelRequest(BaseModel): """给一个 handle 打 / 摘标签。 注意作用对象是 **handle**,不是某一条成员资格 —— 一次生效跨它所在的 所有组织。 """ github_login: str = Field(..., min_length=1, max_length=64) label_keys: list[str] = Field(..., min_length=1) note: str | None = Field(default=None, max_length=255) class LabelRulesRequest(BaseModel): """标签建议规则。``match`` 支持 ``handle_glob`` 与 ``profile_company_contains``。""" rules: list[dict] = Field(...)