initial commit (backend)

This commit is contained in:
2026-09-20 11:45:58 +00:00
commit 033adf023e
65 changed files with 28292 additions and 0 deletions
+530
View File
@@ -0,0 +1,530 @@
from fastapi import APIRouter, Request, HTTPException
from auth import require_auth
from database import get_pool
router = APIRouter(prefix="/achievements", tags=["achievements"])
# ============ Данные 52 достижений ============
ACHIEVEMENTS_DATA = [
# ===== Word Search (11) =====
{"code": "first_level", "category": "wordsearch", "icon": "🔰",
"name_pl": "Pierwszy krok", "name_ru": "Первый шаг", "name_uk": "Перший крок", "name_en": "First Step",
"desc_pl": "Ukończ 1 poziom filwordów", "desc_ru": "Пройти 1 уровень филвордов", "desc_uk": "Пройти 1 рівень філвордів", "desc_en": "Complete 1 word search level",
"type": "levels_completed", "value": 1},
{"code": "ten_levels", "category": "wordsearch", "icon": "",
"name_pl": "Gracz", "name_ru": "Игрок", "name_uk": "Гравець", "name_en": "Player",
"desc_pl": "Ukończ 10 poziomów filwordów", "desc_ru": "Пройти 10 уровней филвордов", "desc_uk": "Пройти 10 рівнів філвордів", "desc_en": "Complete 10 word search levels",
"type": "levels_completed", "value": 10},
{"code": "fifty_levels", "category": "wordsearch", "icon": "🏅",
"name_pl": "Ekspert", "name_ru": "Эксперт", "name_uk": "Експерт", "name_en": "Expert",
"desc_pl": "Ukończ 50 poziomów filwordów", "desc_ru": "Пройти 50 уровней филвордов", "desc_uk": "Пройти 50 рівнів філвордів", "desc_en": "Complete 50 word search levels",
"type": "levels_completed", "value": 50},
{"code": "hundred_levels", "category": "wordsearch", "icon": "👑",
"name_pl": "Król", "name_ru": "Король", "name_uk": "Король", "name_en": "King",
"desc_pl": "Ukończ 100 poziomów filwordów", "desc_ru": "Пройти 100 уровней филвордов", "desc_uk": "Пройти 100 рівнів філвордів", "desc_en": "Complete 100 word search levels",
"type": "levels_completed", "value": 100},
{"code": "ws_200", "category": "wordsearch", "icon": "🎯",
"name_pl": "Mistrz", "name_ru": "Мастер", "name_uk": "Майстер", "name_en": "Master",
"desc_pl": "Ukończ 200 poziomów filwordów", "desc_ru": "Пройти 200 уровней филвордов", "desc_uk": "Пройти 200 рівнів філвордів", "desc_en": "Complete 200 word search levels",
"type": "levels_completed", "value": 200},
{"code": "ws_500", "category": "wordsearch", "icon": "🌟",
"name_pl": "Legenda filwordów", "name_ru": "Легенда филвордов", "name_uk": "Легенда філвордів", "name_en": "Word Search Legend",
"desc_pl": "Ukończ 500 poziomów filwordów", "desc_ru": "Пройти 500 уровней филвордов", "desc_uk": "Пройти 500 рівнів філвордів", "desc_en": "Complete 500 word search levels",
"type": "levels_completed", "value": 500},
{"code": "no_hints", "category": "wordsearch", "icon": "💡",
"name_pl": "Bystrzak", "name_ru": "Умница", "name_uk": "Розумник", "name_en": "Smart",
"desc_pl": "Ukończ poziom bez podpowiedzi", "desc_ru": "Пройти уровень без подсказок", "desc_uk": "Пройти рівень без підказок", "desc_en": "Complete a level without hints",
"type": "no_hints_level", "value": 1},
{"code": "streak_5", "category": "wordsearch", "icon": "🔥",
"name_pl": "Seria", "name_ru": "Серия", "name_uk": "Серія", "name_en": "Streak",
"desc_pl": "Ukończ 5 poziomów z rzędu", "desc_ru": "Пройти 5 уровней подряд", "desc_uk": "Пройти 5 рівнів поспіль", "desc_en": "Complete 5 levels in a row",
"type": "level_streak", "value": 5},
{"code": "speed_60", "category": "wordsearch", "icon": "",
"name_pl": "Szybki", "name_ru": "Быстрый", "name_uk": "Швидкий", "name_en": "Fast",
"desc_pl": "Ukończ poziom w mniej niż 60 sekund", "desc_ru": "Пройти уровень быстрее 60 секунд", "desc_uk": "Пройти рівень швидше 60 секунд", "desc_en": "Complete a level in under 60 seconds",
"type": "speed_level", "value": 60},
{"code": "ws_speed_30", "category": "wordsearch", "icon": "🚀",
"name_pl": "Błyskawica", "name_ru": "Молния", "name_uk": "Блискавка", "name_en": "Lightning",
"desc_pl": "Ukończ poziom w mniej niż 30 sekund", "desc_ru": "Пройти уровень быстрее 30 секунд", "desc_uk": "Пройти рівень швидше 30 секунд", "desc_en": "Complete a level in under 30 seconds",
"type": "speed_level", "value": 30},
{"code": "perfect_5", "category": "wordsearch", "icon": "🎯",
"name_pl": "Snajper", "name_ru": "Снайпер", "name_uk": "Снайпер", "name_en": "Sniper",
"desc_pl": "Znajdź 5 słów bez błędu", "desc_ru": "Найти 5 слов подряд без ошибок", "desc_uk": "Знайти 5 слів без помилки", "desc_en": "Find 5 words without mistakes",
"type": "perfect_words", "value": 5},
# ===== Sentence Builder (10) =====
{"code": "first_sentence", "category": "sentence", "icon": "📝",
"name_pl": "Debiutant", "name_ru": "Дебютант", "name_uk": "Дебютант", "name_en": "Debutant",
"desc_pl": "Ułóż 1 zdanie", "desc_ru": "Составить 1 предложение", "desc_uk": "Скласти 1 речення", "desc_en": "Build 1 sentence",
"type": "sentences_completed", "value": 1},
{"code": "ten_sentences", "category": "sentence", "icon": "✍️",
"name_pl": "Autor", "name_ru": "Автор", "name_uk": "Автор", "name_en": "Author",
"desc_pl": "Ułóż 10 zdań", "desc_ru": "Составить 10 предложений", "desc_uk": "Скласти 10 речень", "desc_en": "Build 10 sentences",
"type": "sentences_completed", "value": 10},
{"code": "fifty_sentences", "category": "sentence", "icon": "🎓",
"name_pl": "Profesor", "name_ru": "Профессор", "name_uk": "Професор", "name_en": "Professor",
"desc_pl": "Ułóż 50 zdań", "desc_ru": "Составить 50 предложений", "desc_uk": "Скласти 50 речень", "desc_en": "Build 50 sentences",
"type": "sentences_completed", "value": 50},
{"code": "hundred_sentences", "category": "sentence", "icon": "📚",
"name_pl": "Bibliotekarz", "name_ru": "Библиотекарь", "name_uk": "Бібліотекар", "name_en": "Librarian",
"desc_pl": "Ułóż 100 zdań", "desc_ru": "Составить 100 предложений", "desc_uk": "Скласти 100 речень", "desc_en": "Build 100 sentences",
"type": "sentences_completed", "value": 100},
{"code": "sentence_200", "category": "sentence", "icon": "📖",
"name_pl": "Pisarz", "name_ru": "Писатель", "name_uk": "Письменник", "name_en": "Writer",
"desc_pl": "Ułóż 200 zdań", "desc_ru": "Составить 200 предложений", "desc_uk": "Скласти 200 речень", "desc_en": "Build 200 sentences",
"type": "sentences_completed", "value": 200},
{"code": "perfect_sentence_5", "category": "sentence", "icon": "",
"name_pl": "Perfekcjonista", "name_ru": "Перфекционист", "name_uk": "Перфекціоніст", "name_en": "Perfectionist",
"desc_pl": "Ułóż 5 zdań bez błędu", "desc_ru": "Составить 5 предложений без единой ошибки", "desc_uk": "Скласти 5 речень без жодної помилки", "desc_en": "Build 5 sentences without mistakes",
"type": "perfect_sentences", "value": 5},
{"code": "sentence_perfect_10", "category": "sentence", "icon": "💎",
"name_pl": "Mistrz zdań", "name_ru": "Мастер предложений", "name_uk": "Майстер речень", "name_en": "Sentence Master",
"desc_pl": "Ułóż 10 zdań bez błędu", "desc_ru": "Составить 10 предложений без ошибок", "desc_uk": "Скласти 10 речень без помилок", "desc_en": "Build 10 sentences without mistakes",
"type": "perfect_sentences", "value": 10},
{"code": "sentence_streak_10", "category": "sentence", "icon": "💪",
"name_pl": "Nieomylny", "name_ru": "Безошибочный", "name_uk": "Безпомилковий", "name_en": "Flawless",
"desc_pl": "Ułóż 10 zdań z rzędu bez błędu", "desc_ru": "Составить 10 предложений подряд без ошибок", "desc_uk": "Скласти 10 речень поспіль без помилок", "desc_en": "Build 10 sentences in a row without mistakes",
"type": "sentence_streak", "value": 10},
{"code": "speed_10", "category": "sentence", "icon": "🚀",
"name_pl": "Turbo", "name_ru": "Турбо", "name_uk": "Турбо", "name_en": "Turbo",
"desc_pl": "Ułóż zdanie w mniej niż 10 sekund", "desc_ru": "Составить предложение быстрее 10 секунд", "desc_uk": "Скласти речення швидше 10 секунд", "desc_en": "Build a sentence in under 10 seconds",
"type": "speed_sentence", "value": 10},
{"code": "sentence_speed_5", "category": "sentence", "icon": "",
"name_pl": "Ekspres", "name_ru": "Экспресс", "name_uk": "Експрес", "name_en": "Express",
"desc_pl": "Ułóż zdanie w mniej niż 5 sekund", "desc_ru": "Составить предложение быстрее 5 секунд", "desc_uk": "Скласти речення швидше 5 секунд", "desc_en": "Build a sentence in under 5 seconds",
"type": "speed_sentence", "value": 5},
# ===== Word Quiz (8) =====
{"code": "quiz_10", "category": "wordquiz", "icon": "🧠",
"name_pl": "Myśliciel", "name_ru": "Мыслитель", "name_uk": "Мислитель", "name_en": "Thinker",
"desc_pl": "Odgadnij 10 słów w Quiz słownym", "desc_ru": "Угадать 10 слов в Квизе слов", "desc_uk": "Відгадати 10 слів у Квізі слів", "desc_en": "Guess 10 words in Word Quiz",
"type": "quiz_guessed", "value": 10},
{"code": "quiz_50", "category": "wordquiz", "icon": "🎓",
"name_pl": "Mistrz quizu", "name_ru": "Мастер квиза", "name_uk": "Майстер квізу", "name_en": "Quiz Master",
"desc_pl": "Odgadnij 50 słów w Quiz słownym", "desc_ru": "Угадать 50 слов в Квизе слов", "desc_uk": "Відгадати 50 слів у Квізі слів", "desc_en": "Guess 50 words in Word Quiz",
"type": "quiz_guessed", "value": 50},
{"code": "quiz_100", "category": "wordquiz", "icon": "🏆",
"name_pl": "Mózg", "name_ru": "Мозг", "name_uk": "Мозок", "name_en": "Brain",
"desc_pl": "Odgadnij 100 słów w Quiz słownym", "desc_ru": "Угадать 100 слов в Квизе слов", "desc_uk": "Відгадати 100 слів у Квізі слів", "desc_en": "Guess 100 words in Word Quiz",
"type": "quiz_guessed", "value": 100},
{"code": "quiz_perfect_10", "category": "wordquiz", "icon": "💎",
"name_pl": "Idealny wynik", "name_ru": "Идеальный результат", "name_uk": "Ідеальний результат", "name_en": "Perfect Score",
"desc_pl": "Odgadnij 10 słów bez pomyłki", "desc_ru": "Угадать 10 слов без ошибок", "desc_uk": "Відгадати 10 слів без помилок", "desc_en": "Guess 10 words without mistakes",
"type": "quiz_perfect_streak", "value": 10},
{"code": "quiz_streak_20", "category": "wordquiz", "icon": "🔥",
"name_pl": "Niepowstrzymany", "name_ru": "Неудержимый", "name_uk": "Невтримний", "name_en": "Unstoppable",
"desc_pl": "Odgadnij 20 słów z rzędu", "desc_ru": "Угадать 20 слов подряд", "desc_uk": "Відгадати 20 слів поспіль", "desc_en": "Guess 20 words in a row",
"type": "quiz_perfect_streak", "value": 20},
{"code": "quiz_speed", "category": "wordquiz", "icon": "",
"name_pl": "Błyskawica", "name_ru": "Молния", "name_uk": "Блискавка", "name_en": "Lightning",
"desc_pl": "Odgadnij słowo w mniej niż 3 sekundy", "desc_ru": "Угадать слово быстрее 3 секунд", "desc_uk": "Відгадати слово швидше 3 секунд", "desc_en": "Guess a word in under 3 seconds",
"type": "quiz_speed", "value": 3},
{"code": "quiz_speed_2", "category": "wordquiz", "icon": "🚀",
"name_pl": "Super szybki", "name_ru": "Супер быстрый", "name_uk": "Супер швидкий", "name_en": "Super Fast",
"desc_pl": "Odgadnij słowo w mniej niż 2 sekundy", "desc_ru": "Угадать слово быстрее 2 секунд", "desc_uk": "Відгадати слово швидше 2 секунд", "desc_en": "Guess a word in under 2 seconds",
"type": "quiz_speed", "value": 2},
{"code": "quiz_no_mistakes", "category": "wordquiz", "icon": "🏆",
"name_pl": "Bezbłędny", "name_ru": "Безошибочный", "name_uk": "Безпомилковий", "name_en": "Flawless",
"desc_pl": "Odgadnij słowo bez żadnej pomyłki", "desc_ru": "Угадать слово без единой ошибки", "desc_uk": "Відгадати слово без жодної помилки", "desc_en": "Guess a word without any mistakes",
"type": "quiz_no_mistakes", "value": 1},
# ===== Bubble Words (8) =====
{"code": "bubble_5", "category": "bubblewords", "icon": "🫧",
"name_pl": "Początkujący", "name_ru": "Начинающий", "name_uk": "Початківець", "name_en": "Beginner",
"desc_pl": "Ukończ 5 poziomów w Bąbelkach słów", "desc_ru": "Пройти 5 уровней в Пузырях слов", "desc_uk": "Пройти 5 рівнів у Бульбашках слів", "desc_en": "Complete 5 levels in Bubble Words",
"type": "bubble_levels", "value": 5},
{"code": "bubble_25", "category": "bubblewords", "icon": "🌟",
"name_pl": "Bąbelkowy mistrz", "name_ru": "Пузырьковый мастер", "name_uk": "Бульбашковий майстер", "name_en": "Bubble Master",
"desc_pl": "Ukończ 25 poziomów w Bąbelkach słów", "desc_ru": "Пройти 25 уровней в Пузырях слов", "desc_uk": "Пройти 25 рівнів у Бульбашках слів", "desc_en": "Complete 25 levels in Bubble Words",
"type": "bubble_levels", "value": 25},
{"code": "bubble_50", "category": "bubblewords", "icon": "💪",
"name_pl": "Bańkowy wojownik", "name_ru": "Пузырьковый воин", "name_uk": "Бульбашковий воїн", "name_en": "Bubble Warrior",
"desc_pl": "Ukończ 50 poziomów w Bąbelkach słów", "desc_ru": "Пройти 50 уровней в Пузырях слов", "desc_uk": "Пройти 50 рівнів у Бульбашках слів", "desc_en": "Complete 50 levels in Bubble Words",
"type": "bubble_levels", "value": 50},
{"code": "bubble_100", "category": "bubblewords", "icon": "👑",
"name_pl": "Król bąbelków", "name_ru": "Король пузырей", "name_uk": "Король бульбашок", "name_en": "Bubble King",
"desc_pl": "Ukończ 100 poziomów w Bąbelkach słów", "desc_ru": "Пройти 100 уровней в Пузырях слов", "desc_uk": "Пройти 100 рівнів у Бульбашках слів", "desc_en": "Complete 100 levels in Bubble Words",
"type": "bubble_levels", "value": 100},
{"code": "bubble_no_hints", "category": "bubblewords", "icon": "💡",
"name_pl": "Samodzielny", "name_ru": "Самостоятельный", "name_uk": "Самостійний", "name_en": "Independent",
"desc_pl": "Ukończ poziom bez podpowiedzi", "desc_ru": "Пройти уровень без подсказок", "desc_uk": "Пройти рівень без підказок", "desc_en": "Complete a level without hints",
"type": "bubble_no_hints", "value": 1},
{"code": "bubble_perfect", "category": "bubblewords", "icon": "",
"name_pl": "Perfekcyjny", "name_ru": "Идеальный", "name_uk": "Ідеальний", "name_en": "Perfect",
"desc_pl": "Ukończ poziom z maksymalną punktacją (100)", "desc_ru": "Пройти уровень с максимальным счётом (100)", "desc_uk": "Пройти рівень з максимальним рахунком (100)", "desc_en": "Complete a level with maximum score (100)",
"type": "bubble_perfect", "value": 1},
{"code": "bubble_speed", "category": "bubblewords", "icon": "🚀",
"name_pl": "Szybki bańki", "name_ru": "Быстрые пузыри", "name_uk": "Швидкі бульбашки", "name_en": "Fast Bubbles",
"desc_pl": "Ukończ poziom w mniej niż 45 sekund", "desc_ru": "Пройти уровень быстрее 45 секунд", "desc_uk": "Пройти рівень швидше 45 секунд", "desc_en": "Complete a level in under 45 seconds",
"type": "bubble_speed", "value": 45},
{"code": "bubble_speed_30", "category": "bubblewords", "icon": "",
"name_pl": "Bąbelkowa błyskawica", "name_ru": "Пузырьковая молния", "name_uk": "Бульбашкова блискавка", "name_en": "Bubble Lightning",
"desc_pl": "Ukończ poziom w mniej niż 30 sekund", "desc_ru": "Пройти уровень быстрее 30 секунд", "desc_uk": "Пройти рівень швидше 30 секунд", "desc_en": "Complete a level in under 30 seconds",
"type": "bubble_speed", "value": 30},
# ===== General (15) =====
{"code": "score_1000", "category": "general", "icon": "🌟",
"name_pl": "Wschodząca gwiazda", "name_ru": "Восходящая звезда", "name_uk": "Висхідна зірка", "name_en": "Rising Star",
"desc_pl": "Zdobądź 1000 punktów", "desc_ru": "Набрать 1000 очков", "desc_uk": "Набрати 1000 балів", "desc_en": "Earn 1000 points",
"type": "total_score", "value": 1000},
{"code": "score_5000", "category": "general", "icon": "💎",
"name_pl": "Diament", "name_ru": "Бриллиант", "name_uk": "Діамант", "name_en": "Diamond",
"desc_pl": "Zdobądź 5000 punktów", "desc_ru": "Набрать 5000 очков", "desc_uk": "Набрати 5000 балів", "desc_en": "Earn 5000 points",
"type": "total_score", "value": 5000},
{"code": "score_10000", "category": "general", "icon": "💯",
"name_pl": "Legenda", "name_ru": "Легенда", "name_uk": "Легенда", "name_en": "Legend",
"desc_pl": "Zdobądź 10000 punktów", "desc_ru": "Набрать 10000 очков", "desc_uk": "Набрати 10000 балів", "desc_en": "Earn 10000 points",
"type": "total_score", "value": 10000},
{"code": "score_25000", "category": "general", "icon": "🦄",
"name_pl": "Jednorożec", "name_ru": "Единорог", "name_uk": "Єдиноріг", "name_en": "Unicorn",
"desc_pl": "Zdobądź 25000 punktów", "desc_ru": "Набрать 25000 очков", "desc_uk": "Набрати 25000 балів", "desc_en": "Earn 25000 points",
"type": "total_score", "value": 25000},
{"code": "rank_1", "category": "general", "icon": "🏆",
"name_pl": "Mistrz", "name_ru": "Чемпион", "name_uk": "Чемпіон", "name_en": "Champion",
"desc_pl": "Zajmij 1 miejsce w rankingu", "desc_ru": "Занять 1 место в рейтинге", "desc_uk": "Зайняти 1 місце в рейтингу", "desc_en": "Reach 1st place on leaderboard",
"type": "rank_reached", "value": 1},
{"code": "rank_top3", "category": "general", "icon": "🥇",
"name_pl": "Medalista", "name_ru": "Призёр", "name_uk": "Призер", "name_en": "Medalist",
"desc_pl": "Zajmij miejsce w top 3", "desc_ru": "Попасть в топ-3 рейтинга", "desc_uk": "Потрапити в топ-3 рейтингу", "desc_en": "Reach top 3 on leaderboard",
"type": "rank_reached", "value": 3},
{"code": "login_3", "category": "general", "icon": "📊",
"name_pl": "Stały gracz", "name_ru": "Постоянный игрок", "name_uk": "Постійний гравець", "name_en": "Regular Player",
"desc_pl": "Zaloguj się 3 dni z rzędu", "desc_ru": "Заходить 3 дня подряд", "desc_uk": "Заходити 3 дні поспіль", "desc_en": "Log in 3 days in a row",
"type": "login_streak", "value": 3},
{"code": "login_7", "category": "general", "icon": "🔥",
"name_pl": "Tygodniowa seria", "name_ru": "Недельная серия", "name_uk": "Тижнева серія", "name_en": "Weekly Streak",
"desc_pl": "Zaloguj się 7 dni z rzędu", "desc_ru": "Заходить 7 дней подряд", "desc_uk": "Заходити 7 днів поспіль", "desc_en": "Log in 7 days in a row",
"type": "login_streak", "value": 7},
{"code": "change_language", "category": "general", "icon": "🌍",
"name_pl": "Poliglota", "name_ru": "Полиглот", "name_uk": "Поліглот", "name_en": "Polyglot",
"desc_pl": "Zmień język interfejsu", "desc_ru": "Сменить язык интерфейса", "desc_uk": "Змінити мову інтерфейсу", "desc_en": "Change interface language",
"type": "language_changed", "value": 1},
# 🔧 ИСПРАВЛЕНО: Заменено достижение "Открой Грамматику" на "Открой меню учителя"
{"code": "open_teacher_menu", "category": "general", "icon": "🎓",
"name_pl": "Uczeń", "name_ru": "Ученик", "name_uk": "Учень", "name_en": "Student",
"desc_pl": "Otwórz menu nauczyciela", "desc_ru": "Открыть меню учителя", "desc_uk": "Відкрити меню вчителя", "desc_en": "Open the teacher menu",
"type": "teacher_menu_opened", "value": 1},
{"code": "open_cheatsheets", "category": "general", "icon": "📝",
"name_pl": "Ciekawski", "name_ru": "Любознательный", "name_uk": "Допитливий", "name_en": "Curious",
"desc_pl": "Otwórz sekcję Ściągi", "desc_ru": "Открыть раздел Шпаргалки", "desc_uk": "Відкрити розділ Шпаргалки", "desc_en": "Open the Cheatsheets section",
"type": "cheatsheets_opened", "value": 1},
{"code": "open_pronunciation", "category": "general", "icon": "🔊",
"name_pl": "Słuchacz", "name_ru": "Слушатель", "name_uk": "Слухач", "name_en": "Listener",
"desc_pl": "Otwórz sekcję Wymowa", "desc_ru": "Открыть раздел Произношение", "desc_uk": "Відкрити розділ Вимова", "desc_en": "Open the Pronunciation section",
"type": "pronunciation_opened", "value": 1},
{"code": "open_ai_assistant", "category": "general", "icon": "🤖",
"name_pl": "Asystent AI", "name_ru": "AI-помощник", "name_uk": "AI-помічник", "name_en": "AI Assistant",
"desc_pl": "Otwórz Asystenta AI", "desc_ru": "Открыть AI-помощника", "desc_uk": "Відкрити AI-помічника", "desc_en": "Open the AI Assistant",
"type": "ai_assistant_opened", "value": 1},
{"code": "open_alphabet", "category": "general", "icon": "🔤",
"name_pl": "Alfabetysta", "name_ru": "Алфавитист", "name_uk": "Алфавітист", "name_en": "Alphabetist",
"desc_pl": "Otwórz sekcję Alfabet", "desc_ru": "Открыть раздел Алфавит", "desc_uk": "Відкрити розділ Алфавіт", "desc_en": "Open the Alphabet section",
"type": "alphabet_opened", "value": 1},
{"code": "open_rules", "category": "general", "icon": "📐",
"name_pl": "Gramatyk", "name_ru": "Грамматик", "name_uk": "Граматик", "name_en": "Grammarian",
"desc_pl": "Otwórz sekcję Zasady", "desc_ru": "Открыть раздел Правила", "desc_uk": "Відкрити розділ Правила", "desc_en": "Open the Rules section",
"type": "rules_opened", "value": 1},
# 🔧 НОВОЕ: Достижение за открытие Прогресса
{"code": "open_progress", "category": "general", "icon": "📊",
"name_pl": "Planista", "name_ru": "Планировщик", "name_uk": "Планувальник", "name_en": "Planner",
"desc_pl": "Otwórz sekcję Postęp", "desc_ru": "Открыть раздел Прогресс", "desc_uk": "Відкрити розділ Прогрес", "desc_en": "Open the Progress section",
"type": "progress_opened", "value": 1},
]
async def init_achievements():
"""Добавляет новые достижения, не трогая существующие"""
pool = await get_pool()
async with pool.acquire() as conn:
for a in ACHIEVEMENTS_DATA:
exists = await conn.fetchval(
"SELECT 1 FROM achievements WHERE code = $1", a["code"]
)
if not exists:
await conn.execute(
"""INSERT INTO achievements (code, category, icon,
name_pl, name_ru, name_uk, name_en,
description_pl, description_ru, description_uk, description_en,
requirement_type, requirement_value)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)""",
a["code"], a["category"], a["icon"],
a["name_pl"], a["name_ru"], a["name_uk"], a["name_en"],
a["desc_pl"], a["desc_ru"], a["desc_uk"], a["desc_en"],
a["type"], a["value"]
)
else:
# 🔧 НОВОЕ: Обновляем существующие достижения (на случай изменения названий)
await conn.execute("""
UPDATE achievements SET
category = $2,
icon = $3,
name_pl = $4,
name_ru = $5,
name_uk = $6,
name_en = $7,
description_pl = $8,
description_ru = $9,
description_uk = $10,
description_en = $11,
requirement_type = $12,
requirement_value = $13
WHERE code = $1
""", a["code"], a["category"], a["icon"],
a["name_pl"], a["name_ru"], a["name_uk"], a["name_en"],
a["desc_pl"], a["desc_ru"], a["desc_uk"], a["desc_en"],
a["type"], a["value"])
@router.get("")
async def get_all_achievements(request: Request):
"""Получить все достижения"""
await require_auth(request)
pool = await get_pool()
async with pool.acquire() as conn:
rows = await conn.fetch("SELECT * FROM achievements ORDER BY id")
return {
"achievements": [dict(row) for row in rows]
}
@router.get("/user")
async def get_user_achievements(request: Request):
"""Получить достижения пользователя"""
telegram_id, _ = await require_auth(request)
pool = await get_pool()
async with pool.acquire() as conn:
rows = await conn.fetch("""
SELECT a.*,
CASE WHEN ua.user_id IS NOT NULL THEN TRUE ELSE FALSE END as unlocked,
ua.unlocked_at
FROM achievements a
LEFT JOIN user_achievements ua ON a.id = ua.achievement_id AND ua.user_id = $1
ORDER BY a.id
""", telegram_id)
achievements = []
for row in rows:
a = dict(row)
if a.get("unlocked_at"):
a["unlocked_at"] = a["unlocked_at"].isoformat()
achievements.append(a)
total = len(achievements)
unlocked = sum(1 for a in achievements if a["unlocked"])
return {
"achievements": achievements,
"total": total,
"unlocked": unlocked,
"progress_percent": round((unlocked / total * 100), 1) if total > 0 else 0
}
async def check_and_award_achievements(telegram_id: int, achievement_type: str, value: int = 1):
"""
Проверяет и выдаёт достижения пользователю.
Возвращает названия на языке пользователя.
"""
pool = await get_pool()
async with pool.acquire() as conn:
user_lang = await conn.fetchval(
"SELECT app_language FROM users WHERE telegram_id = $1", telegram_id
)
if not user_lang or user_lang not in ["pl", "ru", "uk", "en"]:
user_lang = "pl"
matching = await conn.fetch("""
SELECT a.* FROM achievements a
WHERE a.requirement_type = $1
AND a.requirement_value <= $2
AND a.id NOT IN (
SELECT achievement_id FROM user_achievements WHERE user_id = $3
)
""", achievement_type, value, telegram_id)
newly_unlocked = []
name_field = f"name_{user_lang}"
for ach in matching:
await conn.execute(
"INSERT INTO user_achievements (user_id, achievement_id) VALUES ($1, $2) ON CONFLICT DO NOTHING",
telegram_id, ach["id"]
)
newly_unlocked.append({
"id": ach["id"],
"code": ach["code"],
"icon": ach["icon"],
"name": ach[name_field],
})
return newly_unlocked
@router.post("/track-login")
async def track_login(request: Request):
"""Отмечает ежедневный вход пользователя и проверяет достижения серии"""
telegram_id, _ = await require_auth(request)
pool = await get_pool()
async with pool.acquire() as conn:
await conn.execute(
"INSERT INTO daily_logins (user_id, login_date) VALUES ($1, CURRENT_DATE) ON CONFLICT DO NOTHING",
telegram_id
)
streak = await conn.fetchval("""
WITH dates AS (
SELECT login_date,
login_date - ROW_NUMBER() OVER (ORDER BY login_date DESC)::int AS grp
FROM daily_logins
WHERE user_id = $1
ORDER BY login_date DESC
)
SELECT COUNT(*) FROM dates WHERE grp = (SELECT grp FROM dates LIMIT 1)
""", telegram_id)
streak = streak or 0
new_achievements = await check_and_award_achievements(telegram_id, "login_streak", streak)
return {
"login_streak": streak,
"new_achievements": new_achievements
}
# 🔧 НОВОЕ: Трекинг открытия меню учителя
@router.post("/track-teacher-menu")
async def track_teacher_menu_opened(request: Request):
"""Отмечает открытие меню учителя и выдаёт достижение"""
telegram_id, _ = await require_auth(request)
new_achievements = await check_and_award_achievements(telegram_id, "teacher_menu_opened", 1)
return {
"new_achievements": new_achievements
}
# 🔧 НОВОЕ: Трекинг открытия Прогресса
@router.post("/track-progress")
async def track_progress_opened(request: Request):
"""Отмечает открытие раздела Прогресс и выдаёт достижение"""
telegram_id, _ = await require_auth(request)
new_achievements = await check_and_award_achievements(telegram_id, "progress_opened", 1)
return {
"new_achievements": new_achievements
}
@router.post("/track-cheatsheets")
async def track_cheatsheets_opened(request: Request):
"""Отмечает открытие раздела Шпаргалки и выдаёт достижение"""
telegram_id, _ = await require_auth(request)
new_achievements = await check_and_award_achievements(telegram_id, "cheatsheets_opened", 1)
return {
"new_achievements": new_achievements
}
@router.post("/track-pronunciation")
async def track_pronunciation_opened(request: Request):
"""Отмечает открытие раздела Произношение и выдаёт достижение"""
telegram_id, _ = await require_auth(request)
new_achievements = await check_and_award_achievements(telegram_id, "pronunciation_opened", 1)
return {
"new_achievements": new_achievements
}
@router.post("/track-ai-assistant")
async def track_ai_assistant_opened(request: Request):
"""Отмечает открытие AI-помощника и выдаёт достижение"""
telegram_id, _ = await require_auth(request)
new_achievements = await check_and_award_achievements(telegram_id, "ai_assistant_opened", 1)
return {
"new_achievements": new_achievements
}
@router.post("/track-alphabet")
async def track_alphabet_opened(request: Request):
"""Отмечает открытие раздела Алфавит и выдаёт достижение"""
telegram_id, _ = await require_auth(request)
new_achievements = await check_and_award_achievements(telegram_id, "alphabet_opened", 1)
return {
"new_achievements": new_achievements
}
@router.post("/track-rules")
async def track_rules_opened(request: Request):
"""Отмечает открытие раздела Правила и выдаёт достижение"""
telegram_id, _ = await require_auth(request)
new_achievements = await check_and_award_achievements(telegram_id, "rules_opened", 1)
return {
"new_achievements": new_achievements
}