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 }