Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db98405990 | ||
|
|
ccde822899 | ||
|
|
d94d2954b0 | ||
|
|
a904a2beb0 | ||
|
|
bb7063018b | ||
|
|
4a84b442a5 | ||
|
|
8d8361ab8f | ||
|
|
9fb4fc15b9 | ||
|
|
61a2eb8076 | ||
|
|
26111ac632 | ||
|
|
4b64a90839 | ||
|
|
73d16e9111 | ||
|
|
ec60cc1756 |
@@ -1,5 +1,8 @@
|
|||||||
name: Backend CI
|
name: Backend CI
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
name: Frontend CI
|
name: Frontend CI
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- prebuild
|
- prebuild
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
frontend-test-build:
|
frontend-test-build:
|
||||||
name: Frontend (Test & Build)
|
name: Frontend (Test & Build)
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
name: Prebuild Workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- prebuild
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test_build:
|
||||||
|
name: Test build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Try to build project
|
||||||
|
run: docker-compose up --build
|
||||||
|
|
||||||
|
deploy_to_test_server:
|
||||||
|
name: Deploy to test server
|
||||||
|
needs: test_build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Deploy to test server
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.TEST_SERVER_ADDRESS }}
|
||||||
|
username: ${{ secrets.TEST_SERVER_USER }}
|
||||||
|
port: ${{ secrets.TEST_SERVER_PORT }}
|
||||||
|
password: ${{ secrets.TEST_SERVER_PASSWORD }}
|
||||||
|
script: |
|
||||||
|
REPO_NAME=$(basename "${{ github.repository }}")
|
||||||
|
if [ ! -d "$REPO_NAME" ]; then
|
||||||
|
git clone https://github.com/${{ github.repository }}.git $REPO_NAME
|
||||||
|
fi
|
||||||
|
cd $REPO_NAME
|
||||||
|
|
||||||
|
git checkout prebuild
|
||||||
|
git pull
|
||||||
|
|
||||||
|
docker-compose down || true
|
||||||
|
docker-compose up -d --build
|
||||||
@@ -1,31 +1,154 @@
|
|||||||
# this OR that
|
# this OR that | Telegram Mini App
|
||||||
|
|
||||||
Telegram mini-app where you have to choose one of two things.
|
|
||||||
|
|
||||||
## Installation
|
<img width="1447" height="248" alt="thisORthat_logo" src="https://github.com/user-attachments/assets/fee632cf-f778-4a72-95d7-bee2f8602bc0" />
|
||||||
|
|
||||||
1. Installing the repository:
|
|
||||||
```bash
|
## Описание проекта
|
||||||
git clone https://github.com/IgorVolochay/thisORthat
|
|
||||||
|
[«this OR that»](https://t.me/thisorthat_rubot?startapp) — это интерактивное веб-приложение формата Telegram Mini App, предлагающее пользователю сделать выбор между двумя альтернативными вариантами («Это или То»). Сразу после голосования открывается статистика голосов других участников, открывается доступ к обсуждению в комментариях, реакциям (лайки / дизлайки), а также предоставляется возможность создать собственную карточку.
|
||||||
|
|
||||||
|
### Идея и источник вдохновения
|
||||||
|
Вдохновением для проекта послужил русскоязычный ресурс [thisorthat.ru](https://thisorthat.ru), где собраны тысячи вопросов для размышления. Основная цель данного проекта - адаптировать и переосмыслить эту механику в современный экосистемный формат **Telegram Mini App**.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **Это pet-проект.** Проект носит исключительно некоммерческий, учебно-исследовательский характер. Он был создан для отработки и демонстрации практических навыков проектирования асинхронных бэкендов, построения событийно-ориентированной архитектуры (Event-Driven Architecture), интеграции очередей сообщений, работы с Telegram WebApp API и контейнеризации сервисов.
|
||||||
|
|
||||||
|
<img width="1800" height="1244" alt="thisORthat_telegram" src="https://github.com/user-attachments/assets/172e8243-f6da-4dcf-a362-74fa18ce68e9" />
|
||||||
|
|
||||||
|
|
||||||
|
## Технологический стек, подходы и инструменты
|
||||||
|
|
||||||
|
В ходе разработки были применены современные технологии, обеспечивающие высокую производительность, масштабируемость и безопасность:
|
||||||
|
|
||||||
|
### Backend & Асинхронная экосистема
|
||||||
|
* **Python 3.12+**: основной язык разработки сервисов.
|
||||||
|
* **[FastAPI](https://github.com/fastapi/fastapi)**: высокопроизводительный асинхронный веб-фреймворк для реализации REST API.
|
||||||
|
* **[Motor](https://github.com/mongodb/motor)**: асинхронный драйвер для интеграции с базой данных MongoDB.
|
||||||
|
* **[Pydantic v2](https://github.com/pydantic/pydantic)**: строгая валидация входящих и исходящих данных через типизированные схемы.
|
||||||
|
* **[aio-pika](https://github.com/mosquito/aio-pika)**: асинхронный клиент для взаимодействия с брокером сообщений RabbitMQ.
|
||||||
|
* **[aiogram 3](https://github.com/aiogram/aiogram)**: асинхронный фреймворк для Telegram-бота модерации.
|
||||||
|
* **[FastAPI-guard](https://github.com/rennf93/fastapi-guard)**: модуль rate limiting, защита от попыток проникновения, а также временная блокировка подозрительных IP-адресов.
|
||||||
|
* **[Loguru](https://github.com/Delgan/loguru)**: структурированное логирование.
|
||||||
|
* **[Pytest](https://github.com/pytest-dev/pytest)**: тестовый фреймворк для покрытия эндпоинтов, логики карточек и проверки безопасности.
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
* **[React 19](https://github.com/react/react)**: библиотека для построения динамичного пользовательского интерфейса (SPA).
|
||||||
|
* **Telegram WebApp API**: интеграция с окружением мессенджера (Haptic Feedback для тактильного отклика, автоматическая адаптация к системной теме Telegram, SafeArea и управление кнопками).
|
||||||
|
* **Vanilla CSS & Flexbox/Grid**: адаптивная верстка под любые размеры мобильных экранов, плавные микро-анимации, кастомные скроллбары и модальные окна без утяжеления сторонними CSS-библиотеками.
|
||||||
|
|
||||||
|
### Базы данных и очереди
|
||||||
|
* **[MongoDB](https://www.mongodb.com/)**: NoSQL база данных для гибкого хранения карточек, голосов пользователей, профилей и древовидных комментариев.
|
||||||
|
* **[RabbitMQ](https://www.rabbitmq.com/)**: брокер сообщений, обеспечивающий отказоустойчивую буферизацию задач между бэкендом и ботом модерации.
|
||||||
|
|
||||||
|
### Архитектурные подходы и паттерны
|
||||||
|
* **Event-Driven Moderation (EDA)**: отправка предложенных пользователями карточек в очередь сообщений без задержек основного пользовательского API.
|
||||||
|
* **Zero-Trust авторизация через Telegram**: валидация подписи `initData` по алгоритму HMAC-SHA256 с использованием секретного ключа бота.
|
||||||
|
* **Защита API и Rate Limiting**: многоуровневая фильтрация запросов через FastAPI-guard с возвратом специализированных экранов блокировки на фронтенде при превышении лимитов.
|
||||||
|
* **Service-Oriented Architecture (SOA)**: разделение приложения на изолированные контейнеры: БД, брокер, API-сервис и бот.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Архитектура проекта и взаимосвязь компонентов
|
||||||
|
|
||||||
|
Проект построен по сервисной архитектуре, где каждый компонент выполняет строго отведенную роль:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
subgraph Client["Клиентская часть"]
|
||||||
|
TMA["Telegram Mini App (React 19)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph CoreBackend["Бэкенд инфраструктура"]
|
||||||
|
API["FastAPI REST Backend :5000"]
|
||||||
|
Mongo[("MongoDB Database :27017")]
|
||||||
|
RMQ[["RabbitMQ Broker :5672"]]
|
||||||
|
Bot["Telegram Модерация Bot (aiogram 3)"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph TelegramCloud["Инфраструктура Telegram"]
|
||||||
|
TGUser["Пользователь Telegram"]
|
||||||
|
TGAdmin["Администратор в Telegram"]
|
||||||
|
end
|
||||||
|
|
||||||
|
%% Взаимодействия
|
||||||
|
TGUser <--> |Запуск Mini App / initData| TMA
|
||||||
|
TMA <--> |REST API запросы / Голосование / Комментарии| API
|
||||||
|
API <--> |Асинхронные запросы через Motor| Mongo
|
||||||
|
API --> |Публикация новой карточки в очередь moderation| RMQ
|
||||||
|
RMQ --> |Потребление карточки из очереди| Bot
|
||||||
|
Bot --> |Уведомление с кнопками Одобрить / Отклонить| TGAdmin
|
||||||
|
TGAdmin --> |Инлайн-решение| Bot
|
||||||
|
Bot --> |Защищенный вызов API с MODERATION_SECRET| API
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual setup:
|
### Сценарии взаимодействия:
|
||||||
|
1. **Пользовательский сценарий:**
|
||||||
|
- Пользователь открывает Mini App внутри Telegram. Приложение передает `initData`, которая верифицируется бэкендом.
|
||||||
|
- Пользователь получает случайные пары карточек из MongoDB, делает выбор, голосует, оставляет комментарии и реакции.
|
||||||
|
2. **Пайплайн предложения и модерации карточек:**
|
||||||
|
- Пользователь предлагает свою карточку через интерфейс приложения.
|
||||||
|
- Сервис FastAPI сохраняет карточку в MongoDB со статусом ожидания и мгновенно публикует событие в очередь `moderation` брокера **RabbitMQ**.
|
||||||
|
- Сервис **Telegram Bot** (aiogram) слушает очередь, получает карточку и пересылает ее в чат администратора (`TG_ADMIN_CHAT_ID`) с инлайн-кнопками «Одобрить ✅» / «Отклонить ❌».
|
||||||
|
- Администратор принимает решение в Telegram. Бот выполняет защищенный внутренний запрос к API (`/card_accept` или `/card_reject`) с заголовком `MODERATION_SECRET`, после чего статус карточки в базе обновляется.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Краткое руководство по запуску
|
||||||
|
|
||||||
|
Для запуска проекта на локальной машине потребуются установленные **Docker**, **Docker Compose** и **Node.js** (версии 18+).
|
||||||
|
|
||||||
|
### Шаг 1. Конфигурация окружения
|
||||||
|
Создайте файл переменных окружения `.env` в корне проекта на основе образца `.env_example`:
|
||||||
|
|
||||||
2. The project is written in Python3.9. Make sure you have it on your system. Go to the project folder, create a virtual environment and download pip requirements:
|
|
||||||
```bash
|
```bash
|
||||||
cd ./thisORthat
|
cp .env_example .env
|
||||||
python3.9 -m venv venv
|
|
||||||
source ./venv/bin/activate
|
|
||||||
pip3 install -r ./app/requirements.txt
|
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Installing MongoDB database. You can use the [official manual](https://www.mongodb.com/docs/manual/installation/) to install MongoDB manually, or use a [Docker image](https://hub.docker.com/r/mongodb/mongodb-community-server) to run the container:
|
Заполните ключевые параметры в `.env`:
|
||||||
```bash
|
* `TG_BOT_TOKEN` — токен вашего Telegram-бота от [@BotFather](https://t.me/BotFather).
|
||||||
docker run --name mongodb -d -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=user -e MONGO_INITDB_ROOT_PASSWORD=pass mongodb/mongodb-community-server
|
* `TG_ADMIN_CHAT_ID` — ваш Telegram ID или ID чата для модерации карточек.
|
||||||
```
|
* `MODERATION_SECRET` — произвольная секретная строка для взаимодействия между ботом и API.
|
||||||
### Docker Compose setup:
|
* При необходимости скорректируйте учетные данные MongoDB и RabbitMQ. Для локальной разработки без валидации Telegram `DEV_MODE` можно оставить равным `true`.
|
||||||
|
|
||||||
|
### Шаг 2. Сборка фронтенда
|
||||||
|
Соберите статическую версию React-приложения:
|
||||||
|
|
||||||
2. Use docker-compose to automatically build the entire project. For correct build, it is better to use docker-compose version 1.29.2:
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose up --build
|
cd frontend
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
cd ..
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Шаг 3. Запуск сервисов через Docker Compose
|
||||||
|
Запустите сборку и старт всех сервисов в фоновом режиме:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
После завершения запуска будут активны следующие компоненты:
|
||||||
|
* **Backend API:** `http://localhost:5000` (документация Swagger доступна по адресу `http://localhost:5000/docs` при `DEV_MODE=true`).
|
||||||
|
* **Панель RabbitMQ Management:** `http://localhost:15672` (логин и пароль задаются в `.env`).
|
||||||
|
* **MongoDB:** порт `27017`.
|
||||||
|
* **Telegram Bot:** сервис подключится к Telegram и начнет обработку очереди модерации.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Контакты
|
||||||
|
|
||||||
|
* **Автор:** Волочай Игорь (Igor Volochay)
|
||||||
|
* **Telegram:** [@VIAproger](https://t.me/VIAproger)
|
||||||
|
* **Email:** [pseudo.developer.ru@gmail.com](mailto:pseudo.developer.ru@gmail.com)
|
||||||
|
* **GitHub репозиторий:** [https://github.com/IgorVolochay/thisORthat](https://github.com/IgorVolochay/thisORthat)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Благодарности
|
||||||
|
|
||||||
|
### Идейный вдохновитель
|
||||||
|
* **[thisorthat.ru](https://thisorthat.ru)** — оригинальный проект, послуживший источником вдохновения для идеи, концепции дилемм и механики выбора.
|
||||||
|
|
||||||
|
### Open-Source сообщество, библиотеки и фреймворки
|
||||||
|
Выражаю глубокую благодарность разработчикам и мейнтейнерам ключевых библиотек и инструментов, на которых построен проект.
|
||||||
+3
-3
@@ -61,14 +61,14 @@ app: FastAPI = FastAPI(
|
|||||||
)
|
)
|
||||||
config = SecurityConfig(
|
config = SecurityConfig(
|
||||||
enable_rate_limiting=True,
|
enable_rate_limiting=True,
|
||||||
rate_limit=10, # TODO: check rate limits in real usage
|
rate_limit=10,
|
||||||
rate_limit_window=3, # TODO: check rate limits in real usage
|
rate_limit_window=3,
|
||||||
enable_redis=False,
|
enable_redis=False,
|
||||||
enable_ip_banning=True,
|
enable_ip_banning=True,
|
||||||
|
|
||||||
enable_penetration_detection=True,
|
enable_penetration_detection=True,
|
||||||
auto_ban_threshold=3,
|
auto_ban_threshold=3,
|
||||||
auto_ban_duration=3600,
|
auto_ban_duration=600,
|
||||||
|
|
||||||
detection_compiler_timeout=2.0,
|
detection_compiler_timeout=2.0,
|
||||||
detection_max_content_length=10000,
|
detection_max_content_length=10000,
|
||||||
|
|||||||
+2
-1
@@ -67,6 +67,7 @@ services:
|
|||||||
RABBIT_USER: ${RABBIT_USER}
|
RABBIT_USER: ${RABBIT_USER}
|
||||||
RABBIT_PASS: ${RABBIT_PASS}
|
RABBIT_PASS: ${RABBIT_PASS}
|
||||||
MODERATION_SECRET: ${MODERATION_SECRET}
|
MODERATION_SECRET: ${MODERATION_SECRET}
|
||||||
|
TG_BOT_TOKEN: ${TG_BOT_TOKEN}
|
||||||
DEV_MODE: ${DEV_MODE:-false}
|
DEV_MODE: ${DEV_MODE:-false}
|
||||||
LOG_LEVEL: ${LOG_LEVEL:-INFO}
|
LOG_LEVEL: ${LOG_LEVEL:-INFO}
|
||||||
logging:
|
logging:
|
||||||
@@ -92,10 +93,10 @@ services:
|
|||||||
backend:
|
backend:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
environment:
|
environment:
|
||||||
TG_BOT_TOKEN: ${TG_BOT_TOKEN}
|
|
||||||
TG_ADMIN_CHAT_ID: ${TG_ADMIN_CHAT_ID}
|
TG_ADMIN_CHAT_ID: ${TG_ADMIN_CHAT_ID}
|
||||||
API_BASE_URL: http://backend:5000
|
API_BASE_URL: http://backend:5000
|
||||||
MODERATION_SECRET: ${MODERATION_SECRET}
|
MODERATION_SECRET: ${MODERATION_SECRET}
|
||||||
|
TG_BOT_TOKEN: ${TG_BOT_TOKEN}
|
||||||
RABBIT_HOST: rabbitmq
|
RABBIT_HOST: rabbitmq
|
||||||
RABBIT_PORT: "5672"
|
RABBIT_PORT: "5672"
|
||||||
RABBIT_USER: ${RABBIT_USER}
|
RABBIT_USER: ${RABBIT_USER}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import { getTelegramInitData } from './auth';
|
import { getTelegramInitData } from './auth';
|
||||||
|
|
||||||
const BASE_URL = process.env.REACT_APP_API_URL || '';
|
const BASE_URL = process.env.REACT_APP_API_URL || '/api';
|
||||||
|
|
||||||
async function request(method, path, body = null) {
|
async function request(method, path, body = null) {
|
||||||
const headers = {
|
const headers = {
|
||||||
|
|||||||
Reference in New Issue
Block a user