Commit Graph

11 Commits

Author SHA1 Message Date
17437880b1 feat(room): замена фона видео на картинку — только на десктопе
Кнопка «Фон» в тулбаре комнаты и выбор фона в превью на входе: три готовые
сцены и свои картинки из профиля. Фон применяется процессором к самому
публикуемому треку (`LocalVideoTrack.setProcessor`), а НЕ пересозданием
`RoomOptions` — ссылка на них обязана оставаться стабильной, иначе
`LiveKitRoom` переподключается к комнате.

Фича только для ДЕСКТОПА, и «десктоп» определяется по возможностям устройства
(`pointer: fine` + `hover: hover` + `maxTouchPoints`), а НЕ по ширине окна:
узкое окно на десктопе — всё ещё десктоп, а широкий планшет — всё ещё планшет,
который сегментация греет. На мобильном кнопки нет вовсе, а не задизейбленной.

Ассеты сегментации отдаются СО СВОЕГО домена: библиотека по умолчанию тянет
wasm с jsdelivr, а модель с storage.googleapis.com, и в закрытом контуре фича
молча не работала бы. Модель (Apache 2.0, см. NOTICE.txt) лежит в репозитории,
wasm-рантайм (~19 МБ) копируется из node_modules плагином сборки. Сама
библиотека и модель грузятся ЛЕНИВО — только когда фон реально включают, вход
в конференцию не стал медленнее.

Три дефолтные сцены — собственные векторные рисунки (`design/backgrounds/`),
а не фотографии из интернета: у нарисованной сцены нет чужой лицензии, а
продукт расходится по инсталляциям, и проверять права на каждую копию некому.

Свои картинки — в профиле, до 10 штук, с уменьшением до 1280px и переводом в
WebP прямо в браузере перед отправкой. Удаление применённого сейчас фона
сбрасывает выбор на «без фона»: хранится ключ записи, а не URL картинки.

Смена камеры фон не теряет (`restartTrack` перезапускает процессор сам),
выключение и включение камеры — навешивает его на новый трек заново.

⚠️ Прокси dev-сервера для `/media/` — обязательно со слэшем: ключ `/media`
Vite матчит префиксом и перехватывает заодно `/mediapipe/...`, из-за чего
модель получала 404 и фон молча не включался.
2026-08-10 08:59:47 +03:00
7ee68b17ee feat(auth): проверка устройств на входе — запрос доступа и превью камеры
Отключаемый модуль (instance_settings.device_check, дефолт выключен):
запрос доступа к камере/микрофону на LoginPage и в карточке "Как вас
зовут?" (JoinPage), живое зеркальное превью и кнопки вкл/выкл камеры и
микрофона там же. На JoinPage кнопки определяют, с чем гость войдёт в
конференцию (RoomPage.LiveKitRoom audio/video вместо жёстких false) —
на LoginPage только пре-авторизуют разрешение, без UI (карточка ведёт
в лобби, применить выбор некуда). Вход в комнату по умолчанию, как и
раньше, с выключенными микрофоном/камерой.

Публичный GET /api/v1/public/settings отдаёт флаг модуля обеим
страницам до аутентификации.
2026-08-09 18:09:10 +03:00
296ce60c78 fix(auth): не разлогинивать пользователя, когда серверу плохо
Silent-refresh считал неудачей любой не-2xx ответ и на каждую такую
неудачу сбрасывал access-токен с редиректом на /login. Ответ 500 — это
«серверу плохо», а не «вы не авторизованы»: 07.08.2026 refresh отвечал
500 из-за исчерпанного пула БД, и фронтенд разлогинивал людей посреди
работы, а повторный вход падал тем же 500.

`refreshAccessToken` теперь различает причины: `invalid` (backend отверг
сессию — 4xx, единственный случай для разлогина), `unavailable` (5xx,
таймаут, обрыв сети — сессия цела, токен сохраняется, пользователь
получает обычную ошибку запроса) и `ok`. Восстановление сессии при
старте приложения на `unavailable` повторяет попытку трижды с задержками
1/2/4 с, вместо того чтобы сразу объявить пользователя неавторизованным.
2026-08-09 01:06:15 +03:00
4f82ebe17a feat(auth): согласие на обработку персональных данных при регистрации
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
Отключаемый модуль (instance_settings.consent_policy): галочка + ссылка на
публичную страницу регламента на форме регистрации, редактируемый в админке
текст с типовым шаблоном по умолчанию (плейсхолдеры под организацию, не
проходил юридическую проверку), версия текста растёт при каждой правке.
Факт согласия хранится в users (consent_version, consent_given_at) — второй
эшелон проверки на сервере, как и для отключаемых модулей ранее. Дефолт
(выключено) сохраняет поведение существующих инсталляций, у уже
зарегистрированных пользователей согласие не запрашивается.
2026-08-04 22:20:00 +03:00
10a3f8b3b4 feat(room): очередь поднятых рук видна всем + отключаемый модуль
Раньше HandQueueMenu.tsx рендерился только организатору — теперь очередь
видит любой участник, но опустить чужую руку по-прежнему может только
организатор (сервер это уже проверял, менял только фронт). Кнопка
«Опустить» показывается у записи, только если это своя рука или
пользователь — организатор.

Модуль «поднятие руки» (кнопка «Рука» + очередь целиком) — отключаемый
в админке (instance_settings.hand_queue, дефолт enabled=true, как у
chat_enabled). Настройка едет участнику в JoinOut ещё до входа в
комнату; выключенный модуль гасит кнопки и на фронте, и на бэке —
raise_hand/lower_hand отклоняются кодом hand_queue_disabled, если
модуль выключен, даже если у клиента на руках старый JoinOut.
2026-08-04 18:27:14 +03:00
173d384f06 feat(admin): рычаги нагрузки медиа — потолок качества публикации и лимит плиток
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
instance_settings.media_limits (publish_quality_cap: off/720p/360p/180p,
stage_max_tiles: 4/9/16/25) — новая вкладка «Нагрузка» в админке, дефолты
(off/25) сохраняют текущее поведение существующих инсталляций.

Настройка отдаётся не только GET /admin/settings, но и в join-ответе
(JoinOut) — участнику нужно иметь её на руках ДО публикации трека, а
/admin/settings доступен только администратору.

Потолок качества применяется через RoomOptions.publishDefaults
(videoEncoding + videoSimulcastLayers на пресетах VideoPresets LiveKit) —
режет битрейт верхнего слоя симулкаста, реальное разрешение WebRTC
подстраивает сам. Лимит плиток — фильтрация STAGE_GRID_LAYOUTS по
columns*rows в StageGrid, лишние участники уходят на страницу пагинации
вместо подписки.

Значение приезжает в joinState вместе с токеном ДО первого рендера
LiveKitRoom (RoomPage не рендерит его, пока joinState не заполнен целиком),
поэтому смена настройки не переподключает уже вошедшего участника —
roomOptions пересчитывается по стабильной ссылке на joinState, которая
после подключения не меняется.

Проверено вживую на локальном стенде (docker compose --profile media):
сохранение/персист настроек, join отдаёт актуальные значения, уже
подключённый участник не разрывается при смене настройки в другом окне.
2026-08-02 19:53:23 +03:00
4c60e092e5 feat(room): принудительный мьют участника организатором
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
Новый эндпоинт POST /conferences/{id}/mute-participant: права проверяются
ЗАНОВО по владельцу конференции в БД (ConferenceService.mute_participant),
не по метаданным LiveKit-токена вызывающего — те лишь подсказка для UI и
потенциально подделываемы клиентом. Обычный участник получает 403, чужая/
несуществующая конференция — 404, участник не в комнате LiveKit — отдельный
404 (participant_not_in_room).

Само выключение — серверный вызов api.LiveKitAPI (services/room_control.py,
тот же паттерн, что services/egress.py): backend аутентифицируется
СОБСТВЕННЫМИ api_key/api_secret, а не токеном организатора, поэтому
дополнительный LiveKit-грант в токене организатора не нужен — мьютит сервер
от своего имени. Если трек данного source не опубликован (с 0.0.15 участники
заходят с выключенными микрофоном/камерой) — не ошибка, а no-op: искомое
состояние уже достигнуто, ответ muted:false.

Уведомление участника — тот же общий канал комнаты, что и очередь рук
(hand_queue_channel): рассылается всем, получатель сам сверяет identity
(ForcedMuteWatcher, рендерится внутри LiveKitRoom). Само выключение трека
участник видит сразу через штатный useTrackToggle (LiveKit сам присылает
TrackMuted), тост только поясняет причину — иначе не отличить от глюка.
Включить себя обратно можно сразу тем же тулбаром, сервер это не блокирует.

Кнопки — на чужой плитке камеры, видны только организатору по наведению
(на тач-устройствах — всегда, как и булавка закрепления).

Тесты: владелец мьютит успешно и публикует broadcast, уже-выключенный трек —
muted:false без broadcast, администратор мьютит чужую конференцию, обычный
участник получает 403 без обращения к LiveKit, конференция не найдена и
участник не в комнате — соответствующие 404.
2026-08-01 22:07:06 +03:00
09215da22a feat(admin): вкладки фильтра по статусу в списке пользователей
Задача 3 переопределена оператором: вместо окончательного удаления
пользователя (упёрлось в CHECK-constraint'ы participant/chat_messages,
требующие миграции схемы — решили отложить) добавлены вкладки
«Активные»/«Заблокированные»/«Все» перед полем поиска в админке —
список фильтруется по `is_blocked` на бэкенде (GET /admin/users?status=).
2026-07-27 20:59:29 +03:00
25ffd9e678 feat(auth): несколько эталонных mail-доменов для верификации регистрации
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
Настройка «Эталон mail-домена» теперь хранит список доменов вместо
одного — email при регистрации принимается, если совпадает с любым из
них. Старое значение в БД ({"domain": str|None}) читается прозрачно
(обратная совместимость без Alembic-миграции) и переписывается в новую
форму ({"domains": [...]}) при первом же сохранении настроек. В админке
добавление/удаление доменов — списком чипов; на экране регистрации
подсказка о несовпадении домена перечисляет все эталонные варианты.
2026-07-27 20:40:23 +03:00
b35209a775 feat(admin): контактный адрес и тестовое письмо в настройках админки
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
Поле включения/адреса в карточке «Контактный адрес» и кнопка «Отправить
тестовое письмо» с выводом результата — рядом, по образцу соседних
настроек AdminSettingsTab.
2026-07-27 00:35:59 +03:00
896455381a Первоначальная версия VidConf 2026-07-23 01:57:27 +03:00