diff --git a/mnemo_cards_common/test/dtos/game_tests/matrix_test_question_body_test.dart b/mnemo_cards_common/test/dtos/game_tests/matrix_test_question_body_test.dart new file mode 100644 index 0000000..10a8c9b --- /dev/null +++ b/mnemo_cards_common/test/dtos/game_tests/matrix_test_question_body_test.dart @@ -0,0 +1,57 @@ +import 'package:mnemo_cards_common/mnemo_cards_common.dart'; +import 'package:test/test.dart'; + +void main() { + group('MatrixTestQuestionBody', () { + test('json roundtrip preserves fields', () { + final body = MatrixTestQuestionBody( + id: 'q1', + matrixSize: 3, + word: 'hola', + answer: 'card_1', + cards: const [ + MatrixCardDto( + id: 'card_1', + image: '/api/v2/packs/p1/cards/card_1/image', + original: 'hola', + translation: 'привет', + ), + MatrixCardDto( + id: 'card_2', + image: '/api/v2/packs/p1/cards/card_2/image', + original: 'adios', + translation: 'пока', + ), + ], + ); + + final json = body.toJson(); + final parsed = AbstractTestQuestion.fromJson(json) as MatrixTestQuestionBody; + + expect(parsed.questionType, TestQuestionType.matrix); + expect(parsed.id, 'q1'); + expect(parsed.matrixSize, 3); + expect(parsed.word, 'hola'); + expect(parsed.answer, 'card_1'); + expect(parsed.cards.length, 2); + expect(parsed.cards.first.original, 'hola'); + expect(parsed.cards.first.translation, 'привет'); + expect(parsed.cards.first.image, contains('/cards/card_1/image')); + }); + + test('matrixSize falls back to default when missing', () { + final json = { + 'questionType': 'matrix', + 'id': 'q1', + 'word': 'hola', + 'answer': 'card_1', + 'buttons': const [], + }; + + final parsed = AbstractTestQuestion.fromJson(json) as MatrixTestQuestionBody; + expect(parsed.matrixSize, 3); + expect(parsed.cards, isEmpty); + }); + }); +} + diff --git a/mnemo_cards_common/test/user_dto_telegram_test.dart b/mnemo_cards_common/test/user_dto_telegram_test.dart new file mode 100644 index 0000000..e0fb836 --- /dev/null +++ b/mnemo_cards_common/test/user_dto_telegram_test.dart @@ -0,0 +1,121 @@ +import 'package:test/test.dart'; +import 'package:mnemo_cards_common/mnemo_cards_common.dart'; + +void main() { + group('UserDto telegram field', () { + test('should serialize and deserialize with telegram', () { + final userDto = UserDto( + id: 'test-id', + name: 'Test User', + email: 'test@example.com', + telegram: '@testuser', + admin: false, + packs: ['pack1', 'pack2'], + purchases: ['purchase1'], + ); + + final json = userDto.toJson(); + expect(json['telegram'], equals('@testuser')); + expect(json['email'], equals('test@example.com')); + + final deserialized = UserDto.fromJson(json); + expect(deserialized.telegram, equals('@testuser')); + expect(deserialized.email, equals('test@example.com')); + expect(deserialized.name, equals('Test User')); + }); + + test('should handle null telegram', () { + final userDto = UserDto( + id: 'test-id', + name: 'Test User', + email: 'test@example.com', + telegram: null, + admin: false, + ); + + final json = userDto.toJson(); + expect(json['telegram'], isNull); + + final deserialized = UserDto.fromJson(json); + expect(deserialized.telegram, isNull); + expect(deserialized.email, equals('test@example.com')); + }); + + test('should handle missing telegram in JSON', () { + final json = { + 'id': 'test-id', + 'name': 'Test User', + 'email': 'test@example.com', + 'admin': false, + 'packs': [], + 'purchases': [], + }; + + final userDto = UserDto.fromJson(json); + expect(userDto.telegram, isNull); + expect(userDto.email, equals('test@example.com')); + }); + + test('should support telegram without email for Telegram-only users', () { + final userDto = UserDto( + id: 'test-id', + name: 'Telegram User', + email: null, + telegram: '@telegram_only', + admin: false, + ); + + final json = userDto.toJson(); + expect(json['telegram'], equals('@telegram_only')); + expect(json['email'], isNull); + + final deserialized = UserDto.fromJson(json); + expect(deserialized.telegram, equals('@telegram_only')); + expect(deserialized.email, isNull); + }); + + test('should work with copyWith for telegram', () { + final userDto = UserDto( + id: 'test-id', + name: 'Test User', + email: 'test@example.com', + telegram: '@oldusername', + admin: false, + ); + + final updated = userDto.copyWith(telegram: '@newusername'); + expect(updated.telegram, equals('@newusername')); + expect(updated.email, equals('test@example.com')); + expect(userDto.telegram, equals('@oldusername')); // Original unchanged + }); + + test('should handle both email and telegram contact methods', () { + final userDto = UserDto( + id: 'test-id', + name: 'Test User', + email: 'user@example.com', + telegram: '@testuser', + admin: true, + packs: ['pack1'], + purchases: ['purchase1'], + ); + + expect(userDto.email, equals('user@example.com')); + expect(userDto.telegram, equals('@testuser')); + expect(userDto.admin, isTrue); + + final json = userDto.toJson(); + final deserialized = UserDto.fromJson(json); + + expect(deserialized.email, equals('user@example.com')); + expect(deserialized.telegram, equals('@testuser')); + expect(deserialized.admin, isTrue); + }); + + test('empty UserDto should have null telegram', () { + final empty = UserDto.empty; + expect(empty.telegram, isNull); + expect(empty.email, isNull); + }); + }); +} diff --git a/plans/README.md b/plans/README.md new file mode 100644 index 0000000..be461aa --- /dev/null +++ b/plans/README.md @@ -0,0 +1,32 @@ +# 📋 Планы на будущее + +Эта папка содержит планы развития проекта Mnemo Cards. + +## Структура + +Планы сохраняются в формате Markdown с описанием задач, архитектурных решений и стратегий развития. + +## Текущие планы + +- **[микросервисная_архитектура.md](./микросервисная_архитектура.md)** - План разделения монолитного backend на микросервисы + +## Формат планов + +Каждый план содержит: + +- **Название и краткое описание** - что планируется сделать +- **Анализ текущего состояния** - что есть сейчас +- **Детальный план** - как будет реализовано +- **TODO список** - конкретные задачи для выполнения +- **Риски и митигация** - потенциальные проблемы и способы их решения + +## Использование + +Планы используются для: + +- Архитектурных решений +- Долгосрочного планирования +- Документирования стратегических изменений +- Координации работы команды + +После начала реализации плана, TODO список обновляется для отслеживания прогресса. \ No newline at end of file diff --git a/plans/telegram_bot_product_features.md b/plans/telegram_bot_product_features.md new file mode 100644 index 0000000..624ac0a --- /dev/null +++ b/plans/telegram_bot_product_features.md @@ -0,0 +1,1352 @@ +--- +name: Telegram Bot Product Features +overview: Добавление продуктовых фич в Telegram бот для повышения вовлеченности пользователей и создания привычки ежедневного обучения. Фокус на простых, но эффективных механиках для MVP стадии. +todos: + - id: daily-command + content: Реализовать команду /daily для карточки дня с изображением + status: pending + - id: stats-command + content: Реализовать команду /stats и backend endpoint для статистики + status: pending + - id: streaks-tracking + content: Добавить tracking стриков в PostgreSQL (таблица user_streaks) + status: pending + - id: quiz-command + content: Реализовать мини-квиз /quiz с inline keyboard + status: pending + - id: reminders-command + content: Реализовать настройку напоминаний /reminders и cron job для отправки + status: pending + - id: achievements + content: Добавить автоматические поздравления с достижениями + status: pending + - id: collectible-cards-system + content: Реализовать систему коллекционных карточек с редкостью (Common/Rare/Legendary) + status: pending + - id: collection-command + content: Реализовать команду /collection для просмотра коллекции + status: pending + - id: card-variants-generation + content: Добавить генерацию изображений с разными рамками для редкостей + status: pending + - id: tests + content: Написать unit и integration тесты для новых фич + status: pending +--- + +# План продуктовых фич для Telegram бота Mnemo Cards + +## Обзор + +Текущий бот выполняет технические функции (бэкап, авторизация, админ-панель) и имеет базовую команду `/share`. Предлагается добавить **продуктовые фичи для вовлечения и контента**, которые помогут: + +- Создать привычку ежедневного использования +- Повысить удержание пользователей +- Предоставить дополнительную ценность помимо основного приложения +- Собрать аналитику вовлеченности + +## Приоритетные фичи (MVP) + +### 1. 🎴 Карточка дня (`/daily` команда) + +**Описание:** Пользователь получает случайную карточку для изучения + +**UX Flow:** + +``` +Пользователь: /daily +Бот: + [Красивое изображение карточки] + + 🇪🇸 el perro + 🇷🇺 собака + + 💡 Мнемоника: [mnemo text] + 🔊 Транскрипция: [transcription] + + Хочешь проверить себя? Нажми /quiz +``` + +**Реализация:** + +- Новая команда в [`bin/main.dart`](mnemo_cards_telegram_bot/bin/main.dart) → `teledart.onCommand('daily')` +- Использовать существующий `BackendClient.getRandomCard()` +- Повторно использовать `ImageGenerator` из `/share` команды +- Отправлять фото + caption с информацией о карточке + +**Backend изменения:** + +- Endpoint уже есть: `/api/v2/telegram-bot/random-card` +- Добавить опциональный параметр `?userId=` для персонализации (учитывать уже изученные карточки) + +--- + +### 2. 📊 Статистика и стрики (`/stats` команда) + +**Описание:** Показать пользователю его прогресс и мотивационные метрики + +**UX Flow:** + +``` +Пользователь: /stats +Бот: + 📈 Твоя статистика + + 🔥 Стрик: 7 дней + 📚 Изучено карточек: 42 + ✅ Завершено тестов: 5 + ⏱ Последняя активность: 2 часа назад + + Так держать! Продолжай в том же духе! 💪 +``` + +**Реализация:** + +- Новая команда `/stats` в [`bin/main.dart`](mnemo_cards_telegram_bot/bin/main.dart) +- Новый метод в [`lib/backend_client.dart`](mnemo_cards_telegram_bot/lib/backend_client.dart): + ```dart + Future getUserStats(String telegramUserId) + ``` + + +**Backend изменения:** + +- Новый endpoint: `GET /api/v2/telegram-bot/users/:telegramUserId/stats` +- Использовать существующую логику из [`lib/statistics/statistics_calculator.dart`](mnemo_cards_backend/lib/statistics/statistics_calculator.dart) +- Добавить tracking стриков (consecutive days of activity) +- Новая таблица `user_streaks` в PostgreSQL: + ```sql + CREATE TABLE user_streaks ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id), + telegram_user_id TEXT, + current_streak INTEGER DEFAULT 0, + longest_streak INTEGER DEFAULT 0, + last_activity_date DATE, + created_at TIMESTAMP DEFAULT NOW() + ); + ``` + + +--- + +### 3. ⏰ Ежедневные напоминания + +**Описание:** Бот отправляет напоминание о занятиях в выбранное время + +**UX Flow:** + +``` +Пользователь: /reminders +Бот: + ⏰ Настрой напоминания + + [Inline keyboard:] + - 09:00 утра + - 12:00 день + - 18:00 вечер + - 21:00 ночь + - 🚫 Отключить + +После выбора: +Бот: ✅ Буду напоминать тебе каждый день в 18:00! + +--- + +В выбранное время: +Бот: + 👋 Время изучать новые слова! + + Попробуй карточку дня → /daily + Или пройди быстрый квиз → /quiz +``` + +**Реализация:** + +- Новая команда `/reminders` с inline keyboard (используя `teledart.sendMessage` с `replyMarkup`) +- Хранение настроек в БД (новая таблица `user_reminder_settings`) +- **Cron job** в [`lib/cron/`](mnemo_cards_backend/lib/cron/) для отправки напоминаний: + ```dart + // lib/cron/send_reminders.dart + class SendRemindersTask extends Task { + @override + String get name => 'Send Reminders'; + + @override + Duration get interval => const Duration(minutes: 30); + + @override + Future run() async { + // Проверять каждые 30 минут, кому нужно отправить + // Отправлять через Telegram Bot API + } + } + ``` + + +**Backend изменения:** + +- Новый endpoint: `POST /api/v2/telegram-bot/reminders/set` +- Новый endpoint: `GET /api/v2/telegram-bot/reminders/pending` +- Новая таблица: + ```sql + CREATE TABLE user_reminder_settings ( + id SERIAL PRIMARY KEY, + telegram_user_id TEXT UNIQUE, + reminder_time TIME, -- время в UTC + timezone TEXT, -- часовой пояс пользователя + enabled BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT NOW() + ); + ``` + + +--- + +### 4. 🎯 Мини-квиз (`/quiz` команда) + +**Описание:** Быстрая проверка знаний из 3-5 вопросов прямо в боте + +**UX Flow:** + +``` +Пользователь: /quiz +Бот: + 🎯 Быстрый квиз + + Вопрос 1/3 + Как переводится "el perro"? + + [Inline keyboard:] + A) кошка + B) собака ✅ + C) птица + D) рыба + +После ответа: +Бот: + ✅ Правильно! (или ❌ Неправильно, правильный ответ: собака) + + [Следующий вопрос...] + +После завершения: +Бот: + 🎉 Квиз завершен! + + Правильных ответов: 2/3 + Твой результат: 67% + + Продолжай практиковаться → /daily +``` + +**Реализация:** + +- Новая команда `/quiz` с inline keyboard +- Состояние квиза хранить в памяти (Map с ключом chat_id) +- Использовать `teledart.onCallbackQuery` для обработки нажатий кнопок +- Генерировать вопросы на бэкенде: + - Правильный ответ + - 3 неправильных варианта из других карточек + +**Backend изменения:** + +- Новый endpoint: `POST /api/v2/telegram-bot/quiz/generate` + - Параметр: `userId` (опционально, для персонализации) + - Параметр: `count` (количество вопросов, default: 3) +- Endpoint возвращает: + ```json + { + "quizId": "uuid", + "questions": [ + { + "questionId": "uuid", + "question": "Как переводится 'el perro'?", + "options": ["кошка", "собака", "птица", "рыба"], + "correctIndex": 1 + } + ] + } + ``` + +- Новый endpoint: `POST /api/v2/telegram-bot/quiz/submit` + - Записывать результаты квиза для статистики + +--- + +### 5. 🎊 Мотивационные события (автоматические) + +**Описание:** Бот автоматически поздравляет с достижениями + +**События:** + +- 🔥 Стрик 3 дня: "Отличное начало! Продолжай в том же духе!" +- 🔥 Стрик 7 дней: "Целая неделя! Ты молодец! 🎉" +- 🔥 Стрик 14 дней: "Две недели ежедневных занятий! Потрясающе!" +- 🔥 Стрик 30 дней: "Месяц без перерывов! Ты легенда! 🏆" +- 📚 Изучено 10/50/100 карточек +- ✅ Завершено 5/10/20 тестов + +**Реализация:** + +- **Webhook или cron job** для проверки достижений +- Новый cron task: `CheckAchievementsTask` каждый час +- При достижении milestone → отправить сообщение пользователю + +**Backend изменения:** + +- Новая таблица `user_achievements`: + ```sql + CREATE TABLE user_achievements ( + id SERIAL PRIMARY KEY, + telegram_user_id TEXT, + achievement_type TEXT, -- 'streak_3', 'streak_7', 'cards_10', etc. + achieved_at TIMESTAMP DEFAULT NOW(), + notified BOOLEAN DEFAULT false + ); + ``` + +- Endpoint для получения новых достижений: `GET /api/v2/telegram-bot/achievements/pending` + +--- + +### 6. 🃏 Коллекционные карточки (Collectible Cards System) + +**Описание:** Система коллекционирования карточек с разными уровнями редкости. Одно и то же слово может иметь несколько вариантов карточек с разными изображениями и редкостью. + +#### Концепция редкости + +**Три уровня редкости:** + +1. **🟢 Common (Обычная)** + - Базовое изображение карточки + - Серая рамка + - Получение: автоматически при изучении слова или через `/daily` + +2. **🔵 Rare (Редкая)** + - Улучшенное/детальное изображение + - Синяя рамка с эффектом свечения + - Получение: за достижения (стрик 7 дней, квиз 100%, и т.д.) + +3. **🟣 Legendary (Легендарная)** + - Уникальное артовое изображение + - Золотая рамка с блеском и эффектами + - Получение: за высокие достижения (стрик 30+ дней, особые награды) + +#### Пример: слово "el perro" + +``` +el perro (собака) +├─ 🟢 Common ✅ Получена 15.12.2024 +├─ 🔵 Rare ✅ Получена 20.12.2024 (стрик 7 дней) +└─ 🟣 Legendary 🔒 Требуется: стрик 30 дней (текущий: 12) +``` + +#### UX Flow + +**Команда `/collection` - просмотр коллекции:** + +``` +Пользователь: /collection +Бот: + 📚 Твоя коллекция + + 📊 Общий прогресс: 45/450 карточек (10%) + 🟢 Common: 15/150 слов (10%) + 🔵 Rare: 8/150 слов (5%) + 🟣 Legendary: 2/150 слов (1%) + + [Inline keyboard:] + - 📖 По словам + - 🎨 По редкости + - 📦 По пакам + - 🔍 Поиск слова + +При выборе "По словам": + + 🇪🇸 Испанский (15 слов) + + 📝 el perro (собака) + ✅🟢 ✅🔵 🔒🟣 + + 📝 el gato (кот) + ✅🟢 🔒🔵 🔒🟣 + + 📝 la casa (дом) + ✅🟢 🔒🔵 🔒🟣 + + [Кнопка: Показать детали] +``` + +**Команда `/showcase [слово]` - детали карточки:** + +``` +Пользователь: /showcase el perro +Бот: + [Отправляет изображение Common версии с серой рамкой] + + 🟢 COMMON + 🇪🇸 el perro + 🇷🇺 собака + ✅ В коллекции с 15.12.2024 + + [Отправляет изображение Rare версии с синей рамкой] + + 🔵 RARE + 🇪🇸 el perro + 🇷🇺 собака + ✅ Получена за стрик 7 дней (20.12.2024) + + [Placeholder для Legendary] + + 🟣 LEGENDARY + 🇪🇸 el perro + 🇷🇺 собака + 🔒 Как получить: стрик 30 дней + 📊 Твой прогресс: 12/30 дней (осталось 18) +``` + +**Обновленная команда `/daily` с коллекционированием:** + +``` +Пользователь: /daily +Бот: + [Красивое изображение карточки с рамкой редкости] + + 🎴 Карточка дня + 🇪🇸 el perro + 🇷🇺 собака + + 💡 Мнемоника: [текст] + 🔊 Транскрипция: [текст] + + 📦 Коллекция: + ✅ 🟢 Common - уже в коллекции + 🔒 🔵 Rare - пройди квиз на 100% + 🔒 🟣 Legendary - стрик 30 дней + + Хочешь проверить себя? → /quiz + Посмотреть коллекцию → /collection +``` + +**Получение редкой карточки (событие):** + +``` +[После достижения стрика 7 дней] + +Бот: + 🎉 ПОЗДРАВЛЯЕМ! + + 🔥 Ты держишь стрик 7 дней подряд! + + 🎁 Награда: случайная RARE карточка! + + [Анимация/эффект открытия "пака"] + [Задержка 1-2 секунды] + + [Отправляет изображение с синей рамкой и блеском] + + ✨ 🔵 RARE CARD! ✨ + + 🇪🇸 el perro + 🇷🇺 собака + + Это улучшенная версия карточки! + Теперь у тебя 2/3 версии этого слова! + + Посмотри свою коллекцию → /collection +``` + +**Обновленная команда `/stats` с коллекцией:** + +``` +Пользователь: /stats +Бот: + 📈 Твоя статистика + + 🔥 Стрик: 12 дней + 📚 Изучено слов: 42 + ✅ Завершено тестов: 5 + + 📦 Коллекция карточек: + 🟢 Common: 42/150 (28%) + 🔵 Rare: 8/150 (5%) + 🟣 Legendary: 2/150 (1%) + + 🏆 Ближайшие награды: + - 🔥 Стрик 14 дней → 1 Rare карточка (через 2 дня) + - 🔥 Стрик 30 дней → 1 Legendary! (через 18 дней) + - 🎯 Квиз 100% → Rare версия карточки из квиза + + Продолжай в том же духе! 💪 +``` + +#### Условия получения карточек + +**🟢 Common (автоматически):** +- Изучил слово в основном приложении +- Получил через `/daily` (первый раз) +- Прошел тест, где встретилось слово + +**🔵 Rare (за активность):** +- 🔥 Стрик 7 дней → 1 случайная Rare +- 🔥 Стрик 14 дней → 2 случайные Rare +- 🎯 Квиз 100% правильных → Rare версия одной карточки из квиза +- 📅 Использовал `/daily` 10 раз → выбери любую Rare +- ⭐ Завершил пак полностью → все карточки пака в Rare версии +- 📚 Изучил 50 карточек → 3 Rare на выбор + +**🟣 Legendary (за высокие достижения):** +- 🔥 Стрик 30 дней → 1 случайная Legendary +- 🔥 Стрик 60 дней → 2 случайные Legendary +- 🔥 Стрик 100 дней → 5 Legendary на выбор +- 🎯 Прошел 50 квизов → 1 Legendary на выбор +- 🎯 10 квизов подряд на 100% → 2 Legendary +- 📚 Завершил 5 паков → 3 Legendary на выбор +- 🏆 Топ-10 в месячном рейтинге активности → 2 Legendary + +#### Визуальные отличия + +**Генерация изображений с разными рамками:** + +```dart +// Обновленный ImageGenerator +class ImageGenerator { + Future generateCardImage({ + required GameCardModel card, + required CardRarity rarity, + }) async { + final baseImage = await _loadCardImage(card.image); + final frame = _getFrameForRarity(rarity); + final effects = _getEffectsForRarity(rarity); + + return _composeImage(baseImage, frame, effects); + } + + FrameStyle _getFrameForRarity(CardRarity rarity) { + switch (rarity) { + case CardRarity.common: + return FrameStyle( + color: 0xFF808080, // Серый + width: 40, + glowIntensity: 0, + ); + + case CardRarity.rare: + return FrameStyle( + color: 0xFF4169E1, // Синий + width: 50, + glowIntensity: 0.3, + glowColor: 0xFF6495ED, + ); + + case CardRarity.legendary: + return FrameStyle( + color: 0xFFFFD700, // Золотой + width: 60, + glowIntensity: 0.6, + glowColor: 0xFFFFA500, + sparkles: true, + gradient: true, + ); + } + } +} +``` + +**Цветовая схема:** +- 🟢 Common: #808080 (серый) +- 🔵 Rare: #4169E1 (royal blue) + свечение +- 🟣 Legendary: #FFD700 (золотой) + градиент + блестки + +#### Социальный аспект + +**Команда `/share_card [слово]` - поделиться карточкой:** + +``` +Пользователь: /share_card el perro +Бот: + Какую версию карточки хочешь показать? + + [Inline keyboard:] + - 🟢 Common + - 🔵 Rare ✅ + - 🟣 Legendary 🔒 + +После выбора Rare: + + [Генерирует красивое изображение с синей рамкой] + + Вот твоя RARE карточка! 🔵 + + Ты можешь поделиться ей: + - Отправь друзьям в Telegram + - Опубликуй в соц. сетях + + 💬 Сообщение для друзей: + "Смотри какую редкую карточку я собрал в Mnemo Cards! 🔵 + + Присоединяйся и собери свою коллекцию! + [ссылка на бота]" +``` + +#### Техническая реализация + +**Новые таблицы PostgreSQL:** + +```sql +-- Варианты карточек (все возможные collectibles) +CREATE TABLE card_variants ( + id SERIAL PRIMARY KEY, + card_id TEXT NOT NULL, -- ID основной карточки + rarity TEXT NOT NULL, -- 'common', 'rare', 'legendary' + image_variant TEXT, -- Путь к альтернативному изображению + -- Если NULL, используется базовое с рамкой + frame_color TEXT NOT NULL, -- HEX цвет рамки + frame_width INTEGER DEFAULT 40, -- Ширина рамки в px + has_glow BOOLEAN DEFAULT false, -- Эффект свечения + has_sparkles BOOLEAN DEFAULT false, -- Эффект блесток + unlock_condition TEXT, -- Условие получения + description TEXT, -- Описание варианта + created_at TIMESTAMP DEFAULT NOW(), + + UNIQUE(card_id, rarity) +); + +CREATE INDEX idx_variants_card_id ON card_variants(card_id); +CREATE INDEX idx_variants_rarity ON card_variants(rarity); + +-- Коллекция пользователя +CREATE TABLE user_card_collection ( + id SERIAL PRIMARY KEY, + telegram_user_id TEXT NOT NULL, + card_variant_id INTEGER REFERENCES card_variants(id), + obtained_at TIMESTAMP DEFAULT NOW(), + obtained_via TEXT, -- 'daily', 'quiz', 'streak_7', 'achievement', etc. + + UNIQUE(telegram_user_id, card_variant_id) +); + +CREATE INDEX idx_collection_user ON user_card_collection(telegram_user_id); +CREATE INDEX idx_collection_obtained ON user_card_collection(obtained_at); + +-- Прогресс коллекции (кэш для быстрого доступа) +CREATE TABLE user_collection_progress ( + id SERIAL PRIMARY KEY, + telegram_user_id TEXT UNIQUE NOT NULL, + total_cards INTEGER DEFAULT 0, + common_count INTEGER DEFAULT 0, + rare_count INTEGER DEFAULT 0, + legendary_count INTEGER DEFAULT 0, + updated_at TIMESTAMP DEFAULT NOW() +); +``` + +**Backend endpoints:** + +``` +GET /api/v2/telegram-bot/collection/:telegramUserId + - Получить коллекцию пользователя + - Query params: ?rarity=common|rare|legendary, ?pack_id=X + +POST /api/v2/telegram-bot/collection/add + - Добавить карточку в коллекцию + - Body: { telegramUserId, cardId, rarity, obtainedVia } + +GET /api/v2/telegram-bot/card-variants/:cardId + - Получить все варианты карточки (common, rare, legendary) + +GET /api/v2/telegram-bot/collection/:telegramUserId/progress + - Получить статистику коллекции + +POST /api/v2/telegram-bot/collection/award-rare + - Наградить случайной rare карточкой + - Body: { telegramUserId, reason } + +POST /api/v2/telegram-bot/collection/award-legendary + - Наградить legendary карточкой + - Body: { telegramUserId, cardId (optional), reason } +``` + +**Изменения в боте:** + +``` +mnemo_cards_telegram_bot/ +├── lib/ +│ ├── backend_client.dart [ОБНОВИТЬ] +│ │ + getCollection() +│ │ + getCardVariants() +│ │ + addToCollection() +│ │ + getCollectionProgress() +│ │ + awardRareCard() +│ │ + awardLegendaryCard() +│ │ +│ ├── image_generator.dart [ОБНОВИТЬ] +│ │ + generateCardImage(card, rarity) +│ │ + _getFrameForRarity() +│ │ + _addGlowEffect() +│ │ + _addSparkles() +│ │ +│ └── models/ +│ ├── card_rarity.dart [НОВЫЙ] +│ ├── card_variant.dart [НОВЫЙ] +│ └── user_collection.dart [НОВЫЙ] +│ +└── bin/ + └── main.dart [ОБНОВИТЬ] + + onCommand('collection') + + onCommand('showcase') + + onCommand('share_card') + + обновить onCommand('daily') - показывать прогресс коллекции + + обновить onCommand('stats') - добавить статистику коллекции +``` + +**Изменения в бэкенде:** + +``` +mnemo_cards_backend/ +├── lib/ +│ ├── api/v2/ +│ │ └── telegram_bot_api_v2.dart [ОБНОВИТЬ] +│ │ + GET /telegram-bot/collection/:id +│ │ + POST /telegram-bot/collection/add +│ │ + GET /telegram-bot/card-variants/:cardId +│ │ + POST /telegram-bot/collection/award-rare +│ │ + POST /telegram-bot/collection/award-legendary +│ │ +│ ├── database/tables/ +│ │ ├── card_variants.dart [НОВЫЙ] +│ │ ├── user_card_collection.dart [НОВЫЙ] +│ │ └── user_collection_progress.dart [НОВЫЙ] +│ │ +│ ├── collection/ +│ │ ├── collection_manager.dart [НОВЫЙ] +│ │ │ - Логика добавления карточек +│ │ │ - Проверка условий unlock +│ │ │ - Награждение карточками +│ │ │ +│ │ └── rarity_calculator.dart [НОВЫЙ] +│ │ - Определение редкости при награждении +│ │ - Случайный выбор карточек для наград +│ │ +│ └── cron/ +│ └── collection_rewards.dart [НОВЫЙ] + - Автоматическое награждение за стрики/достижения + - Интеграция с системой достижений +``` + +#### Геймификация и мотивация + +**Прогресс-системы:** + +1. **Коллекционирование по пакам:** + - "Собери все Common карточки пака 'Животные'" → награда: 1 Rare любой карточки пака + - "Собери все Rare карточки пака" → награда: 1 Legendary из пака + +2. **Коллекционирование по редкости:** + - "Собери 10 Rare карточек" → награда: 1 Legendary на выбор + - "Собери первую Legendary" → достижение + особая награда + +3. **Тематические коллекции:** + - "Собери все карточки с животными" → награда + - "Собери все карточки с едой" → награда + +**Лидерборд коллекционеров (опционально):** + +``` +Команда: /leaderboard collection + +Бот: + 🏆 Топ коллекционеров + + 🥇 @username1 - 145/450 (32%) + 🟣 15 Legendary + + 🥈 @username2 - 132/450 (29%) + 🟣 12 Legendary + + 🥉 @username3 - 128/450 (28%) + 🟣 10 Legendary + + ... + + 🔹 Ты - #15 - 45/450 (10%) + 🟣 2 Legendary +``` + +#### Канал (если будет) + +**Еженедельный пост "Карточка недели":** + +``` +🌟 КАРТОЧКА НЕДЕЛИ + +[Изображение Legendary карточки с золотой рамкой] + +🟣 LEGENDARY +🇪🇸 el perro (собака) + +Это одна из самых популярных легендарных карточек! + +💎 Как получить: +- 🔥 Стрик 30 дней +- 🎯 Завершить 3 пака подряд + +📊 Статистика: +- У 5% пользователей есть эта карточка +- Средний срок получения: 45 дней + +Сколько Legendary карточек у тебя? 👇 +``` + +**Событие "Редкие выходные":** + +``` +🎉 СОБЫТИЕ: РЕДКИЕ ВЫХОДНЫЕ! + +В эти выходные (23-24 декабря): +- 🔵 Шанс получить Rare увеличен вдвое! +- 🎁 Каждый квиз на 80%+ даёт Rare карточку +- 🎯 /daily даёт Rare вместо Common + +Не упусти шанс! Время ограничено! ⏰ +``` + +#### Дальнейшее развитие (Phase 3) + +**Обмен карточками между пользователями:** +``` +/trade @username + - Предложить обмен: 2 твои Rare на 1 его Rare + - Система подтверждения обмена + - История обменов +``` + +**Крафтинг (объединение карточек):** +``` +/craft el perro + - 3 Common → 1 Rare (того же слова) + - 3 Rare → 1 Legendary + - Требует "пыль" (dust) - получаешь за дубликаты +``` + +**Сезонные/тематические карточки:** +``` +- Новогодние версии (декабрь) +- Летние версии (июль-август) +- Halloween версии (октябрь) +- Ограниченное время получения +``` + +--- + +## Дополнительные фичи (Phase 2) + +### 7. 📢 Telegram канал (опционально) + +Если канал будет создан, можно автоматизировать: + +**Карточка дня в канале:** + +- Автопостинг красивого изображения с карточкой в канал каждый день в 9:00 +- Использовать `ImageGenerator` + `Telegram.sendPhoto` +- Реализация: новый cron task `PostDailyCardTask` + +**Еженедельные итоги:** + +- Каждое воскресенье пост с статистикой: + - Количество активных пользователей за неделю + - Самые популярные карточки + - Мотивационное сообщение + +--- + +## Технические детали + +### Структура проекта + +**Изменения в боте:** + +``` +mnemo_cards_telegram_bot/ +├── lib/ +│ ├── backend_client.dart [ОБНОВИТЬ] +│ │ + getUserStats() +│ │ + generateQuiz() +│ │ + submitQuiz() +│ │ + setReminder() +│ │ + getPendingReminders() +│ │ + getPendingAchievements() +│ │ + getCollection() [НОВЫЙ - для коллекционных карточек] +│ │ + getCardVariants() [НОВЫЙ] +│ │ + addToCollection() [НОВЫЙ] +│ │ + getCollectionProgress() [НОВЫЙ] +│ │ + getPendingCardRewards() [НОВЫЙ] +│ │ +│ ├── bot_config.dart [БЕЗ ИЗМЕНЕНИЙ] +│ │ +│ ├── image_generator.dart [ОБНОВИТЬ] +│ │ + generateCardImage(card, rarity) [НОВЫЙ] +│ │ + _getFrameForRarity() [НОВЫЙ] +│ │ + _addGlowEffect() [НОВЫЙ] +│ │ + _addSparklesEffect() [НОВЫЙ] +│ │ +│ ├── quiz_manager.dart [НОВЫЙ] +│ │ - In-memory state для активных квизов +│ │ - Логика генерации inline keyboards +│ │ +│ └── models/ [НОВАЯ ПАПКА] +│ ├── card_rarity.dart [НОВЫЙ] +│ ├── card_variant.dart [НОВЫЙ] +│ └── user_collection.dart [НОВЫЙ] +│ +└── bin/ + └── main.dart [ОБНОВИТЬ] + + onCommand('daily') + + onCommand('stats') + + onCommand('reminders') + + onCommand('quiz') + + onCommand('collection') [НОВЫЙ] + + onCommand('showcase') [НОВЫЙ] + + onCommand('share_card') [НОВЫЙ] + + onCallbackQuery (для квизов, напоминаний, коллекции) +``` + +**Изменения в бэкенде:** + +``` +mnemo_cards_backend/ +├── lib/ +│ ├── api/v2/ +│ │ └── telegram_bot_api_v2.dart [НОВЫЙ] +│ │ + GET /telegram-bot/users/:id/stats +│ │ + POST /telegram-bot/quiz/generate +│ │ + POST /telegram-bot/quiz/submit +│ │ + POST /telegram-bot/reminders/set +│ │ + GET /telegram-bot/reminders/pending +│ │ + GET /telegram-bot/achievements/pending +│ │ + GET /telegram-bot/collection/:id [НОВЫЙ] +│ │ + POST /telegram-bot/collection/add [НОВЫЙ] +│ │ + GET /telegram-bot/card-variants/:cardId [НОВЫЙ] +│ │ + GET /telegram-bot/collection/:id/progress [НОВЫЙ] +│ │ + POST /telegram-bot/collection/award-rare [НОВЫЙ] +│ │ + POST /telegram-bot/collection/award-legendary [НОВЫЙ] +│ │ + GET /telegram-bot/rewards/pending [НОВЫЙ] +│ │ +│ ├── database/tables/ +│ │ ├── user_streaks.dart [НОВЫЙ] +│ │ ├── user_reminder_settings.dart [НОВЫЙ] +│ │ ├── user_achievements.dart [НОВЫЙ] +│ │ ├── quiz_results.dart [НОВЫЙ] +│ │ ├── card_variants.dart [НОВЫЙ - коллекционные карточки] +│ │ ├── user_card_collection.dart [НОВЫЙ] +│ │ ├── user_collection_progress.dart [НОВЫЙ] +│ │ └── pending_card_rewards.dart [НОВЫЙ] +│ │ +│ ├── collection/ [НОВАЯ ПАПКА] +│ │ ├── collection_manager.dart [НОВЫЙ] +│ │ │ - Добавление карточек в коллекцию +│ │ │ - Проверка unlock conditions +│ │ │ - Награждение карточками +│ │ │ - Обновление прогресса +│ │ │ +│ │ ├── rarity_calculator.dart [НОВЫЙ] +│ │ │ - Определение редкости для наград +│ │ │ - Случайный выбор карточек +│ │ │ - Весовые коэффициенты +│ │ │ +│ │ └── variant_generator.dart [НОВЫЙ] +│ │ - Генерация вариантов карточек при инициализации +│ │ - Создание Common/Rare/Legendary версий +│ │ +│ ├── cron/ +│ │ ├── send_reminders.dart [НОВЫЙ] +│ │ ├── check_achievements.dart [НОВЫЙ] +│ │ ├── collection_rewards.dart [НОВЫЙ - награды за коллекцию] +│ │ └── post_daily_card.dart [НОВЫЙ, опционально для канала] +│ │ +│ └── statistics/ +│ └── streak_calculator.dart [НОВЫЙ] +``` + +--- + +## Миграции базы данных + +### Новые таблицы PostgreSQL + +```sql +-- User streaks tracking +CREATE TABLE user_streaks ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id), + telegram_user_id TEXT UNIQUE, + current_streak INTEGER DEFAULT 0, + longest_streak INTEGER DEFAULT 0, + last_activity_date DATE, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX idx_user_streaks_telegram_user_id ON user_streaks(telegram_user_id); +CREATE INDEX idx_user_streaks_last_activity ON user_streaks(last_activity_date); + +-- Reminder settings +CREATE TABLE user_reminder_settings ( + id SERIAL PRIMARY KEY, + telegram_user_id TEXT UNIQUE, + reminder_time TIME NOT NULL, + timezone TEXT DEFAULT 'UTC', + enabled BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX idx_reminder_settings_time ON user_reminder_settings(reminder_time, enabled); + +-- Achievements +CREATE TABLE user_achievements ( + id SERIAL PRIMARY KEY, + telegram_user_id TEXT NOT NULL, + achievement_type TEXT NOT NULL, + achieved_at TIMESTAMP DEFAULT NOW(), + notified BOOLEAN DEFAULT false +); + +CREATE INDEX idx_achievements_notified ON user_achievements(notified); +CREATE INDEX idx_achievements_telegram_user ON user_achievements(telegram_user_id); + +-- Quiz results +CREATE TABLE quiz_results ( + id SERIAL PRIMARY KEY, + telegram_user_id TEXT NOT NULL, + quiz_id TEXT NOT NULL, + total_questions INTEGER NOT NULL, + correct_answers INTEGER NOT NULL, + completed_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX idx_quiz_results_telegram_user ON quiz_results(telegram_user_id); + +-- ================================================== +-- COLLECTIBLE CARDS SYSTEM +-- ================================================== + +-- Card variants (all possible collectible versions) +CREATE TABLE card_variants ( + id SERIAL PRIMARY KEY, + card_id TEXT NOT NULL, -- ID основной карточки (game_card.id) + rarity TEXT NOT NULL, -- 'common', 'rare', 'legendary' + image_variant TEXT, -- Путь к альтернативному изображению + -- NULL = использовать базовое изображение с рамкой + frame_color TEXT NOT NULL, -- HEX цвет рамки (например: '#808080') + frame_width INTEGER DEFAULT 40, -- Ширина рамки в пикселях + has_glow BOOLEAN DEFAULT false, -- Эффект свечения вокруг рамки + has_sparkles BOOLEAN DEFAULT false, -- Эффект блесток/искр + unlock_condition TEXT, -- Текстовое описание условия получения + description TEXT, -- Описание варианта карточки + created_at TIMESTAMP DEFAULT NOW(), + + UNIQUE(card_id, rarity), + CHECK (rarity IN ('common', 'rare', 'legendary')) +); + +CREATE INDEX idx_variants_card_id ON card_variants(card_id); +CREATE INDEX idx_variants_rarity ON card_variants(rarity); + +-- User card collection (что собрал пользователь) +CREATE TABLE user_card_collection ( + id SERIAL PRIMARY KEY, + telegram_user_id TEXT NOT NULL, + card_variant_id INTEGER NOT NULL REFERENCES card_variants(id) ON DELETE CASCADE, + obtained_at TIMESTAMP DEFAULT NOW(), + obtained_via TEXT, -- 'daily', 'quiz_perfect', 'streak_7', 'streak_30', 'achievement', etc. + + UNIQUE(telegram_user_id, card_variant_id) +); + +CREATE INDEX idx_collection_user ON user_card_collection(telegram_user_id); +CREATE INDEX idx_collection_obtained ON user_card_collection(obtained_at); +CREATE INDEX idx_collection_obtained_via ON user_card_collection(obtained_via); + +-- User collection progress (кэш для быстрого доступа к статистике) +CREATE TABLE user_collection_progress ( + id SERIAL PRIMARY KEY, + telegram_user_id TEXT UNIQUE NOT NULL, + total_unique_words INTEGER DEFAULT 0, -- Количество уникальных слов в коллекции + total_cards INTEGER DEFAULT 0, -- Общее количество карточек (с учетом редкостей) + common_count INTEGER DEFAULT 0, + rare_count INTEGER DEFAULT 0, + legendary_count INTEGER DEFAULT 0, + completion_percentage DECIMAL(5,2) DEFAULT 0.0, + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX idx_collection_progress_user ON user_collection_progress(telegram_user_id); + +-- Pending card rewards (очередь наград, которые нужно выдать) +CREATE TABLE pending_card_rewards ( + id SERIAL PRIMARY KEY, + telegram_user_id TEXT NOT NULL, + reward_type TEXT NOT NULL, -- 'rare_random', 'legendary_random', 'rare_choice', 'legendary_choice' + quantity INTEGER DEFAULT 1, + reason TEXT, -- 'streak_7', 'streak_30', 'quiz_perfect', etc. + card_choices TEXT[], -- Массив card_id для выбора (если reward_type содержит 'choice') + claimed BOOLEAN DEFAULT false, + created_at TIMESTAMP DEFAULT NOW(), + claimed_at TIMESTAMP +); + +CREATE INDEX idx_pending_rewards_user ON pending_card_rewards(telegram_user_id, claimed); +CREATE INDEX idx_pending_rewards_claimed ON pending_card_rewards(claimed); +``` + +--- + +## Аналитика и метрики + +После реализации фич, отслеживать: + +**Вовлеченность:** + +- DAU (Daily Active Users) через команды +- Частота использования команд `/daily`, `/quiz`, `/stats` +- Retention rate (возвращаются ли пользователи на следующий день) + +**Эффективность:** + +- Средняя длина стрика +- Процент пользователей с настроенными напоминаниями +- Процент прохождения квизов (completion rate) +- Средний результат в квизах + +**Endpoints для админа:** + +``` +GET /api/v2/admin/telegram-bot/analytics +- Количество активных пользователей бота +- Популярные команды +- Средняя длина стриков +- Retention metrics +``` + +--- + +## Приоритеты реализации + +### MVP (Iteration 1) - 2-3 недели + +1. ✅ `/daily` - Карточка дня (базовая версия без коллекционирования) +2. ✅ `/stats` - Базовая статистика +3. ✅ Backend: Endpoints для карточек и статистики +4. ✅ Backend: Таблица user_streaks + +### Iteration 2 - 1-2 недели + +5. ✅ `/quiz` - Мини-квизы +6. ✅ Backend: Quiz generation и results tracking + +### Iteration 3 (Коллекционные карточки) - 2-3 недели + +7. ✅ Система коллекционных карточек: + - Backend: таблицы card_variants, user_card_collection, user_collection_progress + - Backend: endpoints для коллекции + - Генерация вариантов карточек с разными рамками (Common/Rare/Legendary) + +8. ✅ Команда `/collection` - просмотр коллекции + +9. ✅ Команда `/showcase [слово]` - детали карточки + +10. ✅ Обновление `/daily` - интеграция с коллекцией + +11. ✅ Обновление `/stats` - статистика коллекции + +12. ✅ Система награждения карточками за достижения + +### Iteration 4 - 1-2 недели + +13. ✅ `/reminders` - Напоминания +14. ✅ Cron job для отправки напоминаний + +### Iteration 5 - 1 неделя + +15. ✅ Автоматические достижения +16. ✅ Cron job для проверки achievements +17. ✅ Интеграция достижений с наградами коллекционных карточек + +### Iteration 6 (Социальные фичи) - 1-2 недели + +18. ✅ `/share_card` - поделиться карточкой +19. ✅ `/leaderboard collection` - топ коллекционеров (опционально) +20. ✅ Тематические события для коллекции (опционально) + +### Optional (если канал появится) + +21. 📢 Автопостинг в канал +22. 📊 Еженедельные итоги в канале +23. 🌟 "Карточка недели" в канале +24. 🎉 События "Редкие выходные" + +--- + +## Риски и митигация + +### Технические риски + +**Проблема:** Telegram API rate limits + +- **Митигация:** Использовать батчинг сообщений, queue для напоминаний + +**Проблема:** Сложность state management для квизов + +- **Митигация:** Простой in-memory Map, ограничить время жизни квиза (15 минут) + +**Проблема:** Часовые пояса для напоминаний + +- **Митигация:** Начать с UTC, потом добавить определение timezone через location + +### Продуктовые риски + +**Проблема:** Низкая активация (пользователи не используют команды) + +- **Митигация:** + - Добавить onboarding после авторизации + - Welcome message с перечислением команд + - Push notification после регистрации + +**Проблема:** Спам напоминаниями + +- **Митигация:** + - Максимум 1 напоминание в день + - Легкое отключение через `/reminders` + - Автоматическое отключение после 7 дней неактивности + +--- + +## Дизайн сообщений + +### Единый стиль + +Использовать эмодзи для визуальной привлекательности: + +- 🎴 Карточки +- 📊 Статистика +- 🔥 Стрики +- 🎯 Квизы +- ⏰ Напоминания +- 🎉 Достижения +- ✅ Успех +- ❌ Ошибка + +**Тон общения:** + +- Дружелюбный, мотивирующий +- Короткие сообщения (2-4 строки) +- Призывы к действию (CTA) + +--- + +## Пример onboarding flow + +После первой авторизации через `/start` или `/login`: + +``` +Бот: + Привет! 👋 + + Я бот Mnemo Cards. Помогу тебе учить языки каждый день! + + Что я умею: + 🎴 /daily - карточка дня для изучения + 🎯 /quiz - быстрый квиз для проверки знаний + 📊 /stats - твоя статистика и стрики + ⏰ /reminders - настрой напоминания + + Начнем с карточки дня? → /daily +``` + +--- + +## Тестирование + +### Unit тесты + +- `BackendClient.getUserStats()` → проверить парсинг response +- `BackendClient.generateQuiz()` → проверить структуру квиза +- `QuizManager` → state management для активных квизов +- `StreakCalculator` → логика подсчета стриков + +### Integration тесты + +- End-to-end flow: `/daily` → получить карточку → отправить изображение +- End-to-end flow: `/quiz` → ответить на вопросы → получить результат +- Cron job: `SendRemindersTask` → отправка напоминаний в нужное время + +### Manual QA + +- Тестирование команд в реальном Telegram +- Проверка inline keyboards +- Проверка таймингов напоминаний +- Проверка корректности стриков при активности в разные дни + +--- + +## Документация + +После реализации обновить: + +- [`mnemo_cards_telegram_bot/README.md`](mnemo_cards_telegram_bot/README.md) - описание новых команд +- [`mnemo_cards_backend/README.md`](mnemo_cards_backend/README.md) - новые API endpoints +- Создать `mnemo_cards_telegram_bot/COMMANDS.md` - справочник команд для пользователей + +--- + +## Заключение + +Предложенные фичи создают **полноценную продуктовую экосистему** вокруг Telegram бота: + +✅ **Привычка** - ежедневные напоминания и карточка дня + +✅ **Мотивация** - стрики, достижения и коллекционирование + +✅ **Практика** - квизы для закрепления знаний + +✅ **Прогресс** - видимая статистика успехов и коллекции + +✅ **Удобство** - обучение прямо в Telegram без открытия приложения + +✅ **Геймификация** - система коллекционных карточек с редкостью создает долгосрочную цель + +✅ **Социальный аспект** - возможность делиться своими достижениями и редкими карточками + +### Почему коллекционные карточки важны для retention: + +1. **Долгосрочная мотивация** - собрать все Legendary карточки может занять месяцы +2. **Визуальная награда** - красивые изображения с разными рамками создают эмоциональную связь +3. **Прогресс виден** - пользователь всегда видит, сколько ещё осталось собрать +4. **FOMO эффект** - ограниченные/сезонные карточки заставляют возвращаться +5. **Гордость достижениями** - возможность похвастаться редкими карточками + +Это создает дополнительную ценность для пользователей и значительно повышает retention, что критично для MVP стадии проекта. + +--- + +## Roadmap Summary + +**Минимальный MVP (4-5 недель):** +- Базовые команды: /daily, /stats, /quiz +- Система стриков +- Простая коллекция (только Common карточки) + +**Полноценный MVP (8-10 недель):** +- Все базовые команды +- Полная система коллекционных карточек (Common/Rare/Legendary) +- Напоминания +- Достижения с наградами в виде карточек +- Команды /collection, /showcase + +**Phase 2 (12+ недель):** +- Социальные фичи (/share_card, /leaderboard) +- Telegram канал с автопостингом +- Тематические события +- Обмен карточками (опционально) +- Крафтинг (опционально) diff --git a/plans/микросервисная_архитектура.md b/plans/микросервисная_архитектура.md new file mode 100644 index 0000000..26cd586 --- /dev/null +++ b/plans/микросервисная_архитектура.md @@ -0,0 +1,543 @@ +--- +name: Микросервисная архитектура Mnemo Cards +overview: План разделения монолитного backend на микросервисы с учетом независимого деплоя, разных технологий и организации команды. Используется гибридный подход к базам данных. +todos: + - id: analyze_dependencies + content: Проанализировать зависимости между модулями и создать карту зависимостей + status: pending + - id: design_api_gateway + content: Спроектировать API Gateway для маршрутизации запросов к микросервисам + status: pending + - id: create_shared_jwt_lib + content: Создать shared библиотеку для валидации JWT токенов + status: pending + - id: plan_database_migration + content: Спланировать миграцию базы данных (схемы/отдельные БД) + status: pending + - id: extract_auth_service + content: Выделить Auth Service как первый микросервис + status: pending + - id: extract_payment_service + content: Выделить Payment Service как второй микросервис + status: pending + - id: setup_service_communication + content: Настроить коммуникацию между сервисами (синхронная/асинхронная) + status: pending + - id: setup_monitoring + content: Настроить мониторинг и логирование для микросервисов + status: pending + - id: create_docker_compose + content: Создать docker-compose для локальной разработки всех микросервисов + status: pending +--- + +# План разделения на микросервисы + +## Анализ текущей архитектуры + +Текущий монолит включает следующие доменные области: + +1. **Authentication & User Management** - JWT, Telegram auth, пользователи +2. **Content Management** - Packs, Cards, Tests, Games +3. **Payments & Subscriptions** - YooKassa, Google Play, RuStore +4. **Statistics & Analytics** - User statistics, achievements, word statistics +5. **Admin Panel** - Админские функции +6. **Telegram Bot** - Бот для авторизации +7. **Cron Jobs & Background Tasks** - Фоновые задачи + +## Предлагаемое разделение на микросервисы + +### 1. **Auth Service** (Сервис аутентификации) + +**Ответственность:** + +- JWT токены (access/refresh) +- Регистрация/логин пользователей +- Telegram авторизация (генерация кодов) +- Управление пользователями (CRUD) +- ACL и авторизация + +**База данных:** + +- `users`, `auth`, `tokens`, `refresh_tokens`, `telegram_auth_codes` + +**API Endpoints:** + +- `/api/v2/auth/*` (register, login, refresh, logout, telegram) +- `/api/v2/users/*` (CRUD пользователей) + +**Зависимости:** + +- PostgreSQL (собственная схема) +- Telegram Bot API (для авторизации) + +**Технологии:** + +- Dart + Shelf (можно оставить или перейти на Go/Node.js) + +**Особенности:** + +- Критический сервис - должен быть высокодоступным +- Низкая задержка критична для всех запросов + +--- + +### 2. **Content Service** (Сервис контента) + +**Ответственность:** + +- Управление паками карточек (CardPacks) +- Управление карточками (GameCards) +- Управление тестами (Tests) +- Управление играми (Games assets) +- Голоса карточек (CardVoices) +- Доступ к контенту (проверка прав доступа через Auth Service) + +**База данных:** + +- `card_packs`, `game_cards`, `card_pack_cards`, `card_voices`, `tests`, `tasks` + +**API Endpoints:** + +- `/api/v2/packs/*` +- `/api/v2/tests/*` +- `/api/v2/games/*` +- `/api/v2/voice/*` + +**Зависимости:** + +- PostgreSQL (собственная схема или shared с Payments для связей) +- Auth Service (проверка доступа через API или shared token validation) + +**Технологии:** + +- Dart + Shelf (можно перейти на Python/FastAPI для удобства работы с контентом) + +**Особенности:** + +- Статические файлы (изображения, аудио) - можно вынести в CDN/Storage +- Высокая нагрузка на чтение + +--- + +### 3. **Payment Service** (Сервис платежей) + +**Ответственность:** + +- Обработка платежей через YooKassa +- Обработка IAP (Google Play, RuStore) +- Управление промокодами +- Управление скидками (discounts) +- Покупка паков +- Webhooks от платежных систем + +**База данных:** + +- `payments`, `promo_codes`, `discounts`, `subscriptions` (или отдельный сервис) + +**API Endpoints:** + +- `/api/v2/purchases/*` +- `/api/v2/promocodes/*` +- `/api/v2/discounts/*` +- `/api/webhooks/yookassa`, `/api/webhooks/google-play`, `/api/webhooks/rustore` + +**Зависимости:** + +- PostgreSQL (собственная схема) +- Auth Service (проверка пользователя) +- Content Service (информация о паках для покупки) +- YooKassa API, Google Play API, RuStore API + +**Технологии:** + +- Dart + Shelf или Node.js/TypeScript (удобная работа с webhooks) + +**Особенности:** + +- Высокие требования к безопасности +- Идемпотентность операций +- Обработка webhooks + +--- + +### 4. **Subscription Service** (Сервис подписок) + +**Ответственность:** + +- Управление подписками пользователей +- Проверка активности подписки +- Фичи подписки +- Автопродление (через cron) + +**База данных:** + +- `subscriptions` (может быть shared с Payment Service на этапе миграции) + +**API Endpoints:** + +- `/api/v2/subscriptions/*` + +**Зависимости:** + +- PostgreSQL (собственная схема или shared с Payment) +- Auth Service (проверка пользователя) +- Payment Service (создание подписки через платеж) + +**Технологии:** + +- Dart + Shelf или отдельный микросервис + +**Вариант:** Можно объединить с Payment Service на первом этапе, если логика тесно связана. + +--- + +### 5. **Statistics Service** (Сервис статистики) + +**Ответственность:** + +- Статистика пользователей (user statistics) +- Статистика по словам (word statistics) +- Достижения (achievements) +- Сессии обучения (study sessions) +- Аналитика и отчеты + +**База данных:** + +- `user_datas`, `word_statistics`, `achievements`, `study_sessions`, `pack_progress` + +**API Endpoints:** + +- `/api/v2/users/me/statistics/*` +- `/api/v2/users/me/achievements` +- `/api/v2/users/me/sessions` + +**Зависимости:** + +- PostgreSQL (собственная схема или отдельная БД для аналитики - TimescaleDB/ClickHouse) +- Auth Service (проверка пользователя) +- Content Service (информация о паках/карточках) + +**Технологии:** + +- Dart + Shelf или Python (удобно для аналитики) или Go (высокая производительность) + +**Особенности:** + +- Высокая нагрузка на запись (tracking событий) +- Возможность использовать TimescaleDB для временных рядов +- Отложенная обработка данных (бэтчинг) + +--- + +### 6. **Admin Service** (Админ панель) + +**Ответственность:** + +- Админская авторизация +- Управление контентом (CRUD паков, карточек) +- Аналитика для админов +- Управление пользователями +- Audit логирование + +**База данных:** + +- `audit` (отдельная таблица для логирования действий) +- Использует данные из других сервисов + +**API Endpoints:** + +- `/api/v2/admin/*` + +**Зависимости:** + +- Auth Service (проверка админ прав) +- Content Service (управление контентом) +- Payment Service (управление платежами) +- Statistics Service (аналитика) + +**Технологии:** + +- Dart + Shelf (можно оставить текущий стек) + +**Особенности:** + +- Агрегирует данные из нескольких сервисов +- Требует высоких прав доступа + +--- + +### 7. **Telegram Bot Service** (Сервис Telegram бота) + +**Ответственность:** + +- Обработка команд Telegram бота +- Генерация кодов авторизации для веба +- Интеграция с Auth Service + +**База данных:** + +- Может использовать shared `telegram_auth_codes` или собственную + +**API Endpoints:** + +- `/api/v2/telegram-bot/*` +- Telegram Bot API webhook + +**Зависимости:** + +- Auth Service (создание/проверка кодов) +- Telegram Bot API + +**Технологии:** + +- Dart + Shelf или Node.js/TypeScript (удобные библиотеки для Telegram) + +**Особенности:** + +- Относительно изолированный сервис +- Может работать независимо + +--- + +### 8. **Background Jobs Service** (Сервис фоновых задач) + +**Ответственность:** + +- Cron задачи (backup, проверка платежей, генерация промокодов, etc.) +- Асинхронная обработка задач +- Очереди задач (опционально) + +**Зависимости:** + +- Все остальные сервисы (для выполнения задач) + +**Технологии:** + +- Dart (текущий стек) или перейти на специализированное решение (Bull/BullMQ на Node.js, Celery на Python) + +**Альтернатива:** Можно интегрировать в каждый сервис отдельно или использовать общий Job Queue (Redis + Worker). + +--- + +## Стратегия миграции + +### Фаза 1: Подготовка (1-2 недели) + +1. **Выделить общие компоненты:** + + - Общие DTO/models в `mnemo_cards_common` + - Shared database connection pool + - API Gateway (опционально, можно использовать nginx/kong) + +2. **Создать API Gateway:** + + - Единая точка входа для клиентов + - Маршрутизация к микросервисам + - Агрегация запросов (если нужно) + - Rate limiting, CORS + +### Фаза 2: Выделение критических сервисов (2-4 недели) + +**Приоритет 1: Auth Service** + +- Выделить аутентификацию в отдельный сервис +- Создать JWT validation library для других сервисов +- Мигрировать пользователей + +**Приоритет 2: Payment Service** + +- Выделить платежи +- Изолировать webhooks +- Мигрировать платежные данные + +### Фаза 3: Выделение остальных сервисов (4-8 недель) + +**Приоритет 3: Content Service** + +- Выделить управление контентом +- Вынести статические файлы в CDN/Storage + +**Приоритет 4: Statistics Service** + +- Выделить статистику +- Оптимизировать запись данных (бэтчинг) + +**Приоритет 5: Остальные сервисы** + +- Admin Service +- Telegram Bot Service +- Background Jobs Service + +### Фаза 4: Оптимизация (ongoing) + +- Разделение баз данных (Database per Service) +- Кэширование (Redis) +- Message Queue для асинхронной коммуникации +- Мониторинг и логирование (ELK, Prometheus) + +--- + +## Схема коммуникации между сервисами + +```mermaid +graph TB + Client[Web Client] --> Gateway[API Gateway] + + Gateway --> AuthService[Auth Service] + Gateway --> ContentService[Content Service] + Gateway --> PaymentService[Payment Service] + Gateway --> SubscriptionService[Subscription Service] + Gateway --> StatisticsService[Statistics Service] + Gateway --> AdminService[Admin Service] + + TelegramBot[Telegram Bot] --> AuthService + TelegramBot --> Gateway + + PaymentService --> AuthService + PaymentService --> ContentService + SubscriptionService --> AuthService + SubscriptionService --> PaymentService + StatisticsService --> AuthService + StatisticsService --> ContentService + AdminService --> AuthService + AdminService --> ContentService + AdminService --> PaymentService + AdminService --> StatisticsService + + BackgroundJobs[Background Jobs] --> AuthService + BackgroundJobs --> PaymentService + BackgroundJobs --> StatisticsService + + AuthService --> AuthDB[(Auth DB)] + ContentService --> ContentDB[(Content DB)] + PaymentService --> PaymentDB[(Payment DB)] + SubscriptionService --> SubscriptionDB[(Subscription DB)] + StatisticsService --> StatisticsDB[(Statistics DB)] +``` + +--- + +## Рекомендации по базам данных (гибридный подход) + +### Вариант 1: Shared Database на первом этапе + +- Все сервисы используют одну БД, но разные схемы (namespaces) +- Упрощает миграцию +- Позволяет постепенно разделять + +### Вариант 2: Database per Service (целевое состояние) + +**Auth Service:** + +- `auth_db` - users, tokens, refresh_tokens, telegram_auth_codes + +**Content Service:** + +- `content_db` - card_packs, game_cards, card_voices, tests, tasks + +**Payment Service:** + +- `payment_db` - payments, promo_codes, discounts + +**Subscription Service:** + +- `subscription_db` - subscriptions + +**Statistics Service:** + +- `statistics_db` - user_datas, word_statistics, achievements, study_sessions +- Или TimescaleDB для временных рядов + +### Переходные связи: + +Для связей между сервисами использовать: + +- **API calls** - синхронная коммуникация (для проверки доступа, получения данных) +- **Event-driven** - асинхронная коммуникация (опционально, через message queue) +- **Shared IDs** - использование UUID для связи между сервисами + +--- + +## Технологические рекомендации + +### API Gateway + +- **Kong** или **nginx** - для маршрутизации +- Или **Istio** для более сложных сценариев + +### Service Mesh (опционально) + +- **Istio** или **Linkerd** - для управления трафиком, безопасности, observability + +### Message Queue (для асинхронной коммуникации) + +- **Redis Pub/Sub** или **RabbitMQ** или **NATS** +- Для событий: payment_completed, user_registered, etc. + +### Мониторинг + +- **Prometheus + Grafana** - метрики +- **ELK Stack** (Elasticsearch, Logstash, Kibana) - логи +- **Jaeger** или **Zipkin** - distributed tracing + +### Кэширование + +- **Redis** - для кэширования токенов, контента, статистики + +--- + +## Файлы, которые нужно будет изменить/создать + +### Новые директории для микросервисов: + +- `services/auth-service/` +- `services/content-service/` +- `services/payment-service/` +- `services/subscription-service/` +- `services/statistics-service/` +- `services/admin-service/` +- `services/telegram-bot-service/` +- `services/background-jobs-service/` + +### Общие компоненты: + +- `shared/jwt-validator/` - библиотека для валидации JWT +- `shared/database-migrations/` - миграции БД +- `shared/common-models/` - общие модели (уже есть в `mnemo_cards_common`) + +### Конфигурация: + +- `docker-compose.microservices.yml` - оркестрация всех сервисов +- `infrastructure/` - terraform/k8s конфигурации + +--- + +## Критерии успешности разделения + +1. ✅ Каждый сервис можно деплоить независимо +2. ✅ Сервисы могут использовать разные технологии +3. ✅ Отказ одного сервиса не ломает остальные +4. ✅ Легко масштабировать отдельные сервисы +5. ✅ Четкое разделение ответственности +6. ✅ Тестируемость каждого сервиса отдельно + +--- + +## Риски и митигация + +### Риск 1: Сложность отладки распределенной системы + +**Митигация:** Использовать distributed tracing (Jaeger), централизованное логирование + +### Риск 2: Сетевая задержка между сервисами + +**Митигация:** Кэширование, асинхронная коммуникация где возможно, оптимизация API calls + +### Риск 3: Консистентность данных между сервисами + +**Митигация:** Event-driven архитектура, eventual consistency, saga pattern для транзакций + +### Риск 4: Усложнение деплоя + +**Митигация:** Docker Compose для разработки, Kubernetes для продакшена, CI/CD для каждого сервиса \ No newline at end of file