Skip to content
mivlabsPublic

About

CoWatch - совместный просмотр видео в реальном времени

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

CoWatch

Сервис для совместного просмотра видео с друзьями: общий плеер, синхронизация play/pause/seek, чат и реакции в реальном времени.

cowatch.fun · Быстрый старт · Возможности · Архитектура · Планы


CI FastAPI React TypeScript Redis PostgreSQL


Демо

Проект уже развёрнут и доступен по адресу 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

Как пользоваться

  1. Зайди на cowatch.fun или подними проект локально.
  2. Создай комнату или введи код приглашения.
  3. Хост вставляет ссылку на видео. Остальные участники видят тот же ролик в том же месте таймлайна.

У гостей плеер синхронизирован с хостом, чат открывается сразу после входа в комнату.


Архитектура

Бэкенд разбит на 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
Loading

Стек

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.

1. Клонировать репозиторий

git clone https://github.com/mivlabs/cowatch.git
cd cowatch

2. Запустить сервисы

docker-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/health

3. Запустить фронтенд

cd 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.


API

Метод 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

About

CoWatch - совместный просмотр видео в реальном времени

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages