mnemo_cards/mnemo_cards_backend/README.md

874 lines
24 KiB
Markdown
Raw Normal View History

2025-12-14 19:21:36 +00:00
# 🎴 Mnemo Cards Backend
2025-11-16 11:25:27 +00:00
2025-12-14 19:21:36 +00:00
Backend сервер для приложения Mnemo Cards - платформы для изучения иностранных слов с использованием мнемотехники.
2025-11-16 11:25:27 +00:00
2025-12-14 19:21:36 +00:00
## 📋 Оглавление
2025-11-16 11:25:27 +00:00
2025-12-14 19:21:36 +00:00
- [Технологии](#-технологии)
- [Архитектура](#-архитектура)
- [Быстрый старт](#-быстрый-старт)
- [Структура проекта](#-структура-проекта)
- [API документация](#-api-документация)
- [Разработка](#-разработка)
- [Деплой](#-деплой)
- [Тестирование](#-тестирование)
- [Конфигурация](#-конфигурация)
2025-11-16 11:25:27 +00:00
2025-12-14 19:21:36 +00:00
---
2025-11-16 11:25:27 +00:00
2025-12-14 19:21:36 +00:00
## 🚀 Технологии
2025-11-16 11:25:27 +00:00
2025-12-14 19:21:36 +00:00
### 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**