Skip to content

Commit 6308acb

Browse files
authored
Merge pull request #5 from SantaRiver/develop
Refactor frontend structure and enhance deployment workflows
2 parents c30f658 + af4afe4 commit 6308acb

33 files changed

Lines changed: 528 additions & 206 deletions

.env.example

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
# Reference template.
2+
# Use `.env.local.example` for local development
3+
# and `.env.server.example` for server deployment.
14
POSTGRES_USER=santa
25
POSTGRES_PASSWORD=santa_pass
36
POSTGRES_DB=santagym
4-
BOT_TOKEN=YOUR_BOT_TOKEN_HERE
5-
JWT_SECRET=generate_strong_secret_key_here
6-
# В прод окружении тут будет реальный домен
7-
WEBHOOK_URL=https://api.santagym.local/api/v1/webhook
8-
9-
FRONTEND_URL=https://gym.santariver.lol
7+
JWT_SECRET=change_me
8+
FRONTEND_URL=http://localhost:5173
9+
ALLOW_TEST_AUTH=false
10+
DEBUG=false

.env.local.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
POSTGRES_USER=santa
2+
POSTGRES_PASSWORD=santa_pass
3+
POSTGRES_DB=santagym
4+
JWT_SECRET=local-dev-secret
5+
ALLOW_TEST_AUTH=true
6+
FRONTEND_URL=http://localhost:5173
7+
DEBUG=true
8+
CHOKIDAR_USEPOLLING=true
9+
WATCHPACK_POLLING=true

.env.server.example

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
POSTGRES_USER=santa
2+
POSTGRES_PASSWORD=change_me
3+
POSTGRES_DB=santagym
4+
JWT_SECRET=change_me
5+
BOT_TOKEN=telegram_prod_bot_token
6+
WEBAPP_BASE_URL=https://gym.example.com
7+
WEBHOOK_URL=https://api.gym.example.com/api/v1/webhook
8+
FRONTEND_URL=https://gym.example.com
9+
FRONTEND_HOST=gym.example.com
10+
API_HOST=api.gym.example.com
11+
TRAEFIK_ACME_EMAIL=admin@example.com
12+
DEBUG=false

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ jobs:
9393
- name: Validate docker compose config
9494
run: docker compose config
9595

96+
- name: Validate local compose config
97+
run: docker compose --env-file .env.local.example -f docker-compose.yml -f docker-compose.local.yml config
98+
99+
- name: Validate server compose config
100+
run: docker compose --env-file .env.server.example -f docker-compose.yml -f docker-compose.server.yml config
101+
96102
- name: Build backend image
97103
run: docker build -f backend/Dockerfile backend
98104

.github/workflows/deploy.yml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
host: ${{ secrets.HOST }}
3333
username: ${{ secrets.USERNAME }}
3434
key: ${{ secrets.SSH_KEY }}
35-
source: "backend,frontend,docker-compose.yml,.env.example"
35+
source: "backend,frontend,docker-compose.yml,docker-compose.server.yml,.env.server.example"
3636
target: "/opt/santagym"
3737
rm: false
3838

@@ -45,18 +45,18 @@ jobs:
4545
script: |
4646
set -euo pipefail
4747
cd /opt/santagym
48-
if [ ! -f .env ]; then
49-
cp .env.example .env
50-
echo "Missing /opt/santagym/.env. Filled it from .env.example; update secrets before next deploy."
48+
if [ ! -f .env.server ]; then
49+
cp .env.server.example .env.server
50+
echo "Missing /opt/santagym/.env.server. Filled it from .env.server.example; update secrets before next deploy."
5151
fi
5252
53-
docker compose build backend bot frontend
54-
docker compose up -d db redis traefik
55-
docker compose run --rm backend alembic upgrade head
56-
docker compose up -d backend bot frontend
53+
docker compose --env-file .env.server -f docker-compose.yml -f docker-compose.server.yml build backend bot frontend
54+
docker compose --env-file .env.server -f docker-compose.yml -f docker-compose.server.yml up -d db redis traefik
55+
docker compose --env-file .env.server -f docker-compose.yml -f docker-compose.server.yml run --rm backend alembic upgrade head
56+
docker compose --env-file .env.server -f docker-compose.yml -f docker-compose.server.yml up -d backend bot frontend
5757
58-
timeout 120 sh -c 'until docker compose exec -T backend python -c "import urllib.request; urllib.request.urlopen(\"http://127.0.0.1:8000/health\")"; do sleep 5; done'
59-
timeout 120 sh -c 'until docker compose exec -T frontend wget --no-verbose --tries=1 --spider http://127.0.0.1/; do sleep 5; done'
58+
timeout 120 sh -c 'until docker compose --env-file .env.server -f docker-compose.yml -f docker-compose.server.yml exec -T backend python -c "import urllib.request; urllib.request.urlopen(\"http://127.0.0.1:8000/health\")"; do sleep 5; done'
59+
timeout 120 sh -c 'until docker compose --env-file .env.server -f docker-compose.yml -f docker-compose.server.yml exec -T frontend wget --no-verbose --tries=1 --spider http://127.0.0.1/; do sleep 5; done'
6060
61-
docker compose ps
61+
docker compose --env-file .env.server -f docker-compose.yml -f docker-compose.server.yml ps
6262
docker image prune -af

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Icon
2121
.env
2222
.env.production
2323
.env.local
24+
.env.server
2425
*.pem
2526
*.key
2627
.claude/

Makefile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
LOCAL_COMPOSE=docker compose --env-file .env.local -f docker-compose.yml -f docker-compose.local.yml
2+
SERVER_COMPOSE=docker compose --env-file .env.server -f docker-compose.yml -f docker-compose.server.yml
3+
4+
local-up:
5+
$(LOCAL_COMPOSE) up --build -d db redis backend frontend
6+
7+
local-down:
8+
$(LOCAL_COMPOSE) down
9+
10+
local-logs:
11+
$(LOCAL_COMPOSE) logs -f backend frontend db redis
12+
13+
local-migrate:
14+
$(LOCAL_COMPOSE) exec backend alembic upgrade head
15+
16+
local-init:
17+
$(LOCAL_COMPOSE) up --build -d db redis backend
18+
$(LOCAL_COMPOSE) exec backend alembic upgrade head
19+
20+
server-config:
21+
$(SERVER_COMPOSE) config

README.md

Lines changed: 68 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -60,90 +60,105 @@ SantaGymBot/
6060

6161
---
6262

63-
## 🚀 Локальная разработка
63+
## 🚀 Local Development
6464

6565
### Требования
6666
* Docker & Docker Compose
67-
* Node.js (v20+)
68-
* Python 3.12+ / Poetry (Опционально, если запускать бэк без докера)
67+
* GNU Make
68+
* Node.js / Python локально не обязательны, если используете только Docker
6969

7070
### 1. Подготовка окружения
71-
Клонируйте репозиторий и создайте файл секретов окружения:
71+
Клонируйте репозиторий и создайте local env:
7272
```bash
7373
git clone <repository_url>
7474
cd SantaGymBot
75-
cp .env.example .env
75+
cp .env.local.example .env.local
7676
```
7777

78-
Отредактируйте `.env`:
79-
* Обязательно укажите `BOT_TOKEN` (получив его у @BotFather в Telegram).
80-
* Сгенерируйте `JWT_SECRET` (любая длинная случайная строка).
81-
82-
### 2. Запуск Backend + Инфраструктура
83-
Мы используем Docker для поднятия Базы данных (PostgreSQL), Кэша (Redis) и самого Бэкенда (API + Bot).
78+
### 2. Первый запуск
8479

8580
```bash
86-
docker compose up -d --build
81+
make local-init
82+
make local-up
8783
```
88-
> **Внимание:** В локальном режиме бэкенд будет доступен Траефиком по хосту `api.santagym.local` (если прописать его в `/etc/hosts` локальной машины), либо можно маппить порты `8000:8000` напрямую для тестов.
8984

90-
### 3. Накатываем миграции Базы Данных
91-
Чтобы в БД появились таблицы, необходимо применить миграции Alembic:
85+
Локальный стенд поднимает:
86+
- `db`
87+
- `redis`
88+
- `backend`
89+
- `frontend`
9290

93-
```bash
94-
docker compose exec backend alembic upgrade head
95-
```
91+
`bot` в обычный local workflow не входит.
92+
93+
### 3. Адреса сервисов
94+
95+
- API: `http://localhost:8000`
96+
- Frontend: `http://localhost:5173`
97+
98+
### 4. Миграции
9699

97-
*(Если вы модифицировали модели и нужно создать новую миграцию):*
98100
```bash
99-
docker compose exec backend alembic revision --autogenerate -m "reason"
100-
docker compose exec backend alembic upgrade head
101+
make local-migrate
101102
```
102103

103-
### 4. Запуск Frontend
104-
В отдельном окне терминала перейдите в папку `frontend`:
104+
### 5. Логи и остановка
105105

106106
```bash
107-
cd frontend
108-
npm install
109-
npm run dev
107+
make local-logs
108+
make local-down
110109
```
111-
Фронтенд запустится на `http://localhost:5173`.
112-
> **Для справки:** При тестировании фронта локально (в браузере, а не внутри Telegram), инициализация будет проходить через заглушку (bypass auth), создавая "тестового пользователя", чтобы вы могли разрабатывать UI без подключения дебаггера смартфона.
110+
111+
### 6. Local Auth
112+
113+
Локальная разработка использует controlled fallback auth через `test_mode`, если в `.env.local` включен `ALLOW_TEST_AUTH=true`.
114+
115+
Это подходит для:
116+
- UI разработки
117+
- локальной проверки API
118+
- быстрой отладки в браузере
119+
120+
Это не заменяет реальную проверку Telegram WebApp авторизации.
121+
122+
Детали: [local-stand.md](/Users/santa/PycharmProjects/SantaGymBot/docs/ops/local-stand.md:1)
113123

114124
---
115125

116-
## 🌍 Production Деплой (Развертывание на сервер)
126+
## 🌍 Server Deployment
127+
128+
Серверный режим использует отдельный compose override с `Traefik`, `bot` и production-oriented frontend runtime.
117129

118-
Проект спроектирован так, чтобы его можно было безболезненно развернуть на VPS (Ubuntu/Debian) без лишних конфликтов портов благодаря использованию сетки Traefik.
130+
### 1. Подготовка env
119131

120-
### 1. Настройка домена
121-
Направьте A-запись вашего домена (например, `gymbot.example.com` и `api.gymbot.example.com`) на IP вашего сервера.
132+
```bash
133+
cp .env.server.example .env.server
134+
```
135+
136+
Заполните как минимум:
137+
- `BOT_TOKEN`
138+
- `JWT_SECRET`
139+
- `WEBAPP_BASE_URL`
140+
- `WEBHOOK_URL`
141+
- `FRONTEND_HOST`
142+
- `API_HOST`
143+
- `TRAEFIK_ACME_EMAIL`
144+
145+
### 2. Проверка server topology
122146

123-
### 2. Подготовка .env для PROD
124-
В файле `.env` на сервере:
125147
```dotenv
126-
WEBHOOK_URL=https://api.gymbot.example.com/api/v1/webhook
127-
JWT_SECRET=super_strong_production_key...
128-
POSTGRES_PASSWORD=super_strong_password
129-
...
148+
make server-config
130149
```
131150

132-
### 3. Изменение Traefik-конфигурации (docker-compose)
133-
В prod-окружении необходимо:
134-
1. Заменить `--api.insecure=true` на SSL/Let's Encrypt Entrypoints в настройках `traefik`.
135-
2. Добавить метки (labels) для `frontend` контейнера (напишите prod-dockerfile, который собирает статику и отдает через `nginx`). Либо отдавать статику через CDN.
136-
3. Обновить label хоста бэкенда:
137-
```yaml
138-
- "traefik.http.routers.backend.rule=Host(`api.gymbot.example.com`)"
139-
```
151+
### 3. Запуск server stack
140152

141-
### 4. Запуск и миграция
142153
```bash
143-
docker compose up -d --build
144-
docker compose exec backend alembic upgrade head
154+
docker compose --env-file .env.server -f docker-compose.yml -f docker-compose.server.yml up -d --build
155+
docker compose --env-file .env.server -f docker-compose.yml -f docker-compose.server.yml exec backend alembic upgrade head
145156
```
146157

158+
`Traefik` здесь нужен для host-based routing и размещения нескольких ботов/WebApp на одном сервере без конфликта внешних портов `80/443`.
159+
160+
Детали: [server-stand.md](/Users/santa/PycharmProjects/SantaGymBot/docs/ops/server-stand.md:1)
161+
147162
---
148163

149164
## 🔒 Модель безопасности
@@ -163,7 +178,7 @@ docker compose exec backend alembic upgrade head
163178

164179
## Deployment (GitHub Actions)
165180

166-
This project has CI/CD configured using GitHub Actions. Upon pushing to the `main` branch, the code is copied via SCP and started using docker-compose on the target server.
181+
This project has CI/CD configured using GitHub Actions. The pipeline validates frontend, backend and compose configuration. Deployment uses the server compose topology.
167182

168183
### Setup Server Secrets
169184
Navigate to **Settings > Secrets and variables > Actions** in your GitHub repository and add:
@@ -172,13 +187,13 @@ Navigate to **Settings > Secrets and variables > Actions** in your GitHub reposi
172187
- `SSH_KEY`: The private Ed25519 or RSA SSH key to access the VPS
173188

174189
### Initial Server Setup
175-
Make sure the folder exists and create a `.env` file containing the protected API tokens:
190+
Make sure the folder exists and create a `.env.server` file containing the protected runtime secrets:
176191
```bash
177192
# Connect to your server
178193
ssh root@YOUR_SERVER_IP
179194
mkdir -p /opt/santagym
180195
cd /opt/santagym
181-
nano .env
196+
nano .env.server
182197
```
183198

184-
Fill the `.env` with your variables (`BOT_TOKEN`, `POSTGRES_USER`, etc).
199+
Fill `.env.server` with your variables (`BOT_TOKEN`, `POSTGRES_USER`, `WEBAPP_BASE_URL`, etc).

backend/Dockerfile.local

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM python:3.12-slim
2+
3+
WORKDIR /app
4+
5+
RUN pip install --no-cache-dir poetry
6+
7+
COPY pyproject.toml poetry.lock* ./
8+
RUN poetry config virtualenvs.create false \
9+
&& poetry install --no-interaction --no-ansi --no-root
10+
11+
COPY . .
12+
13+
CMD ["uvicorn", "app.main_api:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

backend/app/application/services/auth_service.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
import hashlib
44
from urllib.parse import parse_qsl
55
from datetime import datetime, timedelta
6+
67
import jwt
78
from sqlalchemy.ext.asyncio import AsyncSession
89

910
from app.core.config import settings
1011
from app.infrastructure.database.repositories.user import user_repo
1112

1213
class AuthService:
13-
def __init__(self, bot_token: str, jwt_secret: str):
14+
def __init__(self, bot_token: str | None, jwt_secret: str):
1415
self.bot_token = bot_token
1516
self.jwt_secret = jwt_secret
1617

@@ -19,6 +20,9 @@ def validate_telegram_data(self, init_data: str) -> dict:
1920
Провалидировать initData от Telegram WebApp.
2021
Возвращает распаршенный JSON пользователя или кидает ValueError.
2122
"""
23+
if not self.bot_token:
24+
raise ValueError("BOT_TOKEN is not configured")
25+
2226
parsed_data = dict(parse_qsl(init_data))
2327
if "hash" not in parsed_data:
2428
raise ValueError("No hash in init_data")

0 commit comments

Comments
 (0)