mnemo_cards/mnemo_cards_backend/README.md
Dmitry 889784e11f
Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Web App CI / test (push) Waiting to run
Web App CI / build (push) Blocked by required conditions
Deploy Admin Panel / Deploy Admin Panel (push) Waiting to run
Deploy Admin Panel / Admin Panel Verification (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions
stuff
2025-12-14 22:21:36 +03:00

873 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🎴 Mnemo Cards Backend
Backend сервер для приложения Mnemo Cards - платформы для изучения иностранных слов с использованием мнемотехники.
## 📋 Оглавление
- [Технологии](#-технологии)
- [Архитектура](#-архитектура)
- [Быстрый старт](#-быстрый-старт)
- [Структура проекта](#-структура-проекта)
- [API документация](#-api-документация)
- [Разработка](#-разработка)
- [Деплой](#-деплой)
- [Тестирование](#-тестирование)
- [Конфигурация](#-конфигурация)
---
## 🚀 Технологии
### Backend Stack
- **Язык:** Dart 3.8+
- **HTTP сервер:** Shelf
- **База данных:** PostgreSQL 16 + Drift ORM
- **Аутентификация:** JWT (jaguar_jwt)
- **DI:** get_it + injectable
- **API:** RESTful API v2
### Интеграции
- **Платежи:** YooKassa (Российские платежи), Google Play, RuStore
- **Telegram Bot API:** Авторизация через Telegram
- **PostgreSQL:** Production-ready реляционная БД
### DevOps
- **Контейнеризация:** Docker + Docker Compose
- **Deployment:** Coolify (или любой Docker хостинг)
- **CI/CD:** GitHub Actions (опционально)
---
## 🏗️ Архитектура
```
┌─────────────────┐
│ Web Client │
│ (Flutter Web) │
└────────┬────────┘
│ HTTPS
┌─────────────────┐ ┌──────────────┐
│ Mnemo Backend │◄────►│ PostgreSQL │
│ (Dart/Shelf) │ │ Database │
└────────┬────────┘ └──────────────┘
├─► YooKassa API (Payments)
├─► Telegram Bot API (Auth)
├─► Google Play API (IAP)
└─► RuStore API (IAP)
```
### Основные компоненты
- **API Server** (`lib/api/mnemo_shelf.dart`) - HTTP сервер на Shelf
- **Database** (`lib/database/`) - Drift ORM + PostgreSQL
- **Managers** (`lib/user/`, `lib/packs/`, etc.) - Бизнес-логика
- **Cron Jobs** (`lib/cron/`) - Фоновые задачи
- **Auth** (`lib/auth/`, `lib/api/authorize/`) - JWT аутентификация
---
## ⚡ Быстрый старт
### Требования
- **Dart SDK:** 3.8.0+
- **PostgreSQL:** 16+ (локально или Docker)
- **Docker & Docker Compose** (опционально, для PostgreSQL)
### 1. Клонирование репозитория
```bash
git clone <repository-url>
cd mnemo_cards_backend
```
### 2. Установка зависимостей
```bash
# Установка Dart зависимостей
dart pub get
# Генерация кода (Drift, Injectable, JSON)
dart run build_runner build --delete-conflicting-outputs
```
### 3. Настройка PostgreSQL
#### Вариант A: Docker (рекомендуется для разработки)
```bash
# Запустить PostgreSQL через Docker Compose
docker-compose up -d postgres
# Проверить статус
docker-compose ps
```
#### Вариант B: Локальная установка
```bash
# macOS
brew install postgresql@16
brew services start postgresql@16
# Linux
sudo apt install postgresql-16
sudo systemctl start postgresql
```
Создать базу данных:
```sql
CREATE DATABASE mnemo_cards_dev;
CREATE USER mnemo_user WITH PASSWORD 'dev_password';
GRANT ALL PRIVILEGES ON DATABASE mnemo_cards_dev TO mnemo_user;
```
### 4. Настройка переменных окружения
```bash
# Создать .env файл из примера
cp .env.example .env
# Отредактировать .env (указать реальные значения)
nano .env
```
**Минимальная конфигурация** для `.env`:
```bash
# PostgreSQL
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mnemo_cards_dev
DB_USER=mnemo_user
DB_PASSWORD=dev_password
DB_SSL_MODE=disable
# Backend
PORT=3000
SERVER_ADDRESS=0.0.0.0
WORK_DIR=/root/mnemo_cards_backend
DEBUG=true
# Admin IDs
ADMIN_IDS=1
# JWT Secrets (сгенерировать случайные!)
JWT_SECRET=dev_jwt_secret_change_me_12345
JWT_REFRESH_SECRET=dev_refresh_secret_change_me_12345
```
> ⚠️ **Важно:** Сгенерируйте надежные JWT секреты для продакшена:
> ```bash
> openssl rand -base64 32
> ```
### 5. Запуск сервера
```bash
# Development mode
./run_dev.sh
# Или напрямую
dart run lib/main.dart
```
Сервер запустится на `http://localhost:3000`
### 6. Проверка работоспособности
```bash
# Health check
curl http://localhost:3000/health
# API version info
curl http://localhost:3000/api/v2/health
```
---
## 📁 Структура проекта
```
mnemo_cards_backend/
├── lib/
│ ├── api/ # API endpoints и middleware
│ │ ├── v2/ # RESTful API v2
│ │ │ ├── auth_api_v2.dart # Авторизация
│ │ │ ├── users_api_v2.dart # Пользователи
│ │ │ ├── packs_api_v2.dart # Наборы карточек
│ │ │ ├── tests_api_v2.dart # Тесты
│ │ │ ├── subscriptions_api_v2.dart # Подписки
│ │ │ ├── promocodes_api_v2.dart # Промокоды
│ │ │ ├── admin_*_api_v2.dart # Админ панель
│ │ │ └── ...
│ │ ├── authorize/ # Middleware для авторизации
│ │ ├── purchase/ # Платежи (YooKassa, Google Play, RuStore)
│ │ ├── subscription/ # Управление подписками
│ │ ├── di/ # Dependency Injection (get_it)
│ │ └── mnemo_shelf.dart # Главный HTTP сервер
│ │
│ ├── database/ # PostgreSQL + Drift ORM
│ │ ├── database.dart # Главный класс БД
│ │ ├── tables/ # Определения таблиц
│ │ │ ├── users.dart
│ │ │ ├── packs.dart
│ │ │ ├── auth.dart
│ │ │ ├── payments.dart
│ │ │ └── ...
│ │ └── daos/ # Data Access Objects
│ │ ├── user_dao.dart
│ │ ├── pack_dao.dart
│ │ └── ...
│ │
│ ├── user/ # User management
│ │ └── user_manager.dart
│ │
│ ├── packs/ # Pack management
│ │ ├── pack_manager.dart
│ │ └── free_packs_distributor.dart
│ │
│ ├── tests/ # Test management
│ │ └── test_manager.dart
│ │
│ ├── tasks/ # Task management
│ │ └── task_manager.dart
│ │
│ ├── promo_codes/ # Promo codes
│ │ └── promo_codes_manager.dart
│ │
│ ├── discounts/ # Discounts
│ │ └── discounts_manager.dart
│ │
│ ├── statistics/ # User statistics
│ │ ├── session_tracker.dart
│ │ ├── statistics_calculator.dart
│ │ └── session_tracking_middleware.dart
│ │
│ ├── cron/ # Background jobs
│ │ ├── cron_executor.dart
│ │ ├── check_payment.dart
│ │ ├── backup.dart
│ │ ├── add_free_packs.dart
│ │ └── ...
│ │
│ ├── auth/ # Authentication utilities
│ │ ├── hash.dart
│ │ └── password.dart
│ │
│ └── main.dart # Entry point
├── test/ # Unit & integration tests
│ ├── api/v2/ # API tests
│ ├── database/ # Database tests
│ ├── models/ # Model tests
│ └── statistics/ # Statistics tests
├── data/ # Static data (images, audio)
├── public/ # Public files
├── docs/ # Documentation
├── docker-compose.yml # Docker Compose для разработки
├── Dockerfile # Production Docker image
├── pubspec.yaml # Dart dependencies
├── .env.example # Пример переменных окружения
├── run_dev.sh # Скрипт запуска (dev)
├── run_http_production.sh # Скрипт запуска (prod)
├── build_app.sh # Скрипт сборки
├── codegen.sh # Генерация кода
├── COOLIFY_SETUP.md # Инструкции по деплою в Coolify
├── ENVIRONMENT_VARIABLES.md # Описание env переменных
├── DB_PLAN.md # План миграции БД
└── README.md # Этот файл
```
---
## 🌐 API документация
### Base URL
- **Local:** `http://localhost:3000/api/v2`
- **Production:** `https://your-domain.com/api/v2`
### Авторизация
Backend использует **JWT Bearer tokens** для авторизации:
```http
Authorization: Bearer <access_token>
```
### Основные endpoints
#### Аутентификация
```http
POST /api/v2/auth/register # Регистрация
POST /api/v2/auth/login # Логин
POST /api/v2/auth/refresh # Обновить токен
POST /api/v2/auth/telegram # Авторизация через Telegram
GET /api/v2/auth/telegram/code # Получить код для Telegram
```
#### Пользователи
```http
GET /api/v2/users/me # Текущий пользователь
PATCH /api/v2/users/me # Обновить профиль
GET /api/v2/users/:id # Получить пользователя
GET /api/v2/users/:id/statistics # Статистика пользователя
POST /api/v2/users/:id/balance # Обновить баланс
```
#### Наборы карточек (Packs)
```http
GET /api/v2/packs # Список паков (preview)
GET /api/v2/packs/:id # Детали пака
GET /api/v2/packs/:id/cards # Карточки пака
POST /api/v2/packs/:id/purchase # Купить пак
```
#### Тесты
```http
GET /api/v2/tests # Список тестов
GET /api/v2/tests/:id # Детали теста
POST /api/v2/tests/:id/start # Начать тест
POST /api/v2/tests/:id/submit # Отправить ответы
GET /api/v2/tests/:id/results # Результаты теста
```
#### Подписки
```http
GET /api/v2/subscriptions/plans # Доступные планы
POST /api/v2/subscriptions/purchase # Купить подписку
GET /api/v2/subscriptions/my # Мои подписки
```
#### Промокоды
```http
POST /api/v2/promocodes/apply # Применить промокод
GET /api/v2/promocodes/:code # Проверить промокод
```
#### Админ панель
```http
GET /api/v2/admin/users # Все пользователи
GET /api/v2/admin/packs # Все паки
POST /api/v2/admin/packs # Создать пак
PUT /api/v2/admin/packs/:id # Обновить пак
DELETE /api/v2/admin/packs/:id # Удалить пак
GET /api/v2/admin/analytics # Аналитика
```
### Health Check
```http
GET /health # Health check endpoint
```
**Response:**
```
OK
```
---
## 💻 Разработка
### Генерация кода
Backend использует code generation для:
- **Drift** (database code)
- **Injectable** (dependency injection)
- **Shelf Router** (routing)
- **JSON Serializable** (JSON mapping)
```bash
# Генерация кода
dart run build_runner build --delete-conflicting-outputs
# Watch mode (автоматическая генерация при изменениях)
dart run build_runner watch --delete-conflicting-outputs
# Или использовать скрипт
./codegen.sh
```
### Работа с базой данных
#### Создание новой таблицы
1. Создать файл в `lib/database/tables/`:
```dart
// lib/database/tables/my_table.dart
import 'package:drift/drift.dart';
class MyTables extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)();
}
```
2. Добавить таблицу в `lib/database/database.dart`:
```dart
@DriftDatabase(
tables: [
// ... existing tables
MyTables,
],
// ...
)
```
3. Сгенерировать код:
```bash
dart run build_runner build --delete-conflicting-outputs
```
#### Создание DAO
```dart
// lib/database/daos/my_dao.dart
import 'package:drift/drift.dart';
import '../database.dart';
part 'my_dao.g.dart';
@DriftAccessor(tables: [MyTables])
class MyDao extends DatabaseAccessor<AppDatabase> with _$MyDaoMixin {
MyDao(super.db);
Future<List<MyTable>> getAll() => select(myTables).get();
Future<MyTable?> getById(int id) =>
(select(myTables)..where((t) => t.id.equals(id))).getSingleOrNull();
Future<int> create(MyTablesCompanion entry) => into(myTables).insert(entry);
}
```
### Добавление нового API endpoint
1. Создать файл в `lib/api/v2/`:
```dart
// lib/api/v2/my_api_v2.dart
import 'package:shelf/shelf.dart';
import 'package:shelf_router/shelf_router.dart';
import 'package:injectable/injectable.dart';
@lazySingleton
class MyApiV2 {
final AppDatabase _db;
MyApiV2(this._db);
Router get router {
final router = Router();
router.get('/my-endpoint', _handleGet);
router.post('/my-endpoint', _handlePost);
return router;
}
Future<Response> _handleGet(Request request) async {
// Implementation
return Response.ok('{"status": "ok"}');
}
Future<Response> _handlePost(Request request) async {
// Implementation
return Response.ok('{"status": "created"}');
}
}
```
2. Зарегистрировать в `lib/api/mnemo_shelf.dart`:
```dart
v2Router.mount('/', getIt.get<MyApiV2>().router);
```
### Cron Jobs
Фоновые задачи запускаются автоматически при старте сервера.
Создание нового cron job:
```dart
// lib/cron/my_task.dart
import 'package:neat_periodic_task/neat_periodic_task.dart';
import 'task.dart';
class MyTask extends Task {
@override
String get name => 'My Task';
@override
Duration get interval => const Duration(hours: 1);
@override
Duration get timeout => const Duration(minutes: 5);
@override
Future<void> run() async {
print('Running my task...');
// Implementation
}
}
```
Добавить в `lib/main.dart`:
```dart
CronManager([
// ... existing tasks
MyTask(),
]).init();
```
### Debugging
```bash
# Запуск с дебагом
DEBUG=true dart run lib/main.dart
# Логи PostgreSQL
docker-compose logs -f postgres
# Логи backend
docker-compose logs -f backend
# Подключение к PostgreSQL
docker-compose exec postgres psql -U mnemo_user -d mnemo_cards_dev
```
---
## 🚢 Деплой
### Docker (рекомендуется)
#### 1. Сборка образа
```bash
# Build Docker image
docker build -t mnemo_backend:latest .
# Или использовать скрипт
./build_app.sh
```
#### 2. Запуск через Docker Compose
```bash
# Production
docker-compose -f docker-compose.prod.yml up -d
# Проверка
docker-compose ps
docker-compose logs -f backend
```
### Coolify (PaaS)
Подробная инструкция по деплою в Coolify: **[COOLIFY_SETUP.md](./COOLIFY_SETUP.md)**
**Краткая версия:**
1. Создать PostgreSQL в Coolify
2. Создать приложение (Dockerfile)
3. Настроить environment variables
4. Deploy
### Переменные окружения для продакшена
Полное описание всех переменных: **[ENVIRONMENT_VARIABLES.md](./ENVIRONMENT_VARIABLES.md)**
**Критически важные:**
```bash
# PostgreSQL (используйте internal hostname в Coolify)
DB_HOST=mnemo-postgres
DB_PORT=5432
DB_NAME=mnemo_cards
DB_USER=mnemo_user
DB_PASSWORD=<сгенерированный_пароль>
DB_SSL_MODE=require
# Backend
PORT=3000
SERVER_ADDRESS=0.0.0.0
DEBUG=false
# JWT (минимум 32 символа!)
JWT_SECRET=<сгенерированный_секрет>
JWT_REFRESH_SECRET=ругой_сгенерированный_секрет>
# Admin IDs
ADMIN_IDS=1,2,3
# YooKassa (если используете)
YOOKASSA_SHOP_ID=аш_shop_id>
YOOKASSA_SECRET_KEY=аш_secret_key>
```
### Health Checks
Backend предоставляет `/health` endpoint для мониторинга:
```bash
curl https://your-domain.com/health
# Response: OK
```
Настройка в Docker Compose:
```yaml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
```
---
## 🧪 Тестирование
### Запуск тестов
```bash
# Все тесты
dart test
# Конкретный файл
dart test test/api/v2/users_api_v2_test.dart
# С coverage
dart test --coverage=coverage
dart pub global activate coverage
format_coverage --lcov --in=coverage --out=coverage/lcov.info --report-on=lib
```
### Структура тестов
```
test/
├── api/v2/ # API endpoint tests
│ ├── auth_api_v2_test.dart
│ ├── users_api_v2_test.dart
│ ├── packs_api_v2_test.dart
│ └── ...
├── database/ # Database tests
├── models/ # Model tests
└── statistics/ # Statistics tests
```
### Написание тестов
```dart
import 'package:test/test.dart';
import 'package:mockito/mockito.dart';
import 'package:mnemo_cards_backend/database/database.dart';
void main() {
late AppDatabase db;
setUpAll(() async {
// Setup test database
db = AppDatabase.connect(
host: 'localhost',
port: 5433, // Test port
database: 'mnemo_cards_test',
username: 'test_user',
password: 'test_pass',
);
await db.migrator.createAll();
});
tearDownAll(() async {
await db.close();
});
group('UserDao', () {
test('создание пользователя', () async {
// Arrange
final user = UsersCompanion.insert(
name: 'Test User',
externalUserId: 'test123',
);
// Act
final userId = await db.userDao.createUser(user);
final created = await db.userDao.getUserById(userId);
// Assert
expect(created, isNotNull);
expect(created!.name, equals('Test User'));
});
});
}
```
### API тестирование
```bash
# Используйте ./test_api.sh или curl
curl -X POST http://localhost:3000/api/v2/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com", "password": "password123"}'
```
---
## ⚙️ Конфигурация
### Основные переменные окружения
| Переменная | Описание | По умолчанию | Обязательная |
|-----------|----------|--------------|--------------|
| `DB_HOST` | PostgreSQL host | `localhost` | ✅ |
| `DB_PORT` | PostgreSQL port | `5432` | ✅ |
| `DB_NAME` | Database name | `mnemo_cards` | ✅ |
| `DB_USER` | Database user | `mnemo_user` | ✅ |
| `DB_PASSWORD` | Database password | - | ✅ |
| `DB_SSL_MODE` | SSL mode | `disable` | ❌ |
| `PORT` | Backend port | `3000` | ✅ |
| `SERVER_ADDRESS` | Bind address | `0.0.0.0` | ✅ |
| `WORK_DIR` | Working directory | `/app` | ✅ |
| `DEBUG` | Debug mode | `false` | ❌ |
| `JWT_SECRET` | JWT secret | - | ✅ |
| `JWT_REFRESH_SECRET` | Refresh token secret | - | ✅ |
| `ADMIN_IDS` | Admin user IDs | - | ✅ |
| `YOOKASSA_SHOP_ID` | YooKassa shop ID | - | ❌ |
| `YOOKASSA_SECRET_KEY` | YooKassa secret | - | ❌ |
Полное описание: **[ENVIRONMENT_VARIABLES.md](./ENVIRONMENT_VARIABLES.md)**
### Генерация секретов
```bash
# JWT Secret (минимум 32 символа)
openssl rand -base64 32
# Database password
openssl rand -base64 24
```
---
## 📚 Дополнительные ресурсы
### Документация
- **[COOLIFY_SETUP.md](./COOLIFY_SETUP.md)** - Деплой в Coolify
- **[ENVIRONMENT_VARIABLES.md](./ENVIRONMENT_VARIABLES.md)** - Переменные окружения
- **[DB_PLAN.md](./DB_PLAN.md)** - План миграции БД (Isar → PostgreSQL)
### Полезные команды
```bash
# Генерация кода
./codegen.sh
# Запуск dev сервера
./run_dev.sh
# Подключение к PostgreSQL
./connect.sh
# Сборка для продакшена
./build_app.sh
# Тестирование API
./test_api.sh
```
### Troubleshooting
#### Backend не запускается
1. Проверить PostgreSQL:
```bash
docker-compose ps postgres
docker-compose logs postgres
```
2. Проверить переменные окружения:
```bash
cat .env
```
3. Проверить подключение к БД:
```bash
docker-compose exec postgres psql -U mnemo_user -d mnemo_cards_dev
```
#### Ошибка "Connection refused"
- Убедитесь, что PostgreSQL запущен
- Проверьте `DB_HOST` и `DB_PORT`
- В Docker используйте service name (`postgres`), а не `localhost`
#### Ошибка "Invalid JWT secret"
- JWT секрет должен быть минимум 32 символа
- Сгенерируйте новый: `openssl rand -base64 32`
#### Drift генерация не работает
```bash
# Очистить кэш
rm -rf .dart_tool/
dart pub get
dart run build_runner build --delete-conflicting-outputs
```
---
## 🤝 Контрибуция
1. Fork the repository
2. Create feature branch (`git checkout -b feature/amazing-feature`)
3. Commit changes (`git commit -m 'Add amazing feature'`)
4. Push to branch (`git push origin feature/amazing-feature`)
5. Open Pull Request
### Code Style
- Следуйте [Dart Style Guide](https://dart.dev/guides/language/effective-dart/style)
- Используйте `dart format` перед коммитом
- Пишите unit тесты для новой функциональности
---
## 📝 Лицензия
[Your License Here]
---
## 📧 Контакты
Если возникли вопросы или проблемы, создайте Issue в репозитории.
---
**Made with ❤️ using Dart & PostgreSQL**