# 🎴 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 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 ``` ### Основные 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 with _$MyDaoMixin { MyDao(super.db); Future> getAll() => select(myTables).get(); Future getById(int id) => (select(myTables)..where((t) => t.id.equals(id))).getSingleOrNull(); Future 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 _handleGet(Request request) async { // Implementation return Response.ok('{"status": "ok"}'); } Future _handlePost(Request request) async { // Implementation return Response.ok('{"status": "created"}'); } } ``` 2. Зарегистрировать в `lib/api/mnemo_shelf.dart`: ```dart v2Router.mount('/', getIt.get().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 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**