Optimize user activity updates and fix traffic top-up
This commit is contained in:
+78
-56
@@ -30,7 +30,8 @@ async def add_user(
|
|||||||
language_code: str = None,
|
language_code: str = None,
|
||||||
is_bot: bool = False,
|
is_bot: bool = False,
|
||||||
source_code: str = None,
|
source_code: str = None,
|
||||||
):
|
commit: bool = True,
|
||||||
|
) -> bool:
|
||||||
try:
|
try:
|
||||||
stmt = (
|
stmt = (
|
||||||
insert(User)
|
insert(User)
|
||||||
@@ -43,11 +44,17 @@ async def add_user(
|
|||||||
is_bot=is_bot,
|
is_bot=is_bot,
|
||||||
source_code=source_code,
|
source_code=source_code,
|
||||||
)
|
)
|
||||||
.on_conflict_do_nothing(index_elements=[User.tg_id])
|
.on_conflict_do_nothing(index_elements=["tg_id"])
|
||||||
|
.returning(User.tg_id)
|
||||||
)
|
)
|
||||||
await session.execute(stmt)
|
res = await session.execute(stmt)
|
||||||
await session.commit()
|
inserted_tg_id = res.scalar_one_or_none()
|
||||||
|
if inserted_tg_id is None:
|
||||||
|
return False
|
||||||
|
if commit:
|
||||||
|
await session.commit()
|
||||||
logger.info(f"[DB] Новый пользователь добавлен: {tg_id} (source: {source_code})")
|
logger.info(f"[DB] Новый пользователь добавлен: {tg_id} (source: {source_code})")
|
||||||
|
return True
|
||||||
except SQLAlchemyError as e:
|
except SQLAlchemyError as e:
|
||||||
logger.error(f"[DB] Ошибка при добавлении пользователя {tg_id}: {e}")
|
logger.error(f"[DB] Ошибка при добавлении пользователя {tg_id}: {e}")
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
@@ -121,58 +128,64 @@ async def upsert_user(
|
|||||||
is_bot: bool = False,
|
is_bot: bool = False,
|
||||||
only_if_exists: bool = False,
|
only_if_exists: bool = False,
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
|
"""Создаёт пользователя или обновляет поля профиля."""
|
||||||
try:
|
try:
|
||||||
|
now = datetime.utcnow()
|
||||||
|
returning_cols = list(User.__table__.c)
|
||||||
|
|
||||||
if only_if_exists:
|
if only_if_exists:
|
||||||
result = await session.execute(select(User).where(User.tg_id == tg_id))
|
username_value = username if username else User.username
|
||||||
user = result.scalar_one_or_none()
|
first_name_value = first_name if first_name else User.first_name
|
||||||
if not user:
|
last_name_value = last_name if last_name else User.last_name
|
||||||
return None
|
language_code_value = language_code if language_code else User.language_code
|
||||||
await session.execute(
|
|
||||||
|
res = await session.execute(
|
||||||
update(User)
|
update(User)
|
||||||
.where(User.tg_id == tg_id)
|
.where(User.tg_id == tg_id)
|
||||||
.values(
|
.values(
|
||||||
username=username or user.username,
|
username=username_value,
|
||||||
first_name=first_name or user.first_name,
|
first_name=first_name_value,
|
||||||
last_name=last_name or user.last_name,
|
last_name=last_name_value,
|
||||||
language_code=language_code or user.language_code,
|
language_code=language_code_value,
|
||||||
is_bot=is_bot,
|
is_bot=is_bot,
|
||||||
updated_at=datetime.utcnow(),
|
updated_at=now,
|
||||||
)
|
)
|
||||||
|
.returning(*returning_cols)
|
||||||
)
|
)
|
||||||
|
row = res.mappings().one_or_none()
|
||||||
|
if row is None:
|
||||||
|
return None
|
||||||
await session.commit()
|
await session.commit()
|
||||||
result = await session.execute(select(User).where(User.tg_id == tg_id))
|
return dict(row)
|
||||||
return dict(result.scalar_one().__dict__)
|
|
||||||
else:
|
res = await session.execute(
|
||||||
res = await session.execute(
|
insert(User)
|
||||||
insert(User)
|
.values(
|
||||||
.values(
|
tg_id=tg_id,
|
||||||
tg_id=tg_id,
|
username=username,
|
||||||
username=username,
|
first_name=first_name,
|
||||||
first_name=first_name,
|
last_name=last_name,
|
||||||
last_name=last_name,
|
language_code=language_code,
|
||||||
language_code=language_code,
|
is_bot=is_bot,
|
||||||
is_bot=is_bot,
|
created_at=now,
|
||||||
created_at=datetime.utcnow(),
|
updated_at=now,
|
||||||
updated_at=datetime.utcnow(),
|
|
||||||
)
|
|
||||||
.on_conflict_do_update(
|
|
||||||
index_elements=[User.tg_id],
|
|
||||||
set_={
|
|
||||||
"username": username,
|
|
||||||
"first_name": first_name,
|
|
||||||
"last_name": last_name,
|
|
||||||
"language_code": language_code,
|
|
||||||
"is_bot": is_bot,
|
|
||||||
"updated_at": datetime.utcnow(),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.returning(User)
|
|
||||||
)
|
)
|
||||||
obj = res.scalar_one()
|
.on_conflict_do_update(
|
||||||
await session.commit()
|
index_elements=[User.tg_id],
|
||||||
d = obj.__dict__.copy()
|
set_={
|
||||||
d.pop("_sa_instance_state", None)
|
"username": username,
|
||||||
return d
|
"first_name": first_name,
|
||||||
|
"last_name": last_name,
|
||||||
|
"language_code": language_code,
|
||||||
|
"is_bot": is_bot,
|
||||||
|
"updated_at": now,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.returning(*returning_cols)
|
||||||
|
)
|
||||||
|
row = res.mappings().one()
|
||||||
|
await session.commit()
|
||||||
|
return dict(row)
|
||||||
except SQLAlchemyError as e:
|
except SQLAlchemyError as e:
|
||||||
logger.error(f"[DB] Ошибка при UPSERT пользователя {tg_id}: {e}")
|
logger.error(f"[DB] Ошибка при UPSERT пользователя {tg_id}: {e}")
|
||||||
await session.rollback()
|
await session.rollback()
|
||||||
@@ -210,12 +223,10 @@ async def mark_trial_extended(tg_id: int, session: AsyncSession):
|
|||||||
|
|
||||||
|
|
||||||
async def get_user_snapshot(session: AsyncSession, tg_id: int) -> tuple[int, int] | None:
|
async def get_user_snapshot(session: AsyncSession, tg_id: int) -> tuple[int, int] | None:
|
||||||
|
keys_count_sq = select(func.count(Key.client_id)).where(Key.tg_id == tg_id).scalar_subquery()
|
||||||
|
|
||||||
res = await session.execute(
|
res = await session.execute(
|
||||||
select(func.coalesce(User.trial, 0), func.count(Key.client_id))
|
select(func.coalesce(User.trial, 0), keys_count_sq).where(User.tg_id == tg_id)
|
||||||
.select_from(User)
|
|
||||||
.join(Key, Key.tg_id == User.tg_id, isouter=True)
|
|
||||||
.where(User.tg_id == tg_id)
|
|
||||||
.group_by(User.tg_id, User.trial)
|
|
||||||
)
|
)
|
||||||
row = res.first()
|
row = res.first()
|
||||||
if row is None:
|
if row is None:
|
||||||
@@ -223,17 +234,28 @@ async def get_user_snapshot(session: AsyncSession, tg_id: int) -> tuple[int, int
|
|||||||
return int(row[0]), int(row[1])
|
return int(row[0]), int(row[1])
|
||||||
|
|
||||||
|
|
||||||
async def upsert_source_if_empty(session: AsyncSession, tg_id: int, source_code: str) -> None:
|
async def upsert_source_if_empty(
|
||||||
|
session: AsyncSession,
|
||||||
|
tg_id: int,
|
||||||
|
source_code: str,
|
||||||
|
commit: bool = True,
|
||||||
|
) -> bool:
|
||||||
if not source_code:
|
if not source_code:
|
||||||
return
|
return False
|
||||||
stmt = (
|
stmt = (
|
||||||
insert(User)
|
insert(User)
|
||||||
.values(tg_id=tg_id, source_code=source_code)
|
.values(tg_id=tg_id, source_code=source_code)
|
||||||
.on_conflict_do_update(
|
.on_conflict_do_update(
|
||||||
index_elements=[User.tg_id],
|
index_elements=["tg_id"],
|
||||||
set_={"source_code": insert(User).excluded.source_code},
|
set_={"source_code": insert(User).excluded.source_code},
|
||||||
where=(User.source_code.is_(None)),
|
where=(User.source_code.is_(None)),
|
||||||
)
|
)
|
||||||
|
.returning(User.tg_id)
|
||||||
)
|
)
|
||||||
await session.execute(stmt)
|
res = await session.execute(stmt)
|
||||||
await session.commit()
|
changed_tg_id = res.scalar_one_or_none()
|
||||||
|
if changed_tg_id is None:
|
||||||
|
return False
|
||||||
|
if commit:
|
||||||
|
await session.commit()
|
||||||
|
return True
|
||||||
|
|||||||
+12
-15
@@ -18,7 +18,6 @@ from database import (
|
|||||||
add_payment,
|
add_payment,
|
||||||
add_user,
|
add_user,
|
||||||
check_coupon_usage,
|
check_coupon_usage,
|
||||||
check_user_exists,
|
|
||||||
create_coupon_usage,
|
create_coupon_usage,
|
||||||
get_coupon_by_code,
|
get_coupon_by_code,
|
||||||
get_keys,
|
get_keys,
|
||||||
@@ -108,20 +107,18 @@ async def activate_coupon(
|
|||||||
await state.clear()
|
await state.clear()
|
||||||
return
|
return
|
||||||
|
|
||||||
user_exists = await check_user_exists(session, user_id)
|
if isinstance(user, dict):
|
||||||
if not user_exists:
|
await add_user(session=session, **user)
|
||||||
if isinstance(user, dict):
|
else:
|
||||||
await add_user(session=session, **user)
|
await add_user(
|
||||||
else:
|
session=session,
|
||||||
await add_user(
|
tg_id=user.id,
|
||||||
session=session,
|
username=getattr(user, "username", None),
|
||||||
tg_id=user.id,
|
first_name=getattr(user, "first_name", None),
|
||||||
username=getattr(user, "username", None),
|
last_name=getattr(user, "last_name", None),
|
||||||
first_name=getattr(user, "first_name", None),
|
language_code=getattr(user, "language_code", None),
|
||||||
last_name=getattr(user, "last_name", None),
|
is_bot=getattr(user, "is_bot", False),
|
||||||
language_code=getattr(user, "language_code", None),
|
)
|
||||||
is_bot=getattr(user, "is_bot", False),
|
|
||||||
)
|
|
||||||
|
|
||||||
if coupon.amount > 0:
|
if coupon.amount > 0:
|
||||||
try:
|
try:
|
||||||
|
|||||||
+11
-13
@@ -20,7 +20,6 @@ from config import (
|
|||||||
from core.bootstrap import MODES_CONFIG, NOTIFICATIONS_CONFIG
|
from core.bootstrap import MODES_CONFIG, NOTIFICATIONS_CONFIG
|
||||||
from database import (
|
from database import (
|
||||||
add_user,
|
add_user,
|
||||||
check_user_exists,
|
|
||||||
get_tariffs_for_cluster,
|
get_tariffs_for_cluster,
|
||||||
get_trial,
|
get_trial,
|
||||||
)
|
)
|
||||||
@@ -393,18 +392,17 @@ async def create_key(
|
|||||||
selected_traffic_gb: int | None = None,
|
selected_traffic_gb: int | None = None,
|
||||||
selected_price_rub: int | None = None,
|
selected_price_rub: int | None = None,
|
||||||
):
|
):
|
||||||
if not await check_user_exists(session, tg_id):
|
from_user = message_or_query.from_user if isinstance(message_or_query, CallbackQuery | Message) else None
|
||||||
from_user = message_or_query.from_user if isinstance(message_or_query, CallbackQuery | Message) else None
|
if from_user:
|
||||||
if from_user:
|
await add_user(
|
||||||
await add_user(
|
tg_id=from_user.id,
|
||||||
tg_id=from_user.id,
|
username=from_user.username,
|
||||||
username=from_user.username,
|
first_name=from_user.first_name,
|
||||||
first_name=from_user.first_name,
|
last_name=from_user.last_name,
|
||||||
last_name=from_user.last_name,
|
language_code=from_user.language_code,
|
||||||
language_code=from_user.language_code,
|
is_bot=from_user.is_bot,
|
||||||
is_bot=from_user.is_bot,
|
session=session,
|
||||||
session=session,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
|
use_country_selection = bool(MODES_CONFIG.get("COUNTRY_SELECTION_ENABLED", USE_COUNTRY_SELECTION))
|
||||||
|
|
||||||
|
|||||||
+14
-16
@@ -25,7 +25,6 @@ from core.bootstrap import BUTTONS_CONFIG, MODES_CONFIG
|
|||||||
from database import (
|
from database import (
|
||||||
add_referral,
|
add_referral,
|
||||||
add_user,
|
add_user,
|
||||||
check_user_exists,
|
|
||||||
get_referral_by_referred_id,
|
get_referral_by_referred_id,
|
||||||
get_referral_stats,
|
get_referral_stats,
|
||||||
)
|
)
|
||||||
@@ -271,23 +270,22 @@ async def handle_referral_link(
|
|||||||
await message.answer("❌ Вы уже использовали реферальную ссылку.")
|
await message.answer("❌ Вы уже использовали реферальную ссылку.")
|
||||||
return
|
return
|
||||||
|
|
||||||
user_exists = await check_user_exists(session, user_id)
|
if isinstance(user, dict):
|
||||||
if user_exists:
|
inserted = await add_user(session=session, **user)
|
||||||
|
else:
|
||||||
|
inserted = await add_user(
|
||||||
|
session=session,
|
||||||
|
tg_id=user.id,
|
||||||
|
username=getattr(user, "username", None),
|
||||||
|
first_name=getattr(user, "first_name", None),
|
||||||
|
last_name=getattr(user, "last_name", None),
|
||||||
|
language_code=getattr(user, "language_code", None),
|
||||||
|
is_bot=getattr(user, "is_bot", False),
|
||||||
|
)
|
||||||
|
|
||||||
|
if not inserted:
|
||||||
await message.answer("❌ Вы уже зарегистрированы и не можете стать рефералом.")
|
await message.answer("❌ Вы уже зарегистрированы и не можете стать рефералом.")
|
||||||
return
|
return
|
||||||
if not user_exists:
|
|
||||||
if isinstance(user, dict):
|
|
||||||
await add_user(session=session, **user)
|
|
||||||
else:
|
|
||||||
await add_user(
|
|
||||||
session=session,
|
|
||||||
tg_id=user.id,
|
|
||||||
username=getattr(user, "username", None),
|
|
||||||
first_name=getattr(user, "first_name", None),
|
|
||||||
last_name=getattr(user, "last_name", None),
|
|
||||||
language_code=getattr(user, "language_code", None),
|
|
||||||
is_bot=getattr(user, "is_bot", False),
|
|
||||||
)
|
|
||||||
|
|
||||||
await add_referral(session, user_id, referrer_tg_id)
|
await add_referral(session, user_id, referrer_tg_id)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from aiogram import F, Router
|
|||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.types import CallbackQuery, InlineKeyboardButton
|
from aiogram.types import CallbackQuery, InlineKeyboardButton
|
||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from config import USE_NEW_PAYMENT_FLOW
|
from config import USE_NEW_PAYMENT_FLOW
|
||||||
@@ -17,6 +18,7 @@ from database import (
|
|||||||
save_key_config_with_mode,
|
save_key_config_with_mode,
|
||||||
update_balance,
|
update_balance,
|
||||||
)
|
)
|
||||||
|
from database.models import User
|
||||||
from handlers.buttons import BACK, CONFIRM_ADDON_BUTTON_TEXT, PAYMENT
|
from handlers.buttons import BACK, CONFIRM_ADDON_BUTTON_TEXT, PAYMENT
|
||||||
from handlers.keys.key_view import render_key_info
|
from handlers.keys.key_view import render_key_info
|
||||||
from handlers.payments.currency_rates import format_for_user
|
from handlers.payments.currency_rates import format_for_user
|
||||||
@@ -613,252 +615,274 @@ async def handle_addons_confirm(callback: CallbackQuery, state: FSMContext, sess
|
|||||||
tg_id = callback.from_user.id
|
tg_id = callback.from_user.id
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
|
|
||||||
email = data.get("addon_key_email")
|
if data.get("addon_confirm_in_progress"):
|
||||||
tariff_id = data.get("addon_tariff_id")
|
await callback.answer("⏳ Уже обрабатываю...", show_alert=False)
|
||||||
selected_devices = data.get("addon_selected_device_limit")
|
|
||||||
selected_traffic_gb = data.get("addon_selected_traffic_gb")
|
|
||||||
current_devices = data.get("addon_current_device_limit")
|
|
||||||
current_traffic_gb = data.get("addon_current_traffic_gb")
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"[ADDONS] PACK_MODE handle_addons_confirm: "
|
|
||||||
f"tg_id={tg_id} email={email} tariff_id={tariff_id} "
|
|
||||||
f"selected_devices={selected_devices} selected_traffic_gb={selected_traffic_gb} "
|
|
||||||
f"current_devices={current_devices} current_traffic_gb={current_traffic_gb}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not email or not tariff_id:
|
|
||||||
await callback.message.answer("❌ Данные для изменения подписки не найдены.")
|
|
||||||
await state.clear()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
record = await get_key_details(session, email)
|
await state.update_data(addon_confirm_in_progress=True)
|
||||||
if not record:
|
|
||||||
logger.warning(f"[ADDONS] PACK_MODE: подписка {email} не найдена в handle_addons_confirm")
|
|
||||||
await callback.message.answer("❌ Подписка не найдена.")
|
|
||||||
await state.clear()
|
|
||||||
return
|
|
||||||
|
|
||||||
tariff = await get_tariff_by_id(session, int(tariff_id))
|
|
||||||
if not tariff:
|
|
||||||
logger.error(f"[ADDONS] PACK_MODE: тариф {tariff_id} не найден в handle_addons_confirm")
|
|
||||||
await callback.message.answer("❌ Тариф не найден.")
|
|
||||||
await state.clear()
|
|
||||||
return
|
|
||||||
|
|
||||||
cfg = data.get("addon_tariff_config") or {}
|
|
||||||
device_options = cfg.get("device_options") or []
|
|
||||||
traffic_options = cfg.get("traffic_options_gb") or []
|
|
||||||
|
|
||||||
pack_devices, pack_traffic, pack_mode = get_pack_flags()
|
|
||||||
|
|
||||||
has_device_option = pack_devices and bool(device_options)
|
|
||||||
has_traffic_option = pack_traffic and bool(traffic_options)
|
|
||||||
|
|
||||||
if has_device_option and current_devices is not None and int(current_devices) == 0:
|
|
||||||
has_device_option = False
|
|
||||||
selected_devices = None
|
|
||||||
|
|
||||||
if has_traffic_option and current_traffic_gb is not None and int(current_traffic_gb) == 0:
|
|
||||||
has_traffic_option = False
|
|
||||||
selected_traffic_gb = None
|
|
||||||
|
|
||||||
current_devices_for_price = int(current_devices) if current_devices is not None else None
|
|
||||||
current_traffic_for_price = int(current_traffic_gb) if current_traffic_gb is not None else None
|
|
||||||
|
|
||||||
base_price_for_current = calculate_config_price(
|
|
||||||
tariff=tariff,
|
|
||||||
selected_device_limit=current_devices_for_price,
|
|
||||||
selected_traffic_gb=current_traffic_for_price,
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
base_price_for_current_int = int(base_price_for_current) if base_price_for_current is not None else 0
|
email = data.get("addon_key_email")
|
||||||
except (TypeError, ValueError):
|
tariff_id = data.get("addon_tariff_id")
|
||||||
base_price_for_current_int = 0
|
selected_devices = data.get("addon_selected_device_limit")
|
||||||
|
selected_traffic_gb = data.get("addon_selected_traffic_gb")
|
||||||
recalc_enabled = bool(
|
current_devices = data.get("addon_current_device_limit")
|
||||||
MODES_CONFIG.get(
|
current_traffic_gb = data.get("addon_current_traffic_gb")
|
||||||
"KEY_ADDONS_RECALC_PRICE",
|
|
||||||
TARIFFS_CONFIG.get("KEY_ADDONS_RECALC_PRICE", False),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
diff_full = calc_pack_full_price_rub(
|
|
||||||
tariff=tariff,
|
|
||||||
has_device_option=has_device_option,
|
|
||||||
has_traffic_option=has_traffic_option,
|
|
||||||
selected_devices=int(selected_devices) if selected_devices is not None else None,
|
|
||||||
selected_traffic_gb=int(selected_traffic_gb) if selected_traffic_gb is not None else None,
|
|
||||||
)
|
|
||||||
|
|
||||||
if recalc_enabled:
|
|
||||||
remaining_seconds, total_seconds = calc_remaining_ratio_seconds(record.get("expiry_time"), tariff)
|
|
||||||
extra_price = int((diff_full * remaining_seconds + total_seconds - 1) // total_seconds)
|
|
||||||
total_price_after_purchase = base_price_for_current_int + extra_price
|
|
||||||
else:
|
|
||||||
extra_price = int(diff_full)
|
|
||||||
total_price_after_purchase = base_price_for_current_int + diff_full
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
"[ADDONS] PACK_MODE confirm prices: "
|
|
||||||
f"base_price_for_current={base_price_for_current_int} diff_full={diff_full} "
|
|
||||||
f"extra_price={extra_price} recalc_enabled={recalc_enabled} total_price_after_purchase={total_price_after_purchase} "
|
|
||||||
f"has_device_option={has_device_option} has_traffic_option={has_traffic_option} "
|
|
||||||
f"pack_mode={pack_mode!r}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if extra_price <= 0:
|
|
||||||
logger.info(f"[ADDONS] PACK_MODE: extra_price <= 0, доплата не требуется, email={email}")
|
|
||||||
await state.clear()
|
|
||||||
await render_key_info(callback.message, session, email, "img/pic_view.jpg")
|
|
||||||
await callback.answer(ADDONS_NO_EXTRA_PAYMENT_TEXT, show_alert=True)
|
|
||||||
return
|
|
||||||
|
|
||||||
balance = await get_balance(session, tg_id)
|
|
||||||
logger.debug(f"[ADDONS] PACK_MODE balance check: tg_id={tg_id} balance={balance} extra_price={extra_price}")
|
|
||||||
|
|
||||||
if balance < extra_price:
|
|
||||||
required_amount = ceil(extra_price - balance)
|
|
||||||
language_code = getattr(callback.from_user, "language_code", None)
|
|
||||||
required_amount_text = await format_for_user(session, tg_id, float(required_amount), language_code)
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"[ADDONS] PACK_MODE: недостаточно средств "
|
"[ADDONS] PACK_MODE handle_addons_confirm: "
|
||||||
f"balance={balance} extra_price={extra_price} required_amount={required_amount} "
|
f"tg_id={tg_id} email={email} tariff_id={tariff_id} "
|
||||||
f"tg_id={tg_id} USE_NEW_PAYMENT_FLOW={USE_NEW_PAYMENT_FLOW}"
|
f"selected_devices={selected_devices} selected_traffic_gb={selected_traffic_gb} "
|
||||||
|
f"current_devices={current_devices} current_traffic_gb={current_traffic_gb}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if USE_NEW_PAYMENT_FLOW:
|
if not email or not tariff_id:
|
||||||
handled = await try_fast_payment_flow(
|
await callback.message.answer("❌ Данные для изменения подписки не найдены.")
|
||||||
callback,
|
await state.clear()
|
||||||
session,
|
return
|
||||||
state,
|
|
||||||
tg_id=tg_id,
|
|
||||||
temp_key="waiting_for_addons_payment",
|
|
||||||
temp_payload={
|
|
||||||
"email": email,
|
|
||||||
"tariff_id": int(tariff_id),
|
|
||||||
"selected_device_limit": selected_devices,
|
|
||||||
"selected_traffic_gb": selected_traffic_gb,
|
|
||||||
"current_device_limit": current_devices,
|
|
||||||
"current_traffic_gb": current_traffic_gb,
|
|
||||||
"required_amount": required_amount,
|
|
||||||
},
|
|
||||||
required_amount=required_amount,
|
|
||||||
)
|
|
||||||
logger.debug(f"[ADDONS] PACK_MODE try_fast_payment_flow handled={handled} tg_id={tg_id} email={email}")
|
|
||||||
if handled:
|
|
||||||
return
|
|
||||||
|
|
||||||
builder = InlineKeyboardBuilder()
|
record = await get_key_details(session, email)
|
||||||
builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay"))
|
if not record:
|
||||||
await edit_or_send_message(
|
logger.warning(f"[ADDONS] PACK_MODE: подписка {email} не найдена в handle_addons_confirm")
|
||||||
target_message=callback.message,
|
await callback.message.answer("❌ Подписка не найдена.")
|
||||||
text=INSUFFICIENT_FUNDS_RENEWAL_MSG.format(required_amount=required_amount_text),
|
await state.clear()
|
||||||
reply_markup=builder.as_markup(),
|
return
|
||||||
|
|
||||||
|
tariff = await get_tariff_by_id(session, int(tariff_id))
|
||||||
|
if not tariff:
|
||||||
|
logger.error(f"[ADDONS] PACK_MODE: тариф {tariff_id} не найден в handle_addons_confirm")
|
||||||
|
await callback.message.answer("❌ Тариф не найден.")
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
cfg = data.get("addon_tariff_config") or {}
|
||||||
|
device_options = cfg.get("device_options") or []
|
||||||
|
traffic_options = cfg.get("traffic_options_gb") or []
|
||||||
|
|
||||||
|
pack_devices, pack_traffic, pack_mode = get_pack_flags()
|
||||||
|
|
||||||
|
has_device_option = pack_devices and bool(device_options)
|
||||||
|
has_traffic_option = pack_traffic and bool(traffic_options)
|
||||||
|
|
||||||
|
if has_device_option and current_devices is not None and int(current_devices) == 0:
|
||||||
|
has_device_option = False
|
||||||
|
selected_devices = None
|
||||||
|
|
||||||
|
if has_traffic_option and current_traffic_gb is not None and int(current_traffic_gb) == 0:
|
||||||
|
has_traffic_option = False
|
||||||
|
selected_traffic_gb = None
|
||||||
|
|
||||||
|
current_devices_for_price = int(current_devices) if current_devices is not None else None
|
||||||
|
current_traffic_for_price = int(current_traffic_gb) if current_traffic_gb is not None else None
|
||||||
|
|
||||||
|
base_price_for_current = calculate_config_price(
|
||||||
|
tariff=tariff,
|
||||||
|
selected_device_limit=current_devices_for_price,
|
||||||
|
selected_traffic_gb=current_traffic_for_price,
|
||||||
)
|
)
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
expiry_time = record["expiry_time"]
|
|
||||||
client_id = record["client_id"]
|
|
||||||
server_id = record["server_id"]
|
|
||||||
|
|
||||||
device_limit_effective_current, traffic_limit_bytes_effective_current = await get_effective_limits_for_key(
|
|
||||||
session=session,
|
|
||||||
tariff_id=int(tariff_id),
|
|
||||||
selected_device_limit=int(current_devices) if current_devices is not None else None,
|
|
||||||
selected_traffic_gb=int(current_traffic_gb) if current_traffic_gb is not None else None,
|
|
||||||
)
|
|
||||||
traffic_limit_gb_effective_current = (
|
|
||||||
int(traffic_limit_bytes_effective_current / GB) if traffic_limit_bytes_effective_current else 0
|
|
||||||
)
|
|
||||||
|
|
||||||
new_device_limit_effective = device_limit_effective_current
|
|
||||||
new_traffic_limit_gb_effective = traffic_limit_gb_effective_current
|
|
||||||
|
|
||||||
if has_device_option and selected_devices is not None:
|
|
||||||
pack_devices_val = int(selected_devices)
|
|
||||||
if pack_devices_val <= 0 or (new_device_limit_effective is not None and new_device_limit_effective <= 0):
|
|
||||||
new_device_limit_effective = 0
|
|
||||||
else:
|
|
||||||
if new_device_limit_effective is None:
|
|
||||||
new_device_limit_effective = pack_devices_val
|
|
||||||
else:
|
|
||||||
new_device_limit_effective = new_device_limit_effective + pack_devices_val
|
|
||||||
|
|
||||||
if has_traffic_option and selected_traffic_gb is not None:
|
|
||||||
pack_traffic_val = int(selected_traffic_gb)
|
|
||||||
if pack_traffic_val <= 0 or new_traffic_limit_gb_effective <= 0:
|
|
||||||
new_traffic_limit_gb_effective = 0
|
|
||||||
else:
|
|
||||||
new_traffic_limit_gb_effective = new_traffic_limit_gb_effective + pack_traffic_val
|
|
||||||
|
|
||||||
current_subgroup = None
|
|
||||||
try:
|
try:
|
||||||
current_tariff_id = record.get("tariff_id")
|
base_price_for_current_int = int(base_price_for_current) if base_price_for_current is not None else 0
|
||||||
if current_tariff_id:
|
except (TypeError, ValueError):
|
||||||
current_tariff = await get_tariff_by_id(session, int(current_tariff_id))
|
base_price_for_current_int = 0
|
||||||
if current_tariff:
|
|
||||||
current_subgroup = current_tariff.get("subgroup_title")
|
|
||||||
except Exception as error:
|
|
||||||
logger.warning(f"[ADDONS] PACK_MODE: не удалось определить текущую подгруппу: {error}")
|
|
||||||
|
|
||||||
target_subgroup = tariff.get("subgroup_title")
|
recalc_enabled = bool(
|
||||||
old_subgroup = current_subgroup
|
MODES_CONFIG.get(
|
||||||
|
"KEY_ADDONS_RECALC_PRICE",
|
||||||
|
TARIFFS_CONFIG.get("KEY_ADDONS_RECALC_PRICE", False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
total_gb = new_traffic_limit_gb_effective
|
diff_full = calc_pack_full_price_rub(
|
||||||
hwid_device_limit_to_set = new_device_limit_effective
|
tariff=tariff,
|
||||||
|
has_device_option=has_device_option,
|
||||||
|
has_traffic_option=has_traffic_option,
|
||||||
|
selected_devices=int(selected_devices) if selected_devices is not None else None,
|
||||||
|
selected_traffic_gb=int(selected_traffic_gb) if selected_traffic_gb is not None else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
if recalc_enabled:
|
||||||
|
remaining_seconds, total_seconds = calc_remaining_ratio_seconds(record.get("expiry_time"), tariff)
|
||||||
|
extra_price = int((diff_full * remaining_seconds + total_seconds - 1) // total_seconds)
|
||||||
|
total_price_after_purchase = base_price_for_current_int + extra_price
|
||||||
|
else:
|
||||||
|
extra_price = int(diff_full)
|
||||||
|
total_price_after_purchase = base_price_for_current_int + diff_full
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"[ADDONS] PACK_MODE renew_key_in_cluster params: "
|
"[ADDONS] PACK_MODE confirm prices: "
|
||||||
f"server_id={server_id} email={email} client_id={client_id} total_gb={total_gb} "
|
f"base_price_for_current={base_price_for_current_int} diff_full={diff_full} "
|
||||||
f"hwid_device_limit_to_set={hwid_device_limit_to_set} target_subgroup={target_subgroup} "
|
f"extra_price={extra_price} recalc_enabled={recalc_enabled} total_price_after_purchase={total_price_after_purchase} "
|
||||||
f"old_subgroup={old_subgroup}"
|
f"has_device_option={has_device_option} has_traffic_option={has_traffic_option} "
|
||||||
|
f"pack_mode={pack_mode!r}"
|
||||||
)
|
)
|
||||||
|
|
||||||
await renew_key_in_cluster(
|
if extra_price <= 0:
|
||||||
cluster_id=server_id,
|
logger.info(f"[ADDONS] PACK_MODE: extra_price <= 0, доплата не требуется, email={email}")
|
||||||
email=email,
|
await state.clear()
|
||||||
client_id=client_id,
|
await render_key_info(callback.message, session, email, "img/pic_view.jpg")
|
||||||
new_expiry_time=expiry_time,
|
await callback.answer(ADDONS_NO_EXTRA_PAYMENT_TEXT, show_alert=True)
|
||||||
total_gb=total_gb,
|
return
|
||||||
session=session,
|
|
||||||
hwid_device_limit=hwid_device_limit_to_set,
|
|
||||||
reset_traffic=False,
|
|
||||||
target_subgroup=target_subgroup,
|
|
||||||
old_subgroup=old_subgroup,
|
|
||||||
plan=int(tariff_id),
|
|
||||||
)
|
|
||||||
|
|
||||||
await update_balance(session, tg_id, -extra_price)
|
balance = await get_balance(session, tg_id)
|
||||||
|
logger.debug(f"[ADDONS] PACK_MODE balance check: tg_id={tg_id} balance={balance} extra_price={extra_price}")
|
||||||
|
|
||||||
await save_key_config_with_mode(
|
if balance < extra_price:
|
||||||
session=session,
|
required_amount = ceil(extra_price - balance)
|
||||||
email=email,
|
language_code = getattr(callback.from_user, "language_code", None)
|
||||||
selected_devices=new_device_limit_effective,
|
required_amount_text = await format_for_user(session, tg_id, float(required_amount), language_code)
|
||||||
selected_traffic_gb=new_traffic_limit_gb_effective,
|
|
||||||
total_price=int(total_price_after_purchase),
|
|
||||||
has_device_choice=has_device_option,
|
|
||||||
has_traffic_choice=has_traffic_option,
|
|
||||||
config_mode="pack",
|
|
||||||
)
|
|
||||||
|
|
||||||
await session.commit()
|
logger.info(
|
||||||
|
"[ADDONS] PACK_MODE: недостаточно средств "
|
||||||
|
f"balance={balance} extra_price={extra_price} required_amount={required_amount} "
|
||||||
|
f"tg_id={tg_id} USE_NEW_PAYMENT_FLOW={USE_NEW_PAYMENT_FLOW}"
|
||||||
|
)
|
||||||
|
|
||||||
logger.info(
|
if USE_NEW_PAYMENT_FLOW:
|
||||||
"[ADDONS] PACK_MODE успешная покупка пакета: "
|
handled = await try_fast_payment_flow(
|
||||||
f"tg_id={tg_id} email={email} extra_price={extra_price} "
|
callback,
|
||||||
f"new_device_limit_effective={new_device_limit_effective} "
|
session,
|
||||||
f"new_traffic_limit_gb_effective={new_traffic_limit_gb_effective} "
|
state,
|
||||||
f"recalc_enabled={recalc_enabled} pack_mode={pack_mode!r}"
|
tg_id=tg_id,
|
||||||
)
|
temp_key="waiting_for_addons_payment",
|
||||||
|
temp_payload={
|
||||||
|
"email": email,
|
||||||
|
"tariff_id": int(tariff_id),
|
||||||
|
"selected_device_limit": selected_devices,
|
||||||
|
"selected_traffic_gb": selected_traffic_gb,
|
||||||
|
"current_device_limit": current_devices,
|
||||||
|
"current_traffic_gb": current_traffic_gb,
|
||||||
|
"required_amount": required_amount,
|
||||||
|
},
|
||||||
|
required_amount=required_amount,
|
||||||
|
)
|
||||||
|
logger.debug(f"[ADDONS] PACK_MODE try_fast_payment_flow handled={handled} tg_id={tg_id} email={email}")
|
||||||
|
if handled:
|
||||||
|
return
|
||||||
|
|
||||||
await state.clear()
|
builder = InlineKeyboardBuilder()
|
||||||
await render_key_info(callback.message, session, email, "img/pic_view.jpg")
|
builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay"))
|
||||||
await callback.answer(ADDONS_PACK_SUCCESS_TEXT, show_alert=True)
|
await edit_or_send_message(
|
||||||
|
target_message=callback.message,
|
||||||
|
text=INSUFFICIENT_FUNDS_RENEWAL_MSG.format(required_amount=required_amount_text),
|
||||||
|
reply_markup=builder.as_markup(),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
except Exception as error:
|
try:
|
||||||
logger.error(f"[ADDONS] PACK_MODE ошибка при покупке пакета для {email}: {error}")
|
expiry_time = record["expiry_time"]
|
||||||
await callback.message.answer("❌ Ошибка при обновлении подписки. Попробуйте позже.")
|
client_id = record["client_id"]
|
||||||
await state.clear()
|
server_id = record["server_id"]
|
||||||
|
|
||||||
|
device_limit_effective_current, traffic_limit_bytes_effective_current = await get_effective_limits_for_key(
|
||||||
|
session=session,
|
||||||
|
tariff_id=int(tariff_id),
|
||||||
|
selected_device_limit=int(current_devices) if current_devices is not None else None,
|
||||||
|
selected_traffic_gb=int(current_traffic_gb) if current_traffic_gb is not None else None,
|
||||||
|
)
|
||||||
|
traffic_limit_gb_effective_current = (
|
||||||
|
int(traffic_limit_bytes_effective_current / GB) if traffic_limit_bytes_effective_current else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
new_device_limit_effective = device_limit_effective_current
|
||||||
|
new_traffic_limit_gb_effective = traffic_limit_gb_effective_current
|
||||||
|
|
||||||
|
if has_device_option and selected_devices is not None:
|
||||||
|
pack_devices_val = int(selected_devices)
|
||||||
|
if pack_devices_val <= 0 or (new_device_limit_effective is not None and new_device_limit_effective <= 0):
|
||||||
|
new_device_limit_effective = 0
|
||||||
|
else:
|
||||||
|
if new_device_limit_effective is None:
|
||||||
|
new_device_limit_effective = pack_devices_val
|
||||||
|
else:
|
||||||
|
new_device_limit_effective = new_device_limit_effective + pack_devices_val
|
||||||
|
|
||||||
|
if has_traffic_option and selected_traffic_gb is not None:
|
||||||
|
pack_traffic_val = int(selected_traffic_gb)
|
||||||
|
if pack_traffic_val <= 0 or new_traffic_limit_gb_effective <= 0:
|
||||||
|
new_traffic_limit_gb_effective = 0
|
||||||
|
else:
|
||||||
|
new_traffic_limit_gb_effective = new_traffic_limit_gb_effective + pack_traffic_val
|
||||||
|
|
||||||
|
current_subgroup = None
|
||||||
|
try:
|
||||||
|
current_tariff_id = record.get("tariff_id")
|
||||||
|
if current_tariff_id:
|
||||||
|
current_tariff = await get_tariff_by_id(session, int(current_tariff_id))
|
||||||
|
if current_tariff:
|
||||||
|
current_subgroup = current_tariff.get("subgroup_title")
|
||||||
|
except Exception as error:
|
||||||
|
logger.warning(f"[ADDONS] PACK_MODE: не удалось определить текущую подгруппу: {error}")
|
||||||
|
|
||||||
|
target_subgroup = tariff.get("subgroup_title")
|
||||||
|
old_subgroup = current_subgroup
|
||||||
|
|
||||||
|
total_gb = new_traffic_limit_gb_effective
|
||||||
|
hwid_device_limit_to_set = new_device_limit_effective
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
"[ADDONS] PACK_MODE renew_key_in_cluster params: "
|
||||||
|
f"server_id={server_id} email={email} client_id={client_id} total_gb={total_gb} "
|
||||||
|
f"hwid_device_limit_to_set={hwid_device_limit_to_set} target_subgroup={target_subgroup} "
|
||||||
|
f"old_subgroup={old_subgroup}"
|
||||||
|
)
|
||||||
|
|
||||||
|
await renew_key_in_cluster(
|
||||||
|
cluster_id=server_id,
|
||||||
|
email=email,
|
||||||
|
client_id=client_id,
|
||||||
|
new_expiry_time=expiry_time,
|
||||||
|
total_gb=total_gb,
|
||||||
|
session=session,
|
||||||
|
hwid_device_limit=hwid_device_limit_to_set,
|
||||||
|
reset_traffic=False,
|
||||||
|
target_subgroup=target_subgroup,
|
||||||
|
old_subgroup=old_subgroup,
|
||||||
|
plan=int(tariff_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await session.execute(
|
||||||
|
sa.update(User)
|
||||||
|
.where(User.tg_id == tg_id, User.balance >= extra_price)
|
||||||
|
.values(balance=User.balance - extra_price)
|
||||||
|
)
|
||||||
|
if (result.rowcount or 0) != 1:
|
||||||
|
await session.rollback()
|
||||||
|
await callback.answer("❌ Недостаточно средств.", show_alert=True)
|
||||||
|
await state.clear()
|
||||||
|
return
|
||||||
|
|
||||||
|
await save_key_config_with_mode(
|
||||||
|
session=session,
|
||||||
|
email=email,
|
||||||
|
selected_devices=new_device_limit_effective,
|
||||||
|
selected_traffic_gb=new_traffic_limit_gb_effective,
|
||||||
|
total_price=int(total_price_after_purchase),
|
||||||
|
has_device_choice=has_device_option,
|
||||||
|
has_traffic_choice=has_traffic_option,
|
||||||
|
config_mode="pack",
|
||||||
|
)
|
||||||
|
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"[ADDONS] PACK_MODE успешная покупка пакета: "
|
||||||
|
f"tg_id={tg_id} email={email} extra_price={extra_price} "
|
||||||
|
f"new_device_limit_effective={new_device_limit_effective} "
|
||||||
|
f"new_traffic_limit_gb_effective={new_traffic_limit_gb_effective} "
|
||||||
|
f"recalc_enabled={recalc_enabled} pack_mode={pack_mode!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
await render_key_info(callback.message, session, email, "img/pic_view.jpg")
|
||||||
|
await callback.answer(ADDONS_PACK_SUCCESS_TEXT, show_alert=True)
|
||||||
|
|
||||||
|
except Exception as error:
|
||||||
|
logger.error(f"[ADDONS] PACK_MODE ошибка при покупке пакета для {email}: {error}")
|
||||||
|
await callback.message.answer("❌ Ошибка при обновлении подписки. Попробуйте позже.")
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
await state.update_data(addon_confirm_in_progress=False)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|||||||
+13
-10
@@ -5,27 +5,30 @@ from cachetools import TTLCache
|
|||||||
|
|
||||||
class ThrottlingMiddleware(BaseMiddleware):
|
class ThrottlingMiddleware(BaseMiddleware):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.cache = TTLCache(maxsize=10_000, ttl=1.0)
|
self.cache = TTLCache(maxsize=50_000, ttl=1.0)
|
||||||
self.throttle_notice_cache = TTLCache(maxsize=10_000, ttl=1.0)
|
self.throttle_notice_cache = TTLCache(maxsize=50_000, ttl=1.0)
|
||||||
|
|
||||||
async def __call__(self, handler, event, data):
|
async def __call__(self, handler, event, data):
|
||||||
|
if not isinstance(event, CallbackQuery):
|
||||||
|
return await handler(event, data)
|
||||||
|
|
||||||
user_id = event.from_user.id if event.from_user else None
|
user_id = event.from_user.id if event.from_user else None
|
||||||
if user_id is None:
|
if user_id is None:
|
||||||
return await handler(event, data)
|
return await handler(event, data)
|
||||||
|
|
||||||
current_count = self.cache.get(user_id, 0)
|
key = (user_id, event.data or "")
|
||||||
|
current_count = self.cache.get(key, 0)
|
||||||
|
|
||||||
if current_count >= 3:
|
if current_count >= 2:
|
||||||
if isinstance(event, CallbackQuery) and user_id not in self.throttle_notice_cache:
|
if key not in self.throttle_notice_cache:
|
||||||
self.throttle_notice_cache[user_id] = None
|
self.throttle_notice_cache[key] = None
|
||||||
bot: Bot = data["bot"]
|
bot: Bot = data["bot"]
|
||||||
await bot.answer_callback_query(
|
await bot.answer_callback_query(
|
||||||
callback_query_id=event.id,
|
callback_query_id=event.id,
|
||||||
text="Слишком много запросов! Пожалуйста, подождите...",
|
text="Слишком много нажатий, подождите...",
|
||||||
show_alert=False,
|
show_alert=False,
|
||||||
)
|
)
|
||||||
return None
|
return
|
||||||
else:
|
|
||||||
self.cache[user_id] = current_count + 1
|
|
||||||
|
|
||||||
|
self.cache[key] = current_count + 1
|
||||||
return await handler(event, data)
|
return await handler(event, data)
|
||||||
|
|||||||
+41
-16
@@ -1,19 +1,22 @@
|
|||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
|
from datetime import datetime
|
||||||
from time import monotonic
|
from time import monotonic
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiogram import BaseMiddleware
|
from aiogram import BaseMiddleware
|
||||||
from aiogram.types import TelegramObject, User
|
from aiogram.types import TelegramObject, User
|
||||||
|
from sqlalchemy import update
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from database import upsert_user
|
from database import upsert_user
|
||||||
|
from database.models import User as DbUser
|
||||||
from logger import logger
|
from logger import logger
|
||||||
|
|
||||||
|
|
||||||
class UserMiddleware(BaseMiddleware):
|
class UserMiddleware(BaseMiddleware):
|
||||||
def __init__(self, debounce_sec: float = 60.0) -> None:
|
def __init__(self, debounce_sec: float = 60.0) -> None:
|
||||||
self._debounce = float(debounce_sec)
|
self._debounce = float(debounce_sec)
|
||||||
self._cache: dict[int, tuple[str, float, dict | None]] = {}
|
self._cache: dict[int, tuple[str, float, float, dict | None]] = {}
|
||||||
|
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
@@ -40,11 +43,17 @@ class UserMiddleware(BaseMiddleware):
|
|||||||
|
|
||||||
cached = self._cache.get(uid)
|
cached = self._cache.get(uid)
|
||||||
if cached:
|
if cached:
|
||||||
cached_fingerprint, ts, cached_db_user = cached
|
cached_fingerprint, profile_ts, touch_ts, cached_db_user = cached
|
||||||
if fingerprint == cached_fingerprint and now - ts < self._debounce:
|
|
||||||
return cached_db_user
|
if fingerprint == cached_fingerprint:
|
||||||
|
if now - touch_ts >= self._debounce:
|
||||||
|
db_user = await self._touch_user(uid, session)
|
||||||
|
self._cache[uid] = (cached_fingerprint, profile_ts, now, db_user or cached_db_user)
|
||||||
|
return db_user or cached_db_user
|
||||||
|
|
||||||
|
if now - profile_ts < self._debounce:
|
||||||
|
return cached_db_user
|
||||||
|
|
||||||
logger.debug(f"Обработка пользователя: {uid}")
|
|
||||||
db_user = await upsert_user(
|
db_user = await upsert_user(
|
||||||
tg_id=uid,
|
tg_id=uid,
|
||||||
username=user.username,
|
username=user.username,
|
||||||
@@ -55,17 +64,33 @@ class UserMiddleware(BaseMiddleware):
|
|||||||
session=session,
|
session=session,
|
||||||
only_if_exists=True,
|
only_if_exists=True,
|
||||||
)
|
)
|
||||||
self._cache[uid] = (fingerprint, now, db_user)
|
self._cache[uid] = (fingerprint, now, now, db_user)
|
||||||
if db_user:
|
|
||||||
logger.debug(f"Получены данные пользователя из БД: {uid}")
|
|
||||||
return db_user
|
return db_user
|
||||||
|
|
||||||
|
async def _touch_user(self, tg_id: int, session: AsyncSession) -> dict | None:
|
||||||
|
now = datetime.utcnow()
|
||||||
|
res = await session.execute(
|
||||||
|
update(DbUser)
|
||||||
|
.where(DbUser.tg_id == tg_id)
|
||||||
|
.values(updated_at=now)
|
||||||
|
.returning(DbUser)
|
||||||
|
)
|
||||||
|
obj = res.scalar_one_or_none()
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
await session.commit()
|
||||||
|
d = obj.__dict__.copy()
|
||||||
|
d.pop("_sa_instance_state", None)
|
||||||
|
return d
|
||||||
|
|
||||||
def _fingerprint(self, user: User) -> str:
|
def _fingerprint(self, user: User) -> str:
|
||||||
return "|".join([
|
return "|".join(
|
||||||
str(user.id),
|
[
|
||||||
user.username or "",
|
str(user.id),
|
||||||
user.first_name or "",
|
user.username or "",
|
||||||
user.last_name or "",
|
user.first_name or "",
|
||||||
user.language_code or "",
|
user.last_name or "",
|
||||||
"1" if user.is_bot else "0",
|
user.language_code or "",
|
||||||
])
|
"1" if user.is_bot else "0",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
+1
-1
@@ -92,4 +92,4 @@ def get_git_commit_number() -> str:
|
|||||||
|
|
||||||
|
|
||||||
def get_version() -> str:
|
def get_version() -> str:
|
||||||
return f"v.5.1-b261235 {get_git_commit_number()}"
|
return f"v.5.1-b23012603 {get_git_commit_number()}"
|
||||||
|
|||||||
Reference in New Issue
Block a user