Migrating to Postgres from sqlite3

This commit is contained in:
machka
2025-05-20 18:41:32 +00:00
parent ac2f83c061
commit a56d804b58
38 changed files with 5017 additions and 4389 deletions
+76 -53
View File
@@ -3,6 +3,8 @@ import re
from aiogram import Router, F, types, Bot
from aiogram.fsm.context import FSMContext
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from aiogram.utils.markdown import hcode
from config.settings import Settings
from bot.states.user_states import UserPromoStates
@@ -10,26 +12,26 @@ from bot.services.promo_code_service import PromoCodeService
from bot.services.subscription_service import SubscriptionService
from bot.keyboards.inline.user_keyboards import get_back_to_main_menu_markup
from bot.middlewares.i18n import JsonI18n
from aiogram.utils.markdown import hcode
from .start import send_main_menu
router = Router(name="user_promo_router")
SUSPICIOUS_SQL_KEYWORDS_REGEX = re.compile(
r"\b(DROP\s*TABLE|DELETE\s*FROM|ALTER\s*TABLE|TRUNCATE\s*TABLE|UNION\s*SELECT|;\s*SELECT|;\s*INSERT|;\s*UPDATE|;\s*DELETE|xp_cmdshell|sysdatabases|sysobjects|INFORMATION_SCHEMA)\b",
r"\b(DROP\s*TABLE|DELETE\s*FROM|ALTER\s*TABLE|TRUNCATE\s*TABLE|UNION\s*SELECT|"
r";\s*SELECT|;\s*INSERT|;\s*UPDATE|;\s*DELETE|xp_cmdshell|sysdatabases|sysobjects|INFORMATION_SCHEMA)\b",
re.IGNORECASE)
SUSPICIOUS_CHARS_REGEX = re.compile(r"(--|#\s|;|\*\/|\/\*)")
MAX_PROMO_CODE_INPUT_LENGTH = 100
async def prompt_promo_code_input(callback: types.CallbackQuery,
state: FSMContext, i18n_data: dict,
settings: Settings):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
settings: Settings, session: AsyncSession):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await callback.answer("Language error.", show_alert=True)
await callback.answer("Language service error.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
@@ -44,8 +46,10 @@ async def prompt_promo_code_input(callback: types.CallbackQuery,
await callback.message.edit_text(
text=_(key="promo_code_prompt"),
reply_markup=get_back_to_main_menu_markup(current_lang, i18n))
except Exception as e:
logging.warning(f"Failed to edit message for promo prompt: {e}")
except Exception as e_edit:
logging.warning(
f"Failed to edit message for promo prompt: {e_edit}. Sending new one."
)
await callback.message.answer(
text=_(key="promo_code_prompt"),
reply_markup=get_back_to_main_menu_markup(current_lang, i18n))
@@ -53,73 +57,96 @@ async def prompt_promo_code_input(callback: types.CallbackQuery,
await callback.answer()
await state.set_state(UserPromoStates.waiting_for_promo_code)
logging.info(
f"User {callback.from_user.id} entered state UserPromoStates.waiting_for_promo_code. FSM state: {await state.get_state()}"
)
f"User {callback.from_user.id} entered state UserPromoStates.waiting_for_promo_code. "
f"FSM state: {await state.get_state()}")
@router.message(UserPromoStates.waiting_for_promo_code, F.text)
async def process_promo_code_input(message: types.Message, state: FSMContext,
settings: Settings, i18n_data: dict,
promo_code_service: PromoCodeService,
bot: Bot):
bot: Bot, session: AsyncSession):
logging.info(
f"Processing promo code input from user {message.from_user.id} in state {await state.get_state()}: '{message.text}'"
)
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n or not promo_code_service:
logging.error("Deps missing in process_promo_code_input")
await message.reply("Service error. Please try again.")
logging.error(
"Dependencies (i18n or PromoCodeService) missing in process_promo_code_input"
)
await message.reply("Service error. Please try again later.")
await state.clear()
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
code_input = message.text.strip()
code_input = message.text.strip() if message.text else ""
user = message.from_user
is_suspicious = False
if SUSPICIOUS_SQL_KEYWORDS_REGEX.search(
code_input) or SUSPICIOUS_CHARS_REGEX.search(code_input) or len(
code_input) > 100:
if not code_input:
is_suspicious = True
logging.warning(f"Empty promo code input by user {user.id}.")
elif len(
code_input
) > MAX_PROMO_CODE_INPUT_LENGTH or SUSPICIOUS_SQL_KEYWORDS_REGEX.search(
code_input) or SUSPICIOUS_CHARS_REGEX.search(code_input):
is_suspicious = True
logging.warning(
f"Suspicious input for promo by user {user.id} (len: {len(code_input)}): '{code_input}'"
f"Suspicious input for promo code by user {user.id} (len: {len(code_input)}): '{code_input}'"
)
response_to_user_text = ""
if is_suspicious:
admin_notify_key = "admin_suspicious_promo_attempt_notification_no_username" if not user.username else "admin_suspicious_promo_attempt_notification"
admin_lang = settings.DEFAULT_LANGUAGE
_admin = lambda k, **kw: i18n.gettext(admin_lang, k, **kw)
admin_notification_text = _admin(admin_notify_key,
user_id=user.id,
user_username=user.username or "N/A",
user_first_name=user.first_name
or "N/A",
promo_code_input=hcode(code_input))
try:
await bot.send_message(settings.ADMIN_ID,
admin_notification_text,
parse_mode="HTML")
except Exception as e_admin_notify:
logging.error(
f"Failed to send suspicious promo notification to admin: {e_admin_notify}"
)
if settings.ADMIN_IDS:
admin_notify_key = "admin_suspicious_promo_attempt_notification" if user.username else "admin_suspicious_promo_attempt_notification_no_username"
admin_lang = settings.DEFAULT_LANGUAGE
_admin = lambda k, **kw: i18n.gettext(admin_lang, k, **kw)
admin_notification_text = _admin(
admin_notify_key,
user_id=user.id,
user_username=user.username or "N/A",
user_first_name=user.first_name or "N/A",
promo_code_input=hcode(code_input))
for admin_id in settings.ADMIN_IDS:
try:
await bot.send_message(admin_id,
admin_notification_text,
parse_mode="HTML")
except Exception as e_admin_notify:
logging.error(
f"Failed to send suspicious promo notification to admin {admin_id}: {e_admin_notify}"
)
response_to_user_text = _("promo_code_not_found",
code=code_input.upper())
code=hcode(code_input.upper()))
else:
success, response_text_from_service = await promo_code_service.apply_promo_code(
user.id, code_input, current_lang)
session, user.id, code_input, current_lang)
response_to_user_text = response_text_from_service
if success:
await session.commit()
logging.info(
f"Promo code '{code_input}' successfully applied for user {user.id}."
)
else:
await session.rollback()
logging.info(
f"Promo code '{code_input}' application failed for user {user.id}. Reason: {response_text_from_service}"
)
await message.answer(response_to_user_text,
reply_markup=get_back_to_main_menu_markup(
current_lang, i18n))
current_lang, i18n),
parse_mode="HTML")
await state.clear()
logging.info(
f"Promo code '{code_input}' processing finished for user {message.from_user.id}. State cleared."
f"Promo code input '{code_input}' processing finished for user {message.from_user.id}. State cleared."
)
@@ -127,9 +154,9 @@ async def process_promo_code_input(message: types.Message, state: FSMContext,
UserPromoStates.waiting_for_promo_code)
async def cancel_promo_input_via_button(
callback: types.CallbackQuery, state: FSMContext, settings: Settings,
i18n_data: dict, subscription_service: SubscriptionService):
current_lang = i18n_data.get("current_language",
getattr(settings, 'DEFAULT_LANGUAGE', 'en'))
i18n_data: dict, subscription_service: SubscriptionService,
session: AsyncSession):
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
logging.error("i18n missing in cancel_promo_input_via_button")
@@ -140,21 +167,17 @@ async def cancel_promo_input_via_button(
f"User {callback.from_user.id} cancelled promo code input via button from state {await state.get_state()}. Clearing state."
)
await state.clear()
logging.info(
f"State after clear for user {callback.from_user.id}: {await state.get_state()}"
)
if callback.message:
show_trial_button_on_back = False
if settings.TRIAL_ENABLED and not await subscription_service.has_had_any_subscription(
callback.from_user.id):
show_trial_button_on_back = True
await send_main_menu(callback,
settings,
i18n_data,
show_trial_button_flag=show_trial_button_on_back,
subscription_service,
session,
is_edit=True)
else:
await callback.answer("Promo code input cancelled.", show_alert=False)
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
await callback.answer(_("promo_input_cancelled_short"),
show_alert=False)