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
|
2025-12-14 21:10:06 +00:00
|
|
|
|
│ │ │ ├── word_statistics.dart # Статистика ответов на карточки
|
|
|
|
|
|
│ │ │ ├── audit.dart # Audit trail (инфраструктура)
|
2025-12-14 19:21:36 +00:00
|
|
|
|
│ │ │ └── ...
|
|
|
|
|
|
│ │ └── daos/ # Data Access Objects
|
|
|
|
|
|
│ │ ├── user_dao.dart
|
|
|
|
|
|
│ │ ├── pack_dao.dart
|
2025-12-14 21:10:06 +00:00
|
|
|
|
│ │ ├── word_statistics_dao.dart # Работа со статистикой слов
|
|
|
|
|
|
│ │ ├── audit_dao.dart # Audit logging
|
|
|
|
|
|
│ │ └── mixins/
|
|
|
|
|
|
│ │ └── soft_delete_mixin.dart # Soft delete функциональность
|
2025-12-14 19:21:36 +00:00
|
|
|
|
│ │
|
|
|
|
|
|
│ ├── 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
|
2025-12-14 21:10:06 +00:00
|
|
|
|
│ │ ├── word_statistics_manager.dart # Управление статистикой ответов
|
2025-12-14 19:21:36 +00:00
|
|
|
|
│ │ └── 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**
|