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

24 KiB
Raw Blame History

🎴 Mnemo Cards Backend

Backend сервер для приложения Mnemo Cards - платформы для изучения иностранных слов с использованием мнемотехники.

📋 Оглавление


🚀 Технологии

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. Клонирование репозитория

git clone <repository-url>
cd mnemo_cards_backend

2. Установка зависимостей

# Установка Dart зависимостей
dart pub get

# Генерация кода (Drift, Injectable, JSON)
dart run build_runner build --delete-conflicting-outputs

3. Настройка PostgreSQL

Вариант A: Docker (рекомендуется для разработки)

# Запустить PostgreSQL через Docker Compose
docker-compose up -d postgres

# Проверить статус
docker-compose ps

Вариант B: Локальная установка

# macOS
brew install postgresql@16
brew services start postgresql@16

# Linux
sudo apt install postgresql-16
sudo systemctl start postgresql

Создать базу данных:

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. Настройка переменных окружения

# Создать .env файл из примера
cp .env.example .env

# Отредактировать .env (указать реальные значения)
nano .env

Минимальная конфигурация для .env:

# 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 секреты для продакшена:

openssl rand -base64 32

5. Запуск сервера

# Development mode
./run_dev.sh

# Или напрямую
dart run lib/main.dart

Сервер запустится на http://localhost:3000

6. Проверка работоспособности

# 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 для авторизации:

Authorization: Bearer <access_token>

Основные endpoints

Аутентификация

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

Пользователи

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)

GET    /api/v2/packs                   # Список паков (preview)
GET    /api/v2/packs/:id               # Детали пака
GET    /api/v2/packs/:id/cards         # Карточки пака
POST   /api/v2/packs/:id/purchase      # Купить пак

Тесты

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       # Результаты теста

Подписки

GET    /api/v2/subscriptions/plans     # Доступные планы
POST   /api/v2/subscriptions/purchase  # Купить подписку
GET    /api/v2/subscriptions/my        # Мои подписки

Промокоды

POST   /api/v2/promocodes/apply        # Применить промокод
GET    /api/v2/promocodes/:code        # Проверить промокод

Админ панель

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

GET    /health                         # Health check endpoint

Response:

OK

💻 Разработка

Генерация кода

Backend использует code generation для:

  • Drift (database code)
  • Injectable (dependency injection)
  • Shelf Router (routing)
  • JSON Serializable (JSON mapping)
# Генерация кода
dart run build_runner build --delete-conflicting-outputs

# Watch mode (автоматическая генерация при изменениях)
dart run build_runner watch --delete-conflicting-outputs

# Или использовать скрипт
./codegen.sh

Работа с базой данных

Создание новой таблицы

  1. Создать файл в lib/database/tables/:
// 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)();
}
  1. Добавить таблицу в lib/database/database.dart:
@DriftDatabase(
  tables: [
    // ... existing tables
    MyTables,
  ],
  // ...
)
  1. Сгенерировать код:
dart run build_runner build --delete-conflicting-outputs

Создание DAO

// 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/:
// 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"}');
  }
}
  1. Зарегистрировать в lib/api/mnemo_shelf.dart:
v2Router.mount('/', getIt.get<MyApiV2>().router);

Cron Jobs

Фоновые задачи запускаются автоматически при старте сервера.

Создание нового cron job:

// 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:

CronManager([
  // ... existing tasks
  MyTask(),
]).init();

Debugging

# Запуск с дебагом
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. Сборка образа

# Build Docker image
docker build -t mnemo_backend:latest .

# Или использовать скрипт
./build_app.sh

2. Запуск через Docker Compose

# Production
docker-compose -f docker-compose.prod.yml up -d

# Проверка
docker-compose ps
docker-compose logs -f backend

Coolify (PaaS)

Подробная инструкция по деплою в Coolify: COOLIFY_SETUP.md

Краткая версия:

  1. Создать PostgreSQL в Coolify
  2. Создать приложение (Dockerfile)
  3. Настроить environment variables
  4. Deploy

Переменные окружения для продакшена

Полное описание всех переменных: ENVIRONMENT_VARIABLES.md

Критически важные:

# 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 для мониторинга:

curl https://your-domain.com/health
# Response: OK

Настройка в Docker Compose:

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 40s

🧪 Тестирование

Запуск тестов

# Все тесты
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

Написание тестов

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 тестирование

# Используйте ./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

Генерация секретов

# JWT Secret (минимум 32 символа)
openssl rand -base64 32

# Database password
openssl rand -base64 24

📚 Дополнительные ресурсы

Документация

Полезные команды

# Генерация кода
./codegen.sh

# Запуск dev сервера
./run_dev.sh

# Подключение к PostgreSQL
./connect.sh

# Сборка для продакшена
./build_app.sh

# Тестирование API
./test_api.sh

Troubleshooting

Backend не запускается

  1. Проверить PostgreSQL:

    docker-compose ps postgres
    docker-compose logs postgres
    
  2. Проверить переменные окружения:

    cat .env
    
  3. Проверить подключение к БД:

    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 генерация не работает

# Очистить кэш
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
  • Используйте dart format перед коммитом
  • Пишите unit тесты для новой функциональности

📝 Лицензия

[Your License Here]


📧 Контакты

Если возникли вопросы или проблемы, создайте Issue в репозитории.


Made with ❤️ using Dart & PostgreSQL