159 lines
6.2 KiB
Python
159 lines
6.2 KiB
Python
"""Pydantic-схемы админ-API (`/api/v1/admin/*`)."""
|
||
|
||
import uuid
|
||
from datetime import datetime
|
||
from typing import Literal
|
||
|
||
from pydantic import BaseModel, ConfigDict, EmailStr, Field
|
||
|
||
from core.plugins.config import AiLevel, SummaryRecipientsMode
|
||
from schemas.conferences import ConferenceOut
|
||
from services.ai_levels import AiLevelStatus
|
||
|
||
|
||
class AdminConferenceOut(ConferenceOut):
|
||
"""Конференция в ответах админ-API — `ConferenceOut` + данные владельца.
|
||
|
||
`owner_name`/`owner_email` — `None` для конференции без владельца
|
||
(`conferences.owner_id IS NULL`, ADR-001) — колонка «Владелец» в таблице
|
||
админки (`frontend/src/api/admin.ts`).
|
||
"""
|
||
|
||
owner_name: str | None = None
|
||
owner_email: str | None = None
|
||
|
||
|
||
class AdminConferenceListOut(BaseModel):
|
||
"""Страница списка конференций (`GET /admin/conferences`)."""
|
||
|
||
items: list[AdminConferenceOut]
|
||
total: int
|
||
|
||
|
||
class AdminUserOut(BaseModel):
|
||
"""Пользователь в ответах админ-API (профиль + служебные поля модерации).
|
||
|
||
`avatar_url`/`team_name` заполняются явно роутером (не через
|
||
`from_attributes` — оба поля вычисляемые, не хранятся как атрибут ORM),
|
||
см. `api/admin.py::_to_admin_user_out`.
|
||
"""
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
id: uuid.UUID
|
||
email: str
|
||
name_user: str
|
||
role: str
|
||
is_blocked: bool
|
||
email_verified: bool
|
||
created_at: datetime
|
||
team_id: uuid.UUID | None = None
|
||
avatar_url: str | None = None
|
||
team_name: str | None = None
|
||
|
||
|
||
class AdminUserListOut(BaseModel):
|
||
"""Страница списка пользователей (`GET /admin/users`)."""
|
||
|
||
items: list[AdminUserOut]
|
||
total: int
|
||
|
||
|
||
class AdminUserCreateIn(BaseModel):
|
||
"""Тело создания пользователя администратором (`POST /admin/users`).
|
||
|
||
Та же политика пароля, что при самостоятельной регистрации
|
||
(`RegisterIn.password`, min 8 символов). `team_id` — опциональная
|
||
привязка к команде, в отличие от публичной регистрации не завязана на
|
||
настройку `registration_team_choice` (админ назначает команду всегда).
|
||
"""
|
||
|
||
name_user: str = Field(min_length=1, max_length=255)
|
||
email: EmailStr
|
||
password: str = Field(min_length=8)
|
||
team_id: uuid.UUID | None = None
|
||
|
||
|
||
class AdminUserUpdateIn(BaseModel):
|
||
"""Тело правки пользователя администратором — роль, блокировка, ФИО и/или команда.
|
||
|
||
`team_id` не различает «поле не передано» и «явный `null`» через
|
||
сравнение с `None` — роутер читает `model_fields_set`, чтобы явный сброс
|
||
команды (`{"team_id": null}`) отличался от отсутствия поля в запросе
|
||
(тот же паттерн, что `summary_recipients` в `schemas/conferences.py`).
|
||
`name_user` — та же правка, что доступна пользователю в своём профиле
|
||
(редактирование по тем же правилам).
|
||
"""
|
||
|
||
role: Literal["admin", "user"] | None = None
|
||
is_blocked: bool | None = None
|
||
team_id: uuid.UUID | None = None
|
||
name_user: str | None = Field(default=None, min_length=1, max_length=255)
|
||
|
||
|
||
class TeamOut(BaseModel):
|
||
"""Команда в ответах админ-API."""
|
||
|
||
model_config = ConfigDict(from_attributes=True)
|
||
|
||
id: uuid.UUID
|
||
name: str
|
||
created_at: datetime
|
||
|
||
|
||
class TeamListOut(BaseModel):
|
||
"""Список команд (`GET /admin/teams`), отсортирован по названию."""
|
||
|
||
items: list[TeamOut]
|
||
total: int
|
||
|
||
|
||
class TeamCreateIn(BaseModel):
|
||
"""Тело создания команды; `name` обрезается от пробелов до проверки длины."""
|
||
|
||
model_config = ConfigDict(str_strip_whitespace=True)
|
||
|
||
name: str = Field(min_length=1, max_length=255)
|
||
|
||
|
||
class TeamUpdateIn(BaseModel):
|
||
"""Тело переименования команды; `name` обрезается от пробелов до проверки длины."""
|
||
|
||
model_config = ConfigDict(str_strip_whitespace=True)
|
||
|
||
name: str = Field(min_length=1, max_length=255)
|
||
|
||
|
||
class InvitationsSendIn(BaseModel):
|
||
"""Тело ручной рассылки .ics-приглашений (`POST /admin/conferences/{id}/invitations`).
|
||
|
||
Пустой список получателей не отличается от отсутствующего поля — оба
|
||
трактуются как «получатели по умолчанию» (см. `workers/tasks/invitations.py`).
|
||
"""
|
||
|
||
emails: list[EmailStr] | None = None
|
||
|
||
|
||
class SettingsOut(BaseModel):
|
||
"""Эффективные настройки инстанса для отображения в админке.
|
||
|
||
`transcription_queue_served` — обслуживается ли очередь `transcription`
|
||
хотя бы одним воркером Celery
|
||
прямо сейчас (`services.pipeline_producer.transcription_queue_served`);
|
||
`False` при включённой транскрибации — сигнал админке показать
|
||
предупреждение рядом с чекбоксом AI («модуль включён, но задачи некому
|
||
обрабатывать»), не связано с доступностью уровня AI (`ai_levels`,
|
||
которая смотрит только на железо/скачанные модели).
|
||
"""
|
||
|
||
chat_enabled: bool
|
||
transcription_enabled: bool
|
||
ai_level: AiLevel
|
||
ai_levels: list[AiLevelStatus]
|
||
transcription_queue_served: bool
|
||
summary_recipients: SummaryRecipientsMode
|
||
display_timezone: str
|
||
registration_team_choice: bool
|
||
registration_email_domain_enabled: bool
|
||
registration_email_domain: str | None = None
|