ForcePilot/backend/package/yuxi/external_systems/integrations/zendesk/metadata.py

111 lines
4.1 KiB
Python
Raw Normal View History

"""Zendesk 厂商集成元数据。
定义 ``IntegrationMetadata(key="zendesk", ...)`` ``__init__.py`` 自注册到
``IntegrationRegistry``元数据为内存态不持久化到数据库
同时提供
- ``ZendeskConnectionConfig``Zendesk 连接配置 Schema ``subdomain`` / ``base_url``
供前端动态表单与校验
- ``resolve_oauth_endpoints``根据 ``base_url`` 拼接 Zendesk 实例级 OAuth 端点
handler OAuth2 模式下注入到 ``auth_config``
认证设计见设计文档 §3
- ``oauth2_authorization_code``复用框架级插件handler 内通过
``resolve_oauth_endpoints`` 注入 ``token_url``
- ``basic``API Token 模式复用框架级 ``BasicAuthPlugin``
``username="{email}/token"````password=api_token``
"""
from __future__ import annotations
from pydantic import BaseModel, Field, model_validator
from yuxi.external_systems.integrations.schemas import IntegrationMetadata
ZENDESK_METADATA = IntegrationMetadata(
key="zendesk",
display_name="Zendesk",
description="客服 / 工单平台,基于 Support REST API 集成",
adapter_type="http",
source_types=["zendesk"],
supported_auth_types=[
"oauth2_authorization_code", # 标准 OAuth2
"basic", # API Token 模式username="{email}/token", password=api_token
],
tags=["customer_support", "ticketing", "crm"],
icon="zendesk",
docs_url="https://developer.zendesk.com/api-reference/",
connection_extra_schema={
"type": "object",
"properties": {
"subdomain": {"type": "string", "description": "Zendesk 子域名,如 acme"},
"base_url": {
"type": "string",
"description": "完整 base_url优先级高于 subdomain",
},
},
},
example_config={
"connection_config": {
"subdomain": "acme",
},
"auth_config": {
"auth_type": "basic",
"username": "agent@acme.com/token",
"password": "ref://zendesk/acme/api_token",
},
},
dependency_note=(
"1. 调用账号需对 tickets / users / organizations / ticket_fields / "
"user_fields / organization_fields / search 具备 READ 权限;"
"2. OAuth2 应用需在 Zendesk 后台注册并配置回调地址;"
"3. API Token 在 Zendesk 后台 Admin > Channels > API 中生成,"
"auth_type=basicusername='{email}/token'password=api_token"
"4. api_token 建议使用 ref:// 密钥引用;"
"5. 通用 API 限流 OAuth2 700/min、basic 200/min搜索 API 30/min。"
),
)
class ZendeskConnectionConfig(BaseModel):
"""Zendesk 连接配置 Schema供前端动态表单与校验
``base_url`` 优先级高于 ``subdomain``支持自定义域名场景两者至少填写一项
仅填写 ``subdomain`` 时自动拼接为 ``https://{subdomain}.zendesk.com``
"""
subdomain: str | None = Field(
default=None,
description="Zendesk 子域名,如 acme对应 https://acme.zendesk.com",
)
base_url: str | None = Field(
default=None,
description="完整 base_url优先级高于 subdomain用于自定义域名或私有部署",
)
@model_validator(mode="after")
def _ensure_url(self) -> ZendeskConnectionConfig:
if not self.base_url and not self.subdomain:
raise ValueError("subdomain 与 base_url 至少填写一项")
if not self.base_url:
self.base_url = f"https://{self.subdomain}.zendesk.com"
return self
def resolve_oauth_endpoints(base_url: str) -> dict[str, str]:
"""根据 base_url 拼接 Zendesk 实例级 OAuth 端点。
Args:
base_url: Zendesk 实例 base_url ``https://acme.zendesk.com``
Returns:
``authorize_url`` / ``token_url`` 的字典 handler 注入到
``auth_config`` 后传给框架级 OAuth2 插件消费
"""
return {
"authorize_url": f"{base_url}/oauth/authorizations/new",
"token_url": f"{base_url}/oauth/tokens",
}