- JavaScript 50.5%
- Python 37.2%
- CSS 10.8%
- Shell 1.1%
- Dockerfile 0.2%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
All checks were successful
CI/CD / Build and push backend image (push) Successful in 16s
CI/CD / Build and push bot image (push) Successful in 12s
CI/CD / Build and push frontend image (push) Successful in 10s
CI/CD / Deploy to dev-1 (push) Successful in 7s
CI/CD / Deploy to dev (push) Successful in 0s
CI/CD / Deploy to prod-1 (push) Has been skipped
CI/CD / Deploy to prod (push) Successful in 0s
|
||
| .forgejo/workflows | ||
| .idea | ||
| backend | ||
| bot | ||
| frontend | ||
| scripts | ||
| traefik | ||
| .env.example | ||
| .gitignore | ||
| DEPLOY.md | ||
| docker-compose.prod.yml | ||
| docker-compose.yml | ||
| LICENSE | ||
| README.md | ||
🏠 Кампус-отели Диск и Облако — поиск соседей по комнате
Сервис для поиска соседей в комнату кампус-отеля (на 2–4 человека). Показывает анкеты студентов с фото и краткой информацией, а связаться можно прямо в Telegram.
Стек
- Бэкенд: Python, FastAPI, SQLAlchemy
- База данных: PostgreSQL
- Фронтенд: React, Vite (в проде — сборка отдаётся через nginx)
- Инфраструктура: Docker, Docker Compose
Возможности
- Выбор пола на входе: парни живут с парнями, девушки — с девушками, поэтому
первым экраном спрашивается пол, и лента показывает только «своих». Выбор
сохраняется в
localStorageи меняется кнопкой «Сменить» в шапке - Фото: загружается файлом (не ссылкой). Бэкенд проверяет, что это картинка, ужимает до 800px по большей стороне и сохраняет как JPEG
- Аватар из Telegram: можно подставить фото и ник из профиля Telegram — через Login Widget (обычный веб) или Mini App SDK (сайт открыт внутри Telegram). Требует токен бота, см. раздел ниже
- Три раздела: «Ищут соседей» (кто ещё сам по себе), «Комнаты» (кто уже
договорился) и «Блок». Комната — просто группа людей, без номеров и секций:
видно состав, сколько мест занято (
2/3) и сколько свободно. Двое сошлись — собирают комнату на 3 и ищут третьего; человек из комнаты пропадает из раздела одиночек - Блоки: в «Диске» комнаты объединяются попарно в блок на 6 человек — 2+4 или 3+3. Объединяются целыми комнатами: одна зовёт, и согласиться должны все жильцы позванной. Комната в блоке не меняет размер (иначе в блоке перестанет быть 6), а выход одной комнаты распускает блок целиком. В «Облаке» блоков нет, и вкладка там не показывается
- Вступление только по согласию: никто не заходит в комнату сам. Летит заявка, и её должны подтвердить все, кто уже внутри. Любой отказ — заявка отклонена
- Бот в Telegram пишет про новые заявки (с кнопками «Принять / Отклонить»), про решение по твоей заявке и про выход соседа из комнаты
- Лента анкет карточками: фото, имя, направление, о себе. Направление — одно из
пяти: Разработка, Бизнес, Дизайн, ИИ, Не определился. Возраст и курс не
собираются и не показываются (колонки
age/courseоставлены в БД nullable — если понадобятся, вернуть легко) - Метки: режим сна (сова/жаворонок), курение, чистоплотность, желаемый размер комнаты. Размеров можно указать несколько — «хочу 3 или 4, но не двухместную»; ничего не выбрано — подойдёт любая
- Фильтры: размер комнаты, режим сна, курение + поиск по тексту
- Кнопка «Написать в Telegram» ведёт на
t.me/username - Форма «Разместить анкету» — добавление своего профиля (пол подставляется автоматически)
- Темы: светлая и тёмная, по умолчанию — системная. Переключатель в шапке
циклом: Системная → Светлая → Тёмная. Выбор хранится в
localStorage, в режиме «Системная» приложение следит за сменой темы ОС на лету
Деплой на сервер
Разворачивание на VPS с доменом и HTTPS — отдельная инструкция: DEPLOY.md.
Там прод-конфиг docker-compose.prod.yml: наружу торчит только Caddy с
автоматическим сертификатом, база и бэкенд закрыты.
Запуск через Docker (рекомендуется)
Всё поднимается одной командой — Postgres, бэкенд и фронтенд:
docker compose -p obshaga up -d --build
Флаг
-p obshagaзадаёт имя проекта, потому что имя папки кириллическое и Docker его не принимает как имя проекта по умолчанию.
После старта:
- Приложение — http://localhost:5173
- API и Swagger — http://localhost:8000/docs
- Postgres — на хосте проброшен на порт 5433 (внутри сети —
db:5432)
При первом старте бэкенд дожидается готовности БД и создаёт таблицы. Демо-анкет
нет — сервис стартует пустым, первые анкеты появляются, когда их разместят люди.
Данные хранятся в volume pgdata и переживают перезапуск контейнеров.
Полезные команды:
docker compose -p obshaga logs -f backend # логи бэкенда
docker compose -p obshaga down # остановить (данные сохранятся)
docker compose -p obshaga down -v # остановить и стереть данные БД
Telegram: бот и аватарки
Куда класть токен
Скопируй пример и заполни — docker compose подхватит .env сам,
файл уже в .gitignore:
cp .env.example .env
TELEGRAM_BOT_TOKEN=123456789:AA... # от @BotFather
TELEGRAM_BOT_USERNAME=my_campus_bot # ник бота без @
BOT_SECRET=<длинная случайная строка> # openssl rand -hex 32
SITE_URL=https://твой-домен # для ссылок в сообщениях бота
Запуск вместе с ботом (бот под профилем, чтобы без токена не мешал):
docker compose -p obshaga --profile bot up -d --build
docker compose -p obshaga logs -f bot
BOT_SECRET — общий секрет бота и бэкенда. Бот действует от имени
Telegram-пользователя, поэтому служебные ручки /api/bot/* закрыты этим
заголовком: без него голосовать за других смог бы кто угодно.
Как включить уведомления (со стороны студента)
- разместить анкету на сайте, указав свой ник;
- написать боту
/start— он найдёт анкету по нику (или по подтверждённому Telegram-аккаунту) и запомнит чат.
Шаг 2 обязателен: бот не может написать первым тому, кто не начал с ним
диалог — это ограничение Telegram, а не проекта. Пока человек не нажал
/start, уведомления ему просто не уходят, сайт при этом работает как обычно.
Команды бота: /start — включить уведомления, /pending — заявки, ждущие
твоего решения.
Аватарки из профиля
Без этих настроек кнопка входа через Telegram просто не показывается, а загрузка фото файлом работает как обычно.
Важно понимать ограничение: получить аватар по @username нельзя. Bot API
отдаёт фото только тем, кто уже писал боту, и ищет по user_id, а не по нику.
Поэтому аватар берётся только после того, как человек сам подтвердит вход.
1. Завести бота
В @BotFather: /newbot → получаешь токен вида
123456789:AA.... Затем /setdomain → указываешь домен сайта.
2. Передать токен
export TELEGRAM_BOT_TOKEN="123456789:AA..."
export TELEGRAM_BOT_USERNAME="my_campus_bot"
docker compose -p obshaga up -d
3. Что заработает
- Login Widget — на обычном сайте появится кнопка «Log in with Telegram».
Работает только на домене из
/setdomain: наlocalhostTelegram кнопку не покажет, отсюда нужен боевой домен с HTTPS. - Mini App — если открыть сайт внутри Telegram (кнопка у бота через
/setmenubuttonили inline-кнопкаweb_app), приложение само предложит «Взять фото и ник из Telegram». Нужен публичный HTTPS-адрес.
Как это защищено
Telegram подписывает данные HMAC-SHA256 на основе токена бота, и сервер проверяет подпись сам — схемы у Login Widget и Mini App разные и не взаимозаменяемы. Подпись старше суток отклоняется.
Галочку «подтверждён» ставит только сервер после повторной проверки подписи при создании анкеты: клиент не может объявить себя подтверждённым, а ник берётся из подтверждённых данных, чтобы нельзя было указать чужой.
Аватар скачивается и сохраняется у нас — ссылки t.me/i/userpic/... живут недолго.
Запуск без Docker (для разработки)
Нужен запущенный PostgreSQL. Строка подключения задаётся переменной
DATABASE_URL (по умолчанию postgresql+psycopg2://obshaga:obshaga@localhost:5432/obshaga).
1. Бэкенд (порт 8000)
cd backend
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
export DATABASE_URL=postgresql+psycopg2://obshaga:obshaga@localhost:5432/obshaga
uvicorn main:app --reload --port 8000
2. Фронтенд (порт 5173)
cd frontend
npm install
npm run dev
Открыть http://localhost:5173 . В dev-режиме запросы к /api проксируются
на бэкенд (настроено в vite.config.js).
API
| Метод | Путь | Описание |
|---|---|---|
| GET | /api/profiles |
Список анкет (+ фильтры в query) |
| GET | /api/profiles/{id} |
Одна анкета |
| POST | /api/profiles |
Создать анкету |
| GET | /api/groups |
Комнаты (+ gender, only_open) |
| GET | /api/groups/{id} |
Одна комната с составом |
| POST | /api/groups |
Собрать комнату |
| POST | /api/groups/{id}/leave |
Выйти |
| GET | /api/groups/{id}/requests |
Заявки в эту комнату |
| POST | /api/groups/{id}/requests |
Подать заявку |
| POST | /api/requests/{id}/vote |
Голос жильца (за/против) |
| POST | /api/requests/{id}/cancel |
Отозвать свою заявку |
| GET | /api/profiles/{id}/requests |
Мои заявки |
| GET | /api/blocks |
Собранные блоки (+ gender, campus) |
| GET | /api/groups/{id}/block-candidates |
Комнаты, с которыми выйдет полный блок |
| GET | /api/groups/{id}/block-requests |
Заявки на блок этой комнаты |
| POST | /api/blocks/requests |
Позвать комнату в блок |
| POST | /api/blocks/requests/{id}/vote |
Голос жильца позванной комнаты |
| POST | /api/blocks/requests/{id}/cancel |
Отозвать предложение |
| POST | /api/blocks/{id}/leave |
Выйти из блока своей комнатой |
| POST | /api/uploads/photo |
Загрузить фото (multipart) |
| GET | /api/media/{file} |
Отдача загруженных фото |
| POST | /api/auth/telegram |
Вход через Login Widget |
| POST | /api/auth/telegram/webapp |
Вход из Mini App (initData) |
| GET | /api/config |
Включён ли Telegram, лимит фото |
| GET | /api/health |
Проверка статуса |
Фильтры для GET /api/profiles: gender, track, room_capacity, smoking,
sleep_schedule, search, without_group (true — только те, кто ещё не в
комнате). room_capacity — одно число: подходят те, кто выбрал этот размер
среди желаемых, и те, кому размер не важен.
track — одно из dev | business | design | ai | undecided; русские
подписи живут на фронтенде (src/labels.js). search ищет по имени и «о себе»:
направление теперь выбирают фильтром, а не текстом.
Правила комнат и заявок
Проверяются на сервере:
- вступить напрямую нельзя — только заявка;
- заявку должны подтвердить все, кто сейчас в комнате; любой отказ — отклонение;
- голосовать могут только жильцы этой комнаты (
403), сам заявитель — не может; - в комнату пускают только свой пол (
403); - нельзя состоять в двух комнатах сразу (
409), нельзя подать вторую заявку в ту же комнату (409); - места перепроверяются в момент одобрения: пока голосовали, их могли занять;
- голос вышедшего перестаёт учитываться — иначе заявка висела бы вечно;
- когда выходит последний жилец, комната удаляется.
Правила блоков
- блоки есть только в «Диске» (
campuses.WITH_BLOCKS), размер блока — 6 человек; - пара подбирается по размеру комнат: 2+4 или 3+3, третью комнату не добавить;
- предложение должны принять все жильцы позванной комнаты; любой отказ — отклонение (то же правило, что у заявок в комнату);
- пол и кампус-отель у комнат должны совпадать;
- комната в блоке не меняет размер (
409) — иначе в блоке перестанет быть 6; - пара перепроверяется в момент одобрения: пока голосовали, размер комнаты могли изменить или она успела собрать блок с другими;
- выход одной комнаты распускает блок целиком — половина блока смысла не имеет;
- когда комната распадается, её блок распускается вместе с ней.
Авторизации в проекте нет, поэтому «моя анкета» — это её id в localStorage,
он запоминается после создания анкеты. Этого достаточно, чтобы вступать и
выходить, но постороннему ничто не мешает отправить чужой profile_id напрямую
в API — для настоящего продакшена сюда нужна авторизация.
Структура
общага/
├── docker-compose.yml # Postgres + бэкенд + фронтенд
├── backend/
│ ├── main.py # FastAPI-приложение и эндпоинты
│ ├── models.py # SQLAlchemy-модель Profile
│ ├── schemas.py # Pydantic-схемы
│ ├── database.py # подключение к Postgres + ожидание готовности БД
│ ├── requirements.txt
│ └── Dockerfile
└── frontend/
├── src/
│ ├── App.jsx
│ ├── api.js
│ ├── labels.js
│ ├── index.css
│ └── components/
│ ├── RoommateCard.jsx
│ ├── Filters.jsx
│ └── AddProfileModal.jsx
├── index.html
├── vite.config.js
├── nginx.conf # прод-конфиг: статика + прокси /api
└── Dockerfile # сборка Vite → nginx