Files
vidconf/backend/models/user_background.py
Max Ronzhin fec9255baa 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-БД модуль ронял чужие тесты, которые считают себя изолированными.
2026-08-10 08:59:16 +03:00

46 lines
2.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Модель UserBackground — своя картинка пользователя для замены фона видео.
В БД хранится только путь к файлу относительно `MEDIA_ROOT`
(`backgrounds/{user_id}/{background_id}.{ext}`) — ровно тот же приём, что и у
аватаров (`users.avatar_path`, `services/avatars.py`): «чтобы не грузили БД»
(требование оператора). Сами файлы лежат на диске в томе `media`, который
nginx раздаёт напрямую по `location /media/`.
Лимит на число картинок (`MAX_BACKGROUNDS_PER_USER`) проверяется в сервисе, а
не ограничением БД: он про политику продукта, а не про целостность данных, и
администратор может захотеть его поменять.
"""
import uuid
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Index, String, func, text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column
from models.base import Base
class UserBackground(Base):
"""Загруженная пользователем картинка фона."""
__tablename__ = "user_backgrounds"
__table_args__ = (
# Выборка всегда одна и та же — «все фоны этого пользователя, свежие
# сверху» (`services/backgrounds.py::list_backgrounds`), и она же
# считает лимит при загрузке.
Index("ix_user_backgrounds_user_created", "user_id", "created_at"),
)
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, server_default=text("gen_random_uuid()")
)
user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False
)
# Путь относительно `MEDIA_ROOT`: `backgrounds/{user_id}/{id}.{ext}`.
path: Mapped[str] = mapped_column(String(512), nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)