Files
chatballs/apps/backend/chatballs/platform/operator_models.py
T
AndreyandClaude Opus 5 e088ead06f 📝 docs: идентификаторы проектных документов приведены к CHATBALLS
Документация переработана: действующее отделено от истории, отменённые
контуры (продажи, биллинг, managed AI, отделы, сущность Product) убраны из
действующих документов в архив.

Здесь — только кодовая часть: ссылки на документы в комментариях. Ссылки на
действующие документы переименованы ADR/SPEC/ARCH/BUS-HUB-NNNN →
*-CHATBALLS-NNNN (274 ссылки в 173 файлах). Ссылки на документы, ушедшие в
архив, намеренно сохранили прежний идентификатор: он совпадает с именем
архивного файла.

Логика не менялась — правки только в комментариях, докстрингах и одном
описании теста.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 18:13:33 +03:00

53 lines
1.6 KiB
Python

from __future__ import annotations
from django.db import models
class PlatformOperator(models.Model):
"""Global platform principal (ADR-CHATBALLS-0031 §9). Not derived from tenant
membership; never becomes an Organization OWNER. Authenticated via
PlatformToken (machine-to-machine)."""
name = models.CharField(max_length=128)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["name"]
def __str__(self) -> str:
return self.name
class PlatformToken(models.Model):
"""Opaque API token stored as a sha256 hash (mirror identity invitation tokens).
Plaintext is returned only once at creation time by the token service."""
operator = models.ForeignKey(
PlatformOperator,
on_delete=models.CASCADE,
related_name="tokens",
)
name = models.CharField(max_length=128)
token_hash = models.CharField(max_length=128, unique=True)
capabilities = models.JSONField(default=list, blank=True)
last_used_at = models.DateTimeField(null=True, blank=True)
revoked_at = models.DateTimeField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["-created_at"]
constraints = [
models.CheckConstraint(
condition=models.Q(token_hash__isnull=False) & ~models.Q(token_hash=""),
name="platform_token_hash_present",
),
]
def __str__(self) -> str:
return f"{self.operator.name}:{self.name}"
@property
def is_revoked(self) -> bool:
return self.revoked_at is not None