feat(backend): модуль «замена фона» и хранилище своих картинок
Отключаемый в админке модуль `virtual_background` (дефолт — выключен, чтобы
обновление не меняло продукт у тех, кто ничего не просил). Флаг едет клиенту
двумя путями: на публичные страницы входа — через `GET /public/settings`,
участнику комнаты — в join-ответе (`JoinOut`), потому что значение нужно на
руках ДО первого рендера комнаты, а `/admin/settings` доступен только админу.
Свои картинки пользователя (`/users/me/backgrounds`, GET/POST/DELETE):
файлы на диске (`backgrounds/{user_id}/{id}.{ext}`), в БД только путь — как у
аватаров, «чтобы не грузили БД». Лимит в 10 штук проверяется на сервере под
блокировкой строки пользователя: две одновременные загрузки иначе обе увидели
бы «уже девять» и обе прошли бы. Удаление сносит и запись, и файл; чужую
картинку по её id удалить нельзя — владелец в условии запроса.
Валидация загрузки (допустимые форматы, магические байты, реальный размер)
выделена из `services/avatars.py` в общий `services/images.py`: правила у
аватара и фона одни и те же, а разъехавшись, они дали бы дыру ровно там, ради
чего проверка и написана. Публичный API аватаров не изменился.
Сжимает картинку клиент (Pillow на бэкенде нет), но серверная валидация
остаётся полноценной — запрос может прийти и мимо интерфейса.
Новый ключ настройки вписан в `_MANAGED_KEYS` тестов: без этого включённый
в общей dev-БД модуль ронял чужие тесты, которые считают себя изолированными.
This commit is contained in:
@@ -56,6 +56,7 @@ _KEY_CONTACT_EMAIL = "contact_email"
|
||||
_KEY_MEDIA_LIMITS = "media_limits"
|
||||
_KEY_CONSENT_POLICY = "consent_policy"
|
||||
_KEY_DEVICE_CHECK = "device_check"
|
||||
_KEY_VIRTUAL_BACKGROUND = "virtual_background"
|
||||
|
||||
BOOTSTRAP_MANAGED_KEYS: tuple[str, ...] = (
|
||||
_KEY_CHAT,
|
||||
@@ -81,6 +82,10 @@ _DEFAULT_REGISTRATION_EMAIL_DOMAIN_VALUE: dict[str, Any] = {"enabled": False, "d
|
||||
_DEFAULT_CONTACT_EMAIL_VALUE: dict[str, Any] = {"enabled": False, "email": None}
|
||||
_DEFAULT_MEDIA_LIMITS_VALUE: dict[str, Any] = {"publish_quality_cap": "off", "stage_max_tiles": 25}
|
||||
_DEFAULT_DEVICE_CHECK_VALUE = {"enabled": False}
|
||||
_DEFAULT_VIRTUAL_BACKGROUND_VALUE = {"enabled": False}
|
||||
"""Замена фона видео (сессия 35). Дефолт — выключено: фича постоянно считает
|
||||
нейросеть сегментации на клиенте, и включать её самим фактом обновления у тех,
|
||||
кто ничего не просил, нельзя (то же правило, что и у остальных модулей)."""
|
||||
|
||||
DEFAULT_CONSENT_POLICY_TEXT = """Это типовой шаблон для предварительной демонстрации. Текст не проходил проверку юриста и не может использоваться как окончательная редакция без такой проверки. Администратор обязан заменить плейсхолдеры в квадратных скобках и, при необходимости, весь текст — под свою организацию и юрисдикцию.
|
||||
|
||||
@@ -154,6 +159,7 @@ class SettingsUpdateIn(BaseModel):
|
||||
consent_required: bool | None = None
|
||||
consent_policy_text: str | None = None
|
||||
device_check_enabled: bool | None = None
|
||||
virtual_background_enabled: bool | None = None
|
||||
|
||||
|
||||
class BootstrapOverrides(BaseModel):
|
||||
@@ -207,6 +213,7 @@ def build_bootstrap_defaults(
|
||||
_KEY_MEDIA_LIMITS: dict(_DEFAULT_MEDIA_LIMITS_VALUE),
|
||||
_KEY_CONSENT_POLICY: dict(_DEFAULT_CONSENT_POLICY_VALUE),
|
||||
_KEY_DEVICE_CHECK: dict(_DEFAULT_DEVICE_CHECK_VALUE),
|
||||
_KEY_VIRTUAL_BACKGROUND: dict(_DEFAULT_VIRTUAL_BACKGROUND_VALUE),
|
||||
}
|
||||
if overrides is None:
|
||||
return defaults
|
||||
@@ -345,6 +352,10 @@ class InstanceSettingsService:
|
||||
cfg.device_check_enabled = patch.device_check_enabled
|
||||
await self._set(_KEY_DEVICE_CHECK, {"enabled": patch.device_check_enabled})
|
||||
|
||||
if patch.virtual_background_enabled is not None:
|
||||
cfg.virtual_background_enabled = patch.virtual_background_enabled
|
||||
await self._set(_KEY_VIRTUAL_BACKGROUND, {"enabled": patch.virtual_background_enabled})
|
||||
|
||||
if patch.registration_team_choice is not None:
|
||||
cfg.registration_team_choice = patch.registration_team_choice
|
||||
await self._set(
|
||||
@@ -616,4 +627,7 @@ def _build_config(rows: dict[str, Any]) -> InstanceConfig:
|
||||
device_check_enabled=rows.get(_KEY_DEVICE_CHECK, _DEFAULT_DEVICE_CHECK_VALUE).get(
|
||||
"enabled", False
|
||||
),
|
||||
virtual_background_enabled=rows.get(
|
||||
_KEY_VIRTUAL_BACKGROUND, _DEFAULT_VIRTUAL_BACKGROUND_VALUE
|
||||
).get("enabled", False),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user