diff --git a/.github/workflows/docker-hub.yml b/.github/workflows/docker-hub.yml index a0d7d337..7a4a996a 100644 --- a/.github/workflows/docker-hub.yml +++ b/.github/workflows/docker-hub.yml @@ -36,15 +36,15 @@ jobs: TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:latest,fr1ngg/remnawave-bedolaga-telegram-bot:${VERSION}" echo "🏷️ Собираем релизную версию: $VERSION" elif [[ $GITHUB_REF == refs/heads/main ]]; then - VERSION="v2.5.7-$(git rev-parse --short HEAD)" + VERSION="v2.5.8-$(git rev-parse --short HEAD)" TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:latest,fr1ngg/remnawave-bedolaga-telegram-bot:${VERSION}" echo "🚀 Собираем версию из main: $VERSION" elif [[ $GITHUB_REF == refs/heads/dev ]]; then - VERSION="v2.5.7-dev-$(git rev-parse --short HEAD)" + VERSION="v2.5.8-dev-$(git rev-parse --short HEAD)" TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:dev,fr1ngg/remnawave-bedolaga-telegram-bot:${VERSION}" echo "🧪 Собираем dev версию: $VERSION" else - VERSION="v2.5.7-pr-$(git rev-parse --short HEAD)" + VERSION="v2.5.8-pr-$(git rev-parse --short HEAD)" TAGS="fr1ngg/remnawave-bedolaga-telegram-bot:pr-$(git rev-parse --short HEAD)" echo "🔀 Собираем PR версию: $VERSION" fi diff --git a/.github/workflows/docker-registry.yml b/.github/workflows/docker-registry.yml index 916b5d5c..141cbb2e 100644 --- a/.github/workflows/docker-registry.yml +++ b/.github/workflows/docker-registry.yml @@ -49,13 +49,13 @@ jobs: VERSION=${GITHUB_REF#refs/tags/} echo "🏷️ Building release version: $VERSION" elif [[ $GITHUB_REF == refs/heads/main ]]; then - VERSION="v2.5.7-$(git rev-parse --short HEAD)" + VERSION="v2.5.8-$(git rev-parse --short HEAD)" echo "🚀 Building main version: $VERSION" elif [[ $GITHUB_REF == refs/heads/dev ]]; then - VERSION="v2.5.7-dev-$(git rev-parse --short HEAD)" + VERSION="v2.5.8-dev-$(git rev-parse --short HEAD)" echo "🧪 Building dev version: $VERSION" else - VERSION="v2.5.7-pr-$(git rev-parse --short HEAD)" + VERSION="v2.5.8-pr-$(git rev-parse --short HEAD)" echo "🔀 Building PR version: $VERSION" fi echo "version=$VERSION" >> $GITHUB_OUTPUT diff --git a/Dockerfile b/Dockerfile index 2e71dee3..7dc16c78 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN pip install --no-cache-dir --upgrade pip && \ FROM python:3.13-slim -ARG VERSION="v2.5.7" +ARG VERSION="v2.5.8" ARG BUILD_DATE ARG VCS_REF diff --git a/README.md b/README.md index 2606db40..87bb8331 100644 --- a/README.md +++ b/README.md @@ -73,14 +73,6 @@ ## 🚀 Быстрый старт -### 🧙‍♂️ Автоустановка через `install_bot.sh` - -Скрипт-установщик берёт на себя подготовку окружения, настройку конфигурации и дальнейшее обслуживание бота. Он работает поверх Docker Compose и требует заранее установить: - -- **Docker Engine** и **Docker Compose plugin** (2.20+); -- **Git** и **Bash** (по умолчанию есть в большинстве Linux дистрибутивов); -- `openssl` (используется для генерации токенов, но не обязателен — при отсутствии скрипт использует `urandom`). - ```bash # 1. Скачай репозиторий git clone https://github.com/Fr1ngg/remnawave-bedolaga-telegram-bot.git @@ -91,32 +83,18 @@ mkdir -p ./logs ./data ./data/backups ./data/referral_qr chmod -R 755 ./logs ./data sudo chown -R 1000:1000 ./logs ./data -# 3. Запусти мастер установки -chmod +x install_bot.sh -./install_bot.sh + +# 3. make up # Поднять контейнеры + +make help # Показать все команды +make down # Остановить контейнеры +make reload # Перезапустить +make reload-follow # Перезапустить с логами +make test # Запустить тесты + + ``` -На первом запуске мастер: - -1. Спросит путь установки и сохранит его в `./.bot_install_state` — можно оставлять путь по умолчанию (текущая директория). -2. Поможет собрать `.env`: запросит обязательные токены (бот, Remnawave, админы), при необходимости сгенерирует Web API и PostgreSQL пароли, предложит авторизацию Basic Auth или eGames secret. -3. Подготовит структуру каталогов (`logs`, `data`, `backups` и т. д.) и проверит, что Docker готов к запуску. -4. Создаст (или обновит) `docker-compose.yml`, настроит внешнюю сеть `bot_network`, чтобы дальнейшие сервисы (например, Caddy) могли подключаться. -5. Запустит контейнеры бота, PostgreSQL и Redis и выведет их статус. - -После установки повторный запуск `./install_bot.sh` открывает **интерактивное меню управления**: - -- 📊 Мониторинг состояния контейнеров и ресурсов -- ⚙️ Управление сервисами (запуск/остановка/пересборка) -- 📋 Просмотр и поиск по логам -- 🔄 Обновление проекта из Git с автоматическим бэкапом -- 💾 Создание и 📦 восстановление резервных копий (включая базу данных) -- 🧹 Очистка логов, бэкапов и образов -- 🌐 Помощник настройки обратного прокси Caddy (webhook + miniapp, обновление `docker-compose`, перезагрузка) -- ⚙️ Конфигуратор `.env` (редактирование, пересоздание, маскировка секретов) - -> 💡 Скрипт можно запускать сколько угодно раз — он хранит путь установки и понимает, когда конфигурация уже создана. Меню работает и по SSH (достаточно TTY), а для скриптов можно передать путь установки через stdin. - ### 🐳 Ручной Docker запуск Если не хочется пользоваться мастером, можно настроить всё вручную: diff --git a/app/database/crud/promocode.py b/app/database/crud/promocode.py index cbe3c3a0..d765d655 100644 --- a/app/database/crud/promocode.py +++ b/app/database/crud/promocode.py @@ -22,6 +22,41 @@ async def get_promocode_by_code(db: AsyncSession, code: str) -> Optional[PromoCo return result.scalar_one_or_none() +async def get_promocode_by_id(db: AsyncSession, promo_id: int) -> Optional[PromoCode]: + """ + Получает промокод по ID с eager loading всех связанных данных. + Используется для избежания lazy loading в async контексте. + """ + result = await db.execute( + select(PromoCode) + .options( + selectinload(PromoCode.uses), + selectinload(PromoCode.promo_group) + ) + .where(PromoCode.id == promo_id) + ) + return result.scalar_one_or_none() + + +async def check_promocode_validity(db: AsyncSession, code: str) -> dict: + """ + Проверяет существование и валидность промокода без активации. + Возвращает словарь с информацией о промокоде. + """ + promocode = await get_promocode_by_code(db, code) + + if not promocode: + return {"valid": False, "error": "not_found", "promocode": None} + + if not promocode.is_valid: + if promocode.current_uses >= promocode.max_uses: + return {"valid": False, "error": "used", "promocode": None} + else: + return {"valid": False, "error": "expired", "promocode": None} + + return {"valid": True, "error": None, "promocode": promocode} + + async def create_promocode( db: AsyncSession, code: str, @@ -61,9 +96,9 @@ async def use_promocode( promocode_id: int, user_id: int ) -> bool: - + try: - promocode = await db.get(PromoCode, promocode_id) + promocode = await get_promocode_by_id(db, promocode_id) if not promocode: return False diff --git a/app/handlers/admin/promocodes.py b/app/handlers/admin/promocodes.py index e6d0e7aa..73aef95c 100644 --- a/app/handlers/admin/promocodes.py +++ b/app/handlers/admin/promocodes.py @@ -15,7 +15,7 @@ from app.localization.texts import get_texts from app.database.crud.promocode import ( get_promocodes_list, get_promocodes_count, create_promocode, get_promocode_statistics, get_promocode_by_code, update_promocode, - delete_promocode + delete_promocode, get_promocode_by_id ) from app.database.crud.promo_group import get_promo_group_by_id, get_promo_groups_with_counts from app.utils.decorators import admin_required, error_handler @@ -138,8 +138,8 @@ async def show_promocode_management( db: AsyncSession ): promo_id = int(callback.data.split('_')[-1]) - - promo = await db.get(PromoCode, promo_id) + + promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ Промокод не найден", show_alert=True) return @@ -219,8 +219,8 @@ async def show_promocode_edit_menu( except (ValueError, IndexError): await callback.answer("❌ Ошибка получения ID промокода", show_alert=True) return - - promo = await db.get(PromoCode, promo_id) + + promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ Промокод не найден", show_alert=True) return @@ -648,8 +648,8 @@ async def handle_edit_value( data = await state.get_data() promo_id = data.get('editing_promo_id') edit_action = data.get('edit_action') - - promo = await db.get(PromoCode, promo_id) + + promo = await get_promocode_by_id(db, promo_id) if not promo: await message.answer("❌ Промокод не найден") await state.clear() @@ -734,8 +734,8 @@ async def handle_edit_uses( ): data = await state.get_data() promo_id = data.get('editing_promo_id') - - promo = await db.get(PromoCode, promo_id) + + promo = await get_promocode_by_id(db, promo_id) if not promo: await message.answer("❌ Промокод не найден") await state.clear() @@ -873,8 +873,8 @@ async def handle_edit_expiry( ): data = await state.get_data() promo_id = data.get('editing_promo_id') - - promo = await db.get(PromoCode, promo_id) + + promo = await get_promocode_by_id(db, promo_id) if not promo: await message.answer("❌ Промокод не найден") await state.clear() @@ -920,8 +920,8 @@ async def toggle_promocode_status( db: AsyncSession ): promo_id = int(callback.data.split('_')[-1]) - - promo = await db.get(PromoCode, promo_id) + + promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ Промокод не найден", show_alert=True) return @@ -947,8 +947,8 @@ async def confirm_delete_promocode( except (ValueError, IndexError): await callback.answer("❌ Ошибка получения ID промокода", show_alert=True) return - - promo = await db.get(PromoCode, promo_id) + + promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ Промокод не найден", show_alert=True) return @@ -995,8 +995,8 @@ async def delete_promocode_confirmed( except (ValueError, IndexError): await callback.answer("❌ Ошибка получения ID промокода", show_alert=True) return - - promo = await db.get(PromoCode, promo_id) + + promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ Промокод не найден", show_alert=True) return @@ -1019,8 +1019,8 @@ async def show_promocode_stats( db: AsyncSession ): promo_id = int(callback.data.split('_')[-1]) - - promo = await db.get(PromoCode, promo_id) + + promo = await get_promocode_by_id(db, promo_id) if not promo: await callback.answer("❌ Промокод не найден", show_alert=True) return diff --git a/app/handlers/promocode.py b/app/handlers/promocode.py index 949f3b9e..38ed58ad 100644 --- a/app/handlers/promocode.py +++ b/app/handlers/promocode.py @@ -1,9 +1,8 @@ import logging -from aiogram import Dispatcher, types, F +from aiogram import Dispatcher, types, F, Bot from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession -from app.config import settings from app.states import PromoCodeStates from app.database.models import User from app.keyboards.inline import get_back_keyboard @@ -32,6 +31,45 @@ async def show_promocode_menu( await callback.answer() +async def activate_promocode_for_registration( + db: AsyncSession, + user_id: int, + code: str, + bot: Bot = None +) -> dict: + """ + Активирует промокод для пользователя во время регистрации. + Возвращает результат активации без отправки сообщений. + """ + promocode_service = PromoCodeService() + result = await promocode_service.activate_promocode(db, user_id, code) + + if result["success"]: + logger.info(f"✅ Пользователь {user_id} активировал промокод {code} при регистрации") + + # Отправляем уведомление админу, если бот доступен + if bot: + try: + from app.database.crud.user import get_user_by_id + user = await get_user_by_id(db, user_id) + if user: + notification_service = AdminNotificationService(bot) + await notification_service.send_promocode_activation_notification( + db, + user, + result.get("promocode", {"code": code}), + result["description"], + ) + except Exception as notify_error: + logger.error( + "Ошибка отправки админ уведомления об активации промокода %s: %s", + code, + notify_error, + ) + + return result + + @error_handler async def process_promocode( message: types.Message, @@ -40,9 +78,9 @@ async def process_promocode( db: AsyncSession ): texts = get_texts(db_user.language) - + code = message.text.strip() - + if not code: await message.answer( texts.t( @@ -52,31 +90,14 @@ async def process_promocode( reply_markup=get_back_keyboard(db_user.language) ) return - - promocode_service = PromoCodeService() - result = await promocode_service.activate_promocode(db, db_user.id, code) - + + result = await activate_promocode_for_registration(db, db_user.id, code, message.bot) + if result["success"]: await message.answer( texts.PROMOCODE_SUCCESS.format(description=result["description"]), reply_markup=get_back_keyboard(db_user.language) ) - logger.info(f"✅ Пользователь {db_user.telegram_id} активировал промокод {code}") - - try: - notification_service = AdminNotificationService(message.bot) - await notification_service.send_promocode_activation_notification( - db, - db_user, - result.get("promocode", {"code": code}), - result["description"], - ) - except Exception as notify_error: - logger.error( - "Ошибка отправки админ уведомления об активации промокода %s: %s", - code, - notify_error, - ) else: error_messages = { "not_found": texts.PROMOCODE_INVALID, @@ -85,13 +106,13 @@ async def process_promocode( "already_used_by_user": texts.PROMOCODE_USED, "server_error": texts.ERROR } - + error_text = error_messages.get(result["error"], texts.PROMOCODE_INVALID) await message.answer( error_text, reply_markup=get_back_keyboard(db_user.language) ) - + await state.clear() diff --git a/app/handlers/start.py b/app/handlers/start.py index abeeb10a..16a171e2 100644 --- a/app/handlers/start.py +++ b/app/handlers/start.py @@ -100,15 +100,15 @@ async def handle_potential_referral_code( db: AsyncSession ): current_state = await state.get_state() - logger.info(f"🔍 REFERRAL CHECK: Проверка сообщения '{message.text}' в состоянии {current_state}") - + logger.info(f"🔍 REFERRAL/PROMO CHECK: Проверка сообщения '{message.text}' в состоянии {current_state}") + if current_state not in [ RegistrationStates.waiting_for_rules_accept.state, RegistrationStates.waiting_for_referral_code.state, - None + None ]: return False - + user = await get_user_by_telegram_id(db, message.from_user.id) if user and user.status == UserStatus.ACTIVE.value: return False @@ -125,37 +125,73 @@ async def handle_potential_referral_code( if len(potential_code) < 4 or len(potential_code) > 20: return False + # Сначала проверяем реферальный код referrer = await get_user_by_referral_code(db, potential_code) - if not referrer: - await message.answer(texts.t( - "REFERRAL_CODE_INVALID_HELP", - "❌ Неверный реферальный код.\n\n" - "💡 Если у вас есть реферальный код, убедитесь что он введен правильно.\n" - "⏭️ Для продолжения регистрации без реферального кода используйте команду /start", - )) + if referrer: + data['referral_code'] = potential_code + data['referrer_id'] = referrer.id + await state.set_data(data) + + await message.answer(texts.t("REFERRAL_CODE_ACCEPTED", "✅ Реферальный код принят!")) + logger.info(f"✅ Реферальный код {potential_code} применен для пользователя {message.from_user.id}") + + if current_state != RegistrationStates.waiting_for_referral_code.state: + language = data.get('language', DEFAULT_LANGUAGE) + texts = get_texts(language) + + rules_text = await get_rules(language) + await message.answer( + rules_text, + reply_markup=get_rules_keyboard(language) + ) + await state.set_state(RegistrationStates.waiting_for_rules_accept) + logger.info("📋 Правила отправлены после ввода реферального кода") + else: + await complete_registration(message, state, db) + return True - data['referral_code'] = potential_code - data['referrer_id'] = referrer.id - await state.set_data(data) + # Если реферальный код не найден, проверяем промокод + from app.database.crud.promocode import check_promocode_validity + + promocode_check = await check_promocode_validity(db, potential_code) + + if promocode_check["valid"]: + # Промокод валиден - сохраняем его в state для активации после создания пользователя + data['promocode'] = potential_code + await state.set_data(data) - await message.answer(texts.t("REFERRAL_CODE_ACCEPTED", "✅ Реферальный код принят!")) - logger.info(f"✅ Реферальный код {potential_code} применен для пользователя {message.from_user.id}") - - if current_state != RegistrationStates.waiting_for_referral_code.state: - language = data.get('language', DEFAULT_LANGUAGE) - texts = get_texts(language) - - rules_text = await get_rules(language) await message.answer( - rules_text, - reply_markup=get_rules_keyboard(language) + texts.t( + "PROMOCODE_ACCEPTED_WILL_ACTIVATE", + "✅ Промокод принят! Он будет активирован после завершения регистрации." + ) ) - await state.set_state(RegistrationStates.waiting_for_rules_accept) - logger.info("📋 Правила отправлены после ввода реферального кода") - else: - await complete_registration(message, state, db) - + logger.info(f"✅ Промокод {potential_code} сохранен для активации для пользователя {message.from_user.id}") + + if current_state != RegistrationStates.waiting_for_referral_code.state: + language = data.get('language', DEFAULT_LANGUAGE) + texts = get_texts(language) + + rules_text = await get_rules(language) + await message.answer( + rules_text, + reply_markup=get_rules_keyboard(language) + ) + await state.set_state(RegistrationStates.waiting_for_rules_accept) + logger.info("📋 Правила отправлены после принятия промокода") + else: + await complete_registration(message, state, db) + + return True + + # Ни реферальный код, ни промокод не найдены + await message.answer(texts.t( + "REFERRAL_OR_PROMO_CODE_INVALID_HELP", + "❌ Неверный реферальный код или промокод.\n\n" + "💡 Если у вас есть реферальный код или промокод, убедитесь что он введен правильно.\n" + "⏭️ Для продолжения регистрации без кода используйте команду /start", + )) return True @@ -692,31 +728,57 @@ async def process_rules_accept( async def process_referral_code_input( - message: types.Message, - state: FSMContext, + message: types.Message, + state: FSMContext, db: AsyncSession ): - - logger.info(f"🎫 REFERRAL: Обработка реферального кода: {message.text}") - + + logger.info(f"🎫 REFERRAL/PROMO: Обработка кода: {message.text}") + data = await state.get_data() or {} language = data.get('language', DEFAULT_LANGUAGE) texts = get_texts(language) - referral_code = message.text.strip() + code = message.text.strip() - referrer = await get_user_by_referral_code(db, referral_code) + # Сначала проверяем, является ли это реферальным кодом + referrer = await get_user_by_referral_code(db, code) if referrer: data['referrer_id'] = referrer.id await state.set_data(data) await message.answer(texts.t("REFERRAL_CODE_ACCEPTED", "✅ Реферальный код принят!")) - logger.info(f"✅ Реферальный код применен") - else: - await message.answer(texts.t("REFERRAL_CODE_INVALID", "❌ Неверный реферальный код")) - logger.info(f"❌ Неверный реферальный код") + logger.info(f"✅ Реферальный код применен: {code}") + await complete_registration(message, state, db) return - - await complete_registration(message, state, db) + + # Если реферальный код не найден, проверяем промокод + from app.database.crud.promocode import check_promocode_validity + + promocode_check = await check_promocode_validity(db, code) + + if promocode_check["valid"]: + # Промокод валиден - сохраняем его в state для активации после создания пользователя + data['promocode'] = code + await state.set_data(data) + await message.answer( + texts.t( + "PROMOCODE_ACCEPTED_WILL_ACTIVATE", + "✅ Промокод принят! Он будет активирован после завершения регистрации." + ) + ) + logger.info(f"✅ Промокод сохранен для активации: {code}") + await complete_registration(message, state, db) + return + + # Ни реферальный код, ни промокод не найдены + await message.answer( + texts.t( + "REFERRAL_OR_PROMO_CODE_INVALID", + "❌ Неверный реферальный код или промокод" + ) + ) + logger.info(f"❌ Неверный код (ни реферальный, ни промокод): {code}") + return async def process_referral_code_skip( @@ -1157,6 +1219,29 @@ async def complete_registration( except Exception as e: logger.error(f"Ошибка при обработке реферальной регистрации: {e}") + # Активируем промокод если был сохранен в state + promocode_to_activate = data.get('promocode') + if promocode_to_activate: + try: + from app.handlers.promocode import activate_promocode_for_registration + + promocode_result = await activate_promocode_for_registration( + db, user.id, promocode_to_activate, message.bot + ) + + if promocode_result["success"]: + await message.answer( + texts.t( + "PROMOCODE_ACTIVATED_AT_REGISTRATION", + "✅ Промокод активирован!\n\n{description}" + ).format(description=promocode_result["description"]) + ) + logger.info(f"✅ Промокод {promocode_to_activate} активирован для пользователя {user.id}") + else: + logger.warning(f"⚠️ Не удалось активировать промокод {promocode_to_activate}: {promocode_result.get('error')}") + except Exception as e: + logger.error(f"❌ Ошибка при активации промокода {promocode_to_activate}: {e}") + campaign_message = await _apply_campaign_bonus_if_needed(db, user, data, texts) try: diff --git a/app/localization/locales/en.json b/app/localization/locales/en.json index 78232619..0a92e40a 100644 --- a/app/localization/locales/en.json +++ b/app/localization/locales/en.json @@ -1133,7 +1133,11 @@ "REFERRAL_CODE_APPLIED": "🎁 Referral code applied! You will receive a bonus after the first purchase.", "REFERRAL_CODE_INVALID": "❌ Invalid referral code", "REFERRAL_CODE_INVALID_HELP": "❌ Invalid referral code.\n\n💡 If you have a referral code, please double-check the spelling.\n⏭️ To continue without a referral code, use the /start command.", - "REFERRAL_CODE_QUESTION": "\n🤝 Do you have a friend's referral code?\n\nIf you have a promo code or referral link, enter it now to receive a bonus!\n\nSend the code or tap \"Skip\":\n", + "REFERRAL_CODE_QUESTION": "\n🤝 Do you have a referral code or promo code?\n\nIf you have a promo code or referral link from a friend, enter it now to receive a bonus!\n\nSend the code or tap \"Skip\":\n", + "REFERRAL_OR_PROMO_CODE_INVALID": "❌ Invalid referral code or promo code", + "REFERRAL_OR_PROMO_CODE_INVALID_HELP": "❌ Invalid referral code or promo code.\n\n💡 If you have a referral code or promo code, please double-check the spelling.\n⏭️ To continue registration without a code, use the /start command.", + "PROMOCODE_ACTIVATED_AT_REGISTRATION": "✅ Promo code activated!\n\n{description}", + "PROMOCODE_ACCEPTED_WILL_ACTIVATE": "✅ Promo code accepted! It will be activated after registration is complete.", "REFERRAL_CODE_SKIP": "⏭️ Skip", "REFERRAL_CODE_TITLE": "🆔 Your code: {code}", "REFERRAL_EARNINGS_BY_TYPE_HEADER": "📈 Earnings by type:", diff --git a/app/localization/locales/ru.json b/app/localization/locales/ru.json index ad2d8e54..da49f786 100644 --- a/app/localization/locales/ru.json +++ b/app/localization/locales/ru.json @@ -1153,7 +1153,11 @@ "REFERRAL_CODE_APPLIED": "🎁 Реферальный код применен! Вы получите бонус после первой покупки.", "REFERRAL_CODE_INVALID": "❌ Неверный реферальный код", "REFERRAL_CODE_INVALID_HELP": "❌ Неверный реферальный код.\n\n💡 Если у вас есть реферальный код, убедитесь что он введен правильно.\n⏭️ Для продолжения регистрации без реферального кода используйте команду /start", - "REFERRAL_CODE_QUESTION": "\n🤝 У вас есть реферальный код от друга?\n\nЕсли у вас есть промокод или реферальная ссылка от друга, введите её сейчас, чтобы получить бонус!\n\nВведите код или нажмите \"Пропустить\":\n", + "REFERRAL_CODE_QUESTION": "\n🤝 У вас есть реферальный код или промокод?\n\nЕсли у вас есть промокод или реферальная ссылка от друга, введите её сейчас, чтобы получить бонус!\n\nВведите код или нажмите \"Пропустить\":\n", + "REFERRAL_OR_PROMO_CODE_INVALID": "❌ Неверный реферальный код или промокод", + "REFERRAL_OR_PROMO_CODE_INVALID_HELP": "❌ Неверный реферальный код или промокод.\n\n💡 Если у вас есть реферальный код или промокод, убедитесь что он введен правильно.\n⏭️ Для продолжения регистрации без кода используйте команду /start", + "PROMOCODE_ACTIVATED_AT_REGISTRATION": "✅ Промокод активирован!\n\n{description}", + "PROMOCODE_ACCEPTED_WILL_ACTIVATE": "✅ Промокод принят! Он будет активирован после завершения регистрации.", "REFERRAL_CODE_SKIP": "⏭️ Пропустить", "REFERRAL_CODE_TITLE": "🆔 Ваш код: {code}", "REFERRAL_EARNINGS_BY_TYPE_HEADER": "📈 Доходы по типам:", diff --git a/app/webapi/routes/promocodes.py b/app/webapi/routes/promocodes.py index a39c70b0..11e4e312 100644 --- a/app/webapi/routes/promocodes.py +++ b/app/webapi/routes/promocodes.py @@ -10,6 +10,7 @@ from app.database.crud.promocode import ( create_promocode, delete_promocode, get_promocode_by_code, + get_promocode_by_id, get_promocode_statistics, get_promocodes_count, get_promocodes_list, @@ -163,7 +164,7 @@ async def get_promocode( _: Any = Depends(require_api_token), db: AsyncSession = Depends(get_db_session), ) -> PromoCodeDetailResponse: - promocode = await db.get(PromoCode, promocode_id) + promocode = await get_promocode_by_id(db, promocode_id) if not promocode: raise HTTPException(status.HTTP_404_NOT_FOUND, "Promo code not found") @@ -236,7 +237,7 @@ async def update_promocode_endpoint( _: Any = Depends(require_api_token), db: AsyncSession = Depends(get_db_session), ) -> PromoCodeResponse: - promocode = await db.get(PromoCode, promocode_id) + promocode = await get_promocode_by_id(db, promocode_id) if not promocode: raise HTTPException(status.HTTP_404_NOT_FOUND, "Promo code not found") @@ -290,7 +291,7 @@ async def delete_promocode_endpoint( _: Any = Depends(require_api_token), db: AsyncSession = Depends(get_db_session), ) -> Response: - promocode = await db.get(PromoCode, promocode_id) + promocode = await get_promocode_by_id(db, promocode_id) if not promocode: raise HTTPException(status.HTTP_404_NOT_FOUND, "Promo code not found")