Files
chatballs/apps/backend/hub_platform/support/serializers.py
T
Andrey 092553941f feat(support): verify product support token and start session
SPEC-HUB-0011 §12 (алгоритм start) + §14 (error model).

- token.py: HS256 JWT verify через stdlib (hmac/hashlib/base64), envelope claims
  (iss/aud/contract/iat/exp/jti/data). RS256/EdDSA — TODO.
- jsonpath.py: ограниченный JSONPath-subset ($.field, $.a.b, $.arr[0].field).
- session.py: verify_and_resolve (channel policy → token → contract → schema →
  identity/operator/ai/search extraction) + start_support_session (snapshot upsert,
  create-or-continue conversation, audit success/denied). Raw token не сохраняется
  и не логируется — только хэш jti.
- errors.py: машинные коды (TOKEN_*, CONTRACT_*, PAYLOAD_SCHEMA_INVALID, ...) +
  SupportSessionError; публичное сообщение безопасное.
- services.py: register_contract / set_contract_status (зеркало products).
- selectors.py / serializers.py / views.py / urls.py: CRUD контрактов (IsOwner),
  POST /api/v1/support/sessions/ (AllowAny, продукт→Hub), snapshot-by-subject.
- Публичный error envelope {error, message} для start (как OrderIngestView).
2026-07-09 00:55:03 +03:00

43 lines
1.8 KiB
Python

from __future__ import annotations
from hub_platform.support.models import ProductSupportContract, SupportIdentitySnapshot
def support_contract_payload(contract: ProductSupportContract) -> dict[str, object]:
return {
"id": contract.id,
"code": contract.code,
"version": contract.version,
"status": contract.status,
"product": {"code": contract.product.code, "name": contract.product.name},
"allowedChannels": [
{"id": ch.id, "code": ch.code, "name": ch.name}
for ch in contract.allowed_channels.all()
],
"schemaJson": contract.schema_json,
"identityMappingJson": contract.identity_mapping_json,
"operatorUiJson": contract.operator_ui_json,
"aiContextJson": contract.ai_context_json,
"searchMappingJson": contract.search_mapping_json,
"sensitiveFieldsJson": contract.sensitive_fields_json,
"createdAt": contract.created_at.isoformat(),
"updatedAt": contract.updated_at.isoformat(),
}
def support_identity_snapshot_payload(snapshot: SupportIdentitySnapshot) -> dict[str, object]:
# payload_json НЕ отдаётся — внутреннее; оператор получает operator_context_json.
return {
"id": snapshot.id,
"product": {"code": snapshot.product.code, "name": snapshot.product.name},
"contractCode": snapshot.contract_code,
"subjectKey": snapshot.subject_key,
"accountKey": snapshot.account_key,
"displayName": snapshot.display_name,
"displayEmail": snapshot.display_email,
"operatorContextJson": snapshot.operator_context_json,
"aiContextJson": snapshot.ai_context_json,
"searchText": snapshot.search_text,
"verifiedAt": snapshot.verified_at.isoformat(),
}