Сервис для совместного просмотра видео с друзьями: общий плеер, синхронизация play/pause/seek, чат и реакции в реальном времени.
cowatch.fun · Быстрый старт · Возможности · Архитектура · Планы
Проект уже развёрнут и доступен по адресу https://cowatch.fun.
Для пользователей из России: сайт может не открываться или работать нестабильно без VPN. Это ограничение связано с доступностью хостинга и внешних сервисов, а не с блокировкой со стороны проекта.
Можно создать комнату, отправить другу 6-буквенный код и смотреть YouTube, Rutube или прямую ссылку на видео вместе.
| Синхронный плеер | Воспроизведение, пауза и перемотка синхронизируются у всех участников через WebSocket |
| Разные источники | YouTube, Rutube, прямые ссылки на видеофайлы |
| Чат | Сообщения в комнате во время просмотра |
| Реакции | Эмодзи поверх видео |
| Роли host / guest | Управление плеером и смена видео только у хоста |
| Гостевой вход | Можно зайти с ником без регистрации |
| Регистрация | Email, пароль, JWT |
| Комнаты | Приватные комнаты по коду, до 50 участников |
| Real-time | Redis Pub/Sub для рассылки событий |
| Ачивки и история просмотра | Кросс-сервисные события в RabbitMQ (создание/вход в комнату, сообщения, законченный просмотр) → notifications считает пороги и выдаёт ачивки через auth |
| Telegram-бот и Mini App | Вход без пароля по подписи initData, комнаты из бота (/new, /join), приглашения в любой чат через inline-режим, уведомления хосту о гостях и о новых наклейках. Подробнее: services/telegram_bot/README.md |
- Зайди на cowatch.fun или подними проект локально.
- Создай комнату или введи код приглашения.
- Хост вставляет ссылку на видео. Остальные участники видят тот же ролик в том же месте таймлайна.
У гостей плеер синхронизирован с хостом, чат открывается сразу после входа в комнату.
Бэкенд разбит на 5 независимых микросервисов: auth, rooms, messages, recommendations, notifications.
У каждого своя выделенная база Postgres (auth_db, rooms_db, messages_db, recommendations_db,
notifications_db) — сервисы не лезут друг другу в таблицы напрямую. События в комнатах (чат, видео,
реакции) идут через WebSocket и Redis Pub/Sub. Кросс-сервисная система ачивок работает отдельно, через
события в RabbitMQ: rooms и messages публикуют доменные события (создание комнаты, вход в комнату,
отправленное сообщение, законченный просмотр), notifications их слушает, проверяет условия ачивок и
выдаёт их через внутренний HTTP-эндпоинт auth. Шестой сервис — Telegram-бот (aiogram): он не
трогает базы, а ходит в auth и rooms по HTTP и слушает те же события из RabbitMQ, чтобы писать людям
в Telegram. Mini App — часть фронтенда (/tg), открывается внутри Telegram.
flowchart TB
subgraph Client["Frontend (React + Vite)"]
UI[RoomPage / VideoPlayer]
TMA["Telegram Mini App (/tg)"]
WS_CLIENT[WebSocket Client]
end
subgraph Telegram["Telegram"]
TGBOT["Telegram Bot (aiogram) :8006"]
end
subgraph Backend["Backend (FastAPI)"]
AUTH["Auth Service :8001"]
ROOMS["Rooms Service :8003"]
MSG["Messages Service :8002"]
RECS["Recommendations Service :8005"]
NOTIF["Notifications Service :8004"]
end
subgraph Infra["Инфраструктура"]
PG[(PostgreSQL 16 — auth_db / rooms_db / messages_db / recommendations_db / notifications_db)]
REDIS[(Redis 7)]
RMQ[(RabbitMQ 3 — cowatch.events)]
end
UI --> AUTH
UI --> ROOMS
WS_CLIENT <-->|"ws://rooms/ws/{code}"| ROOMS
AUTH --> PG
ROOMS --> PG
ROOMS --> REDIS
MSG --> PG
MSG --> REDIS
RECS --> PG
TMA -->|"POST /auth/telegram (initData)"| AUTH
TMA --> ROOMS
TGBOT -->|"/internal/telegram/*"| AUTH
TGBOT --> ROOMS
RMQ -->|"room.joined / achievement.granted"| TGBOT
NOTIF -->|"achievement.granted"| RMQ
ROOMS -->|"room.created / room.joined / video.watch_completed"| RMQ
MSG -->|"message.sent"| RMQ
RMQ --> NOTIF
NOTIF -->|"POST /internal/achievements/grant, /internal/history/record"| AUTH
NOTIF --> PG
Frontend: React 19, TypeScript, Vite, Tailwind CSS, Framer Motion, React Query, React Router, React Player
Backend: Python 3.11, FastAPI, SQLAlchemy 2.0 (async), PostgreSQL, Redis, JWT
События и ачивки: RabbitMQ (topic exchange cowatch.events, aio-pika) — rooms/messages публикуют
события, notifications их слушает и идемпотентно выдаёт ачивки через внутренний API auth
(защищён общим секретом INTERNAL_API_SECRET, не JWT)
Telegram: aiogram 3, Mini Apps SDK (telegram-web-app.js), long polling или вебхук
Инфра: Docker Compose
Нужны Docker Desktop, Node.js 20+ и Git.
git clone https://github.com/mivlabs/cowatch.git
cd cowatchdocker-compose up -d postgres redis rabbitmq
docker-compose up -d auth rooms messages notificationsПроверка:
curl http://localhost:8001/health
curl http://localhost:8003/health
curl http://localhost:8004/healthcd frontend
npm install
npm run devФронтенд будет на http://localhost:5173.
Файл frontend/.env (если нужен):
VITE_AUTH_URL=http://localhost:8001
VITE_API_URL=http://localhost:8003
VITE_WS_URL=ws://localhost:8003Для Telegram (необязательно): TELEGRAM_BOT_TOKEN в .env рядом с docker-compose.yml (его читают auth и бот),
VITE_TELEGRAM_BOT_USERNAME во frontend/.env — имя бота для ссылок-приглашений из Mini App.
Бот поднимается отдельным профилем: docker compose --profile telegram up -d telegram_bot.
Настройка в BotFather и переменные — в services/telegram_bot/README.md.
cowatch/
├── frontend/ # React SPA (+ Telegram Mini App в src/telegram, маршрут /tg)
├── services/
│ ├── auth/ # Регистрация, логин, гостевой JWT, ачивки, watch-history
│ ├── rooms/ # Комнаты, WebSocket, синхронизация видео, события в RabbitMQ
│ ├── messages/ # Сервис сообщений, события в RabbitMQ
│ ├── notifications/ # Слушает cowatch.events, выдаёт ачивки через internal API auth
│ ├── telegram_bot/ # Telegram-бот: команды, inline-приглашения, уведомления из RabbitMQ
│ └── gateway/ # WIP: единая точка входа, ещё не реализован
├── tests/ # pytest для auth/rooms/messages/notifications
├── infra/postgres/ # init-скрипт БД для docker-compose
├── docker-compose.yml
└── README.md
gatewayпока присутствует вdocker-compose.ymlкак заготовка (папка, Dockerfile, пустойapp/main.py) — реальный трафик через него не идёт, фронтенд ходит вauth/rooms/messagesнапрямую по портам 8001–8003.notifications(порт 8004) с ачивками уже боевой — просто у него нет собственного публичного REST API, кроме/health: он только слушает RabbitMQ и зовёт auth.
| Метод | Endpoint | Описание |
|---|---|---|
POST |
/auth/register |
Регистрация |
POST |
/auth/login |
Вход |
POST |
/auth/guest?username=… |
Гостевой вход |
POST |
/auth/telegram |
Вход из Telegram Mini App: тело {init_data}, подпись проверяется токеном бота |
POST |
/rooms/ |
Создать комнату |
GET |
/rooms/{code} |
Получить комнату |
POST |
/rooms/{code}/join |
Присоединиться к комнате |
PATCH |
/rooms/{code}/video |
Сменить видео (только хост) |
WS |
/rooms/ws/{code}?token=… |
Чат, видео-события, реакции |
GET |
/auth/profile/{user_id} |
Ачивки, история просмотра, total_movies/total_hours |
POST |
/internal/achievements/grant |
Только для notifications, секрет X-Internal-Secret |
POST |
/internal/history/record |
Только для notifications, секрет X-Internal-Secret |
POST |
/internal/telegram/token |
Только для бота: JWT пользователя по telegram_id, секрет X-Internal-Secret |
GET |
/internal/telegram/users?user_ids=… |
Только для бота: кто из пользователей привязан к Telegram |
Типы WebSocket-событий: chat_message, video_play, video_pause, video_seek, video_end, video_changed, video_reaction, connected, system.
video_play/video_pause/video_state (из Redis, т.е. состояние плеера комнаты) и video_end (от плеера зрителя)
используются rooms для трекинга личного времени просмотра (per user, per room) — по ним считается video.watch_completed.
Ключ ачивки — code (см. SEED_ACHIEVEMENTS в services/auth/app/services/achievement_service.py),
пороги — в services/notifications/app/services/rules.py. Профиль отдаёт всю коллекцию:
у неполученных наклеек unlocked_at: null, фронт показывает их приглушёнными с подсказкой.
| Группа | Ачивка | code |
Условие |
|---|---|---|---|
| Зал | Первый шаг | first_step |
Регистрация |
| Зал | Хозяин вечеринки | first_room |
Создание первой комнаты |
| Зал | Гостеприимный | hospitable |
В твою комнату впервые зашёл гость по коду |
| Зал | Полный кинозал | full_house |
В твоей комнате 5+ зрителей одновременно |
| Зал | Киноклуб | film_club |
10 созданных комнат |
| Зал | Душа компании | social_butterfly |
Присоединение к 5 разным комнатам |
| Зал | Завсегдатай | regular |
Присоединение к 25 разным комнатам |
| Общение | Болтун | chatterbox |
100 сообщений в чате комнат (chat_message по WS rooms) |
| Общение | Комментатор | commentator |
1000 сообщений в чате комнат |
| Общение | Эмоции через край | emotions |
50 реакций во время просмотра |
| Просмотр | Первый киносеанс | first_session |
Один просмотр до конца (video_end от плеера зрителя) |
| Просмотр | Двойной сеанс | double_feature |
Два просмотра до конца за один день (по времени зрителя) |
| Просмотр | На бис | encore |
Один и тот же фильм досмотрен дважды |
| Просмотр | Полуночник | night_owl |
Просмотр до конца закончился в 00:00–04:59 по времени зрителя |
| Просмотр | Марафонец | marathon |
10+ часов просмотра суммарно |
| Просмотр | Киноман | cinephile |
50+ часов просмотра суммарно |
События для ачивок публикует rooms в cowatch.events: room.created, room.joined,
message.sent, reaction.sent, video.watch_completed (с флагом completed: true —
плеер дошёл до конца, false — зритель вышел из комнаты раньше; часы и история пишутся в обоих случаях).
Гости (/auth/guest) не получают ачивки и историю — у них нет строки в таблице users
(UserAchievement/WatchHistory ссылаются на users.id через FK), так что
/internal/* эндпоинты тихо игнорируют неизвестный user_id. Это осознанное решение,
а не забытый баг — см. services/auth/app/services/achievement_service.py.
- Личный кабинет на фронтенде для ачивок и истории просмотра (бэкенд уже отдаёт
/auth/profile/{user_id}) - Публичные комнаты
- Поиск фильмов и сериалов прямо в интерфейсе
- Мобильная версия сайта или отдельное приложение (Telegram Mini App уже есть)
- API Gateway как единая точка входа
- Уведомления при приглашении в комнату на сайте (в Telegram уже есть: бот пишет хосту, когда кто-то зашёл)
- Более точная синхронизация с учётом сетевой задержки
pytest гоняется отдельно на каждый сервис (auth, rooms, messages, notifications —
все называют свой корневой пакет app, поэтому им нужен разный PYTHONPATH;
scripts/test.sh уже это учитывает). Нужны поднятые Postgres, Redis и RabbitMQ:
docker-compose up -d postgres redis rabbitmq
pip install -r requirements-dev.txt
pip install -r services/auth/requirements.txt # для конкретного сервиса
./scripts/test.sh auth # или без аргумента — все сразуrooms тестируется против настоящего Postgres, а не sqlite: Room.id — колонка
типа UUID, специфичная для диалекта postgresql, на sqlite такая таблица просто
не создастся.
notifications тестируется против настоящего RabbitMQ, а не мока aio-pika: главный
риск в системе ачивок — разъехавшиеся exchange/routing key/формат сообщения между
publisher (rooms/messages) и consumer, и это именно то, что мок скрыл бы, а не поймал.
Его тесты дополнительно поднимают auth как второе FastAPI-приложение в том же процессе
(см. tests/test_notifications/conftest.py) и подменяют только транспорт HTTP-вызова
в auth на ASGITransport — без реального сокета, но с настоящим запросом/ответом.
CI (.github/workflows/ci.yml) поднимает postgres/redis/rabbitmq как сервис-контейнеры и
гоняет тот же scripts/test.sh по каждому сервису в матрице, плюс ruff check и
сборку фронтенда.
Pull request'ы и issue приветствуются. Перед PR стоит описать задачу в issue и проверить сборку: npm run build.
MIT