feat: email login, smtp codes
This commit is contained in:
@@ -23,6 +23,7 @@ from bot.middlewares.i18n import JsonI18n
|
||||
|
||||
router = Router(name="admin_logs_router")
|
||||
USERNAME_REGEX = re.compile(r"^[a-zA-Z0-9_]{5,32}$")
|
||||
EMAIL_REGEX = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
|
||||
|
||||
|
||||
async def display_logs_menu(callback: types.CallbackQuery, i18n_data: dict,
|
||||
@@ -225,12 +226,14 @@ async def process_user_id_for_logs_handler(message: types.Message,
|
||||
input_text = message.text.strip() if message.text else ""
|
||||
user_model_for_logs: Optional[User] = None
|
||||
|
||||
if input_text.isdigit():
|
||||
if input_text.isdigit() or (input_text.startswith("-") and input_text[1:].isdigit()):
|
||||
try:
|
||||
user_model_for_logs = await user_dal.get_user_by_id(
|
||||
session, int(input_text))
|
||||
except ValueError:
|
||||
pass
|
||||
elif EMAIL_REGEX.match(input_text):
|
||||
user_model_for_logs = await user_dal.get_user_by_email(session, input_text)
|
||||
elif input_text.startswith("@") and USERNAME_REGEX.match(input_text[1:]):
|
||||
user_model_for_logs = await user_dal.get_user_by_username(
|
||||
session, input_text[1:])
|
||||
@@ -245,7 +248,7 @@ async def process_user_id_for_logs_handler(message: types.Message,
|
||||
target_user_id = user_model_for_logs.user_id
|
||||
user_display_name = user_model_for_logs.first_name or (
|
||||
f"@{user_model_for_logs.username}"
|
||||
if user_model_for_logs.username else f"ID {target_user_id}")
|
||||
if user_model_for_logs.username else (user_model_for_logs.email or f"ID {target_user_id}"))
|
||||
|
||||
logs_models = await message_log_dal.get_user_message_logs(
|
||||
session, target_user_id, settings.LOGS_PAGE_SIZE, 0)
|
||||
@@ -292,7 +295,7 @@ async def view_user_logs_paginated_handler(callback: types.CallbackQuery,
|
||||
|
||||
user_display_name = user_model_for_logs.first_name or (
|
||||
f"@{user_model_for_logs.username}"
|
||||
if user_model_for_logs.username else f"ID {target_user_id}")
|
||||
if user_model_for_logs.username else (user_model_for_logs.email or f"ID {target_user_id}"))
|
||||
|
||||
logs_models = await message_log_dal.get_user_message_logs(
|
||||
session, target_user_id, settings.LOGS_PAGE_SIZE,
|
||||
|
||||
@@ -94,6 +94,7 @@ async def perform_sync(
|
||||
"shortUuid"
|
||||
)
|
||||
telegram_id_from_panel = panel_user_dict.get("telegramId")
|
||||
email_from_panel = (panel_user_dict.get("email") or "").strip().lower() or None
|
||||
|
||||
if not panel_uuid:
|
||||
sync_errors.append(f"Panel user missing UUID: {panel_user_dict}")
|
||||
@@ -111,14 +112,25 @@ async def perform_sync(
|
||||
|
||||
# First, try to find by telegram ID if available
|
||||
if telegram_id_from_panel:
|
||||
existing_user = await user_dal.get_user_by_id(
|
||||
existing_user = await user_dal.get_user_by_telegram_id(
|
||||
session, telegram_id_from_panel
|
||||
)
|
||||
if not existing_user:
|
||||
existing_user = await user_dal.get_user_by_id(
|
||||
session, telegram_id_from_panel
|
||||
)
|
||||
if existing_user:
|
||||
logging.debug(
|
||||
f"Found user by telegramId {telegram_id_from_panel}"
|
||||
)
|
||||
|
||||
if not existing_user and email_from_panel:
|
||||
existing_user = await user_dal.get_user_by_email(
|
||||
session, email_from_panel
|
||||
)
|
||||
if existing_user:
|
||||
logging.debug(f"Found user by email {email_from_panel}")
|
||||
|
||||
# If not found by telegram ID, try to find by panel UUID
|
||||
if not existing_user:
|
||||
existing_user = await user_dal.get_user_by_panel_uuid(
|
||||
@@ -144,6 +156,8 @@ async def perform_sync(
|
||||
try:
|
||||
user_data = {
|
||||
"user_id": telegram_id_from_panel,
|
||||
"telegram_id": telegram_id_from_panel,
|
||||
"email": email_from_panel,
|
||||
"username": None, # Username will be updated when user interacts with bot
|
||||
"first_name": None, # Panel doesn't provide this info
|
||||
"last_name": None, # Panel doesn't provide this info
|
||||
@@ -172,6 +186,28 @@ async def perform_sync(
|
||||
f"Error creating user {telegram_id_from_panel}: {e_create}"
|
||||
)
|
||||
continue
|
||||
elif email_from_panel:
|
||||
try:
|
||||
new_user, was_created = await user_dal.create_email_user(
|
||||
session,
|
||||
email=email_from_panel,
|
||||
language_code="ru",
|
||||
)
|
||||
new_user.panel_user_uuid = panel_uuid
|
||||
if was_created:
|
||||
users_created += 1
|
||||
logging.info(
|
||||
f"Created new email user {new_user.user_id} from panel sync with UUID {panel_uuid}"
|
||||
)
|
||||
existing_user = new_user
|
||||
except Exception as e_create_email:
|
||||
sync_errors.append(
|
||||
f"Error creating email user {email_from_panel}: {str(e_create_email)}"
|
||||
)
|
||||
logging.error(
|
||||
f"Error creating email user {email_from_panel}: {e_create_email}"
|
||||
)
|
||||
continue
|
||||
else:
|
||||
logging.debug(
|
||||
f"Panel user with UUID {panel_uuid} (no telegramId) not found in local DB - skipping"
|
||||
@@ -193,6 +229,17 @@ async def perform_sync(
|
||||
logging.info(
|
||||
f"Updated panel UUID for user {actual_user_id}: {panel_uuid}"
|
||||
)
|
||||
if email_from_panel and existing_user.email != email_from_panel:
|
||||
existing_user.email = email_from_panel
|
||||
if not existing_user.email_verified_at:
|
||||
existing_user.email_verified_at = datetime.now(timezone.utc)
|
||||
user_was_updated = True
|
||||
if (
|
||||
telegram_id_from_panel
|
||||
and existing_user.telegram_id != telegram_id_from_panel
|
||||
):
|
||||
existing_user.telegram_id = telegram_id_from_panel
|
||||
user_was_updated = True
|
||||
|
||||
lifetime_used = _extract_lifetime_used_traffic_bytes(panel_user_dict)
|
||||
if (
|
||||
@@ -206,11 +253,12 @@ async def perform_sync(
|
||||
try:
|
||||
if panel_uuid and existing_user:
|
||||
description_text = "\n".join(
|
||||
[
|
||||
line for line in [
|
||||
existing_user.email or "",
|
||||
existing_user.username or "",
|
||||
existing_user.first_name or "",
|
||||
existing_user.last_name or "",
|
||||
]
|
||||
] if line
|
||||
)
|
||||
# Update description only when it differs from the current one on panel
|
||||
current_panel_description = (
|
||||
@@ -222,7 +270,11 @@ async def perform_sync(
|
||||
and desired_description != current_panel_description
|
||||
):
|
||||
await panel_service.update_user_details_on_panel(
|
||||
panel_uuid, {"description": description_text}
|
||||
panel_uuid, {
|
||||
"description": description_text,
|
||||
**({"email": existing_user.email} if existing_user.email else {}),
|
||||
**({"telegramId": existing_user.telegram_id} if existing_user.telegram_id else {}),
|
||||
}
|
||||
)
|
||||
except Exception as e_desc:
|
||||
logging.warning(
|
||||
|
||||
@@ -31,6 +31,7 @@ from bot.utils.telegram_markup import (
|
||||
|
||||
router = Router(name="admin_user_management_router")
|
||||
USERNAME_REGEX = re.compile(r"^[a-zA-Z0-9_]{5,32}$")
|
||||
EMAIL_REGEX = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
|
||||
|
||||
|
||||
def _format_traffic_period(strategy: Optional[str], get_text: Callable[..., str]) -> Optional[str]:
|
||||
@@ -53,6 +54,24 @@ def _format_used_with_period(get_text: Callable[..., str], used_display: str, pe
|
||||
return get_text("traffic_used_with_period", traffic_used=used_display, traffic_period=period_label)
|
||||
|
||||
|
||||
async def _find_user_by_admin_input(
|
||||
session: AsyncSession,
|
||||
input_text: str,
|
||||
) -> Optional[User]:
|
||||
if input_text.isdigit() or (input_text.startswith("-") and input_text[1:].isdigit()):
|
||||
try:
|
||||
return await user_dal.get_user_by_id(session, int(input_text))
|
||||
except ValueError:
|
||||
return None
|
||||
if EMAIL_REGEX.match(input_text):
|
||||
return await user_dal.get_user_by_email(session, input_text)
|
||||
if input_text.startswith("@") and USERNAME_REGEX.match(input_text[1:]):
|
||||
return await user_dal.get_user_by_username(session, input_text[1:])
|
||||
if USERNAME_REGEX.match(input_text):
|
||||
return await user_dal.get_user_by_username(session, input_text)
|
||||
return None
|
||||
|
||||
|
||||
async def users_list_handler(callback: types.CallbackQuery,
|
||||
i18n_data: dict, settings: Settings,
|
||||
session: AsyncSession, page: int = 0):
|
||||
@@ -250,6 +269,10 @@ async def format_user_card(user: User, session: AsyncSession,
|
||||
card_parts.append(f"{_('admin_user_id_label')} {hcode(str(user.user_id))}")
|
||||
card_parts.append(f"{_('admin_user_name_label')} {hcode(user_name)}")
|
||||
card_parts.append(f"{_('admin_user_username_label')} {hcode(username_display)}")
|
||||
if user.email:
|
||||
card_parts.append(f"{_('admin_user_email_label')} {hcode(user.email)}")
|
||||
if user.telegram_id and int(user.telegram_id) != int(user.user_id):
|
||||
card_parts.append(f"{_('admin_user_telegram_id_label')} {hcode(str(user.telegram_id))}")
|
||||
card_parts.append(f"{_('admin_user_language_label')} {hcode(user.language_code or na_value)}")
|
||||
card_parts.append(f"{_('admin_user_registration_label')} {hcode(registration_date)}")
|
||||
|
||||
@@ -361,18 +384,7 @@ async def process_user_search_handler(message: types.Message, state: FSMContext,
|
||||
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||
|
||||
input_text = message.text.strip() if message.text else ""
|
||||
user_model: Optional[User] = None
|
||||
|
||||
# Try to find user by ID or username
|
||||
if input_text.isdigit():
|
||||
try:
|
||||
user_model = await user_dal.get_user_by_id(session, int(input_text))
|
||||
except ValueError:
|
||||
pass
|
||||
elif input_text.startswith("@") and USERNAME_REGEX.match(input_text[1:]):
|
||||
user_model = await user_dal.get_user_by_username(session, input_text[1:])
|
||||
elif USERNAME_REGEX.match(input_text):
|
||||
user_model = await user_dal.get_user_by_username(session, input_text)
|
||||
user_model = await _find_user_by_admin_input(session, input_text)
|
||||
|
||||
if not user_model:
|
||||
await message.answer(_(
|
||||
@@ -1177,18 +1189,7 @@ async def process_ban_user_handler(message: types.Message, state: FSMContext,
|
||||
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||
|
||||
input_text = message.text.strip() if message.text else ""
|
||||
user_model: Optional[User] = None
|
||||
|
||||
# Try to find user by ID or username
|
||||
if input_text.isdigit():
|
||||
try:
|
||||
user_model = await user_dal.get_user_by_id(session, int(input_text))
|
||||
except ValueError:
|
||||
pass
|
||||
elif input_text.startswith("@") and USERNAME_REGEX.match(input_text[1:]):
|
||||
user_model = await user_dal.get_user_by_username(session, input_text[1:])
|
||||
elif USERNAME_REGEX.match(input_text):
|
||||
user_model = await user_dal.get_user_by_username(session, input_text)
|
||||
user_model = await _find_user_by_admin_input(session, input_text)
|
||||
|
||||
if not user_model:
|
||||
await message.answer(_(
|
||||
@@ -1244,18 +1245,7 @@ async def process_unban_user_handler(message: types.Message, state: FSMContext,
|
||||
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
|
||||
|
||||
input_text = message.text.strip() if message.text else ""
|
||||
user_model: Optional[User] = None
|
||||
|
||||
# Try to find user by ID or username
|
||||
if input_text.isdigit():
|
||||
try:
|
||||
user_model = await user_dal.get_user_by_id(session, int(input_text))
|
||||
except ValueError:
|
||||
pass
|
||||
elif input_text.startswith("@") and USERNAME_REGEX.match(input_text[1:]):
|
||||
user_model = await user_dal.get_user_by_username(session, input_text[1:])
|
||||
elif USERNAME_REGEX.match(input_text):
|
||||
user_model = await user_dal.get_user_by_username(session, input_text)
|
||||
user_model = await _find_user_by_admin_input(session, input_text)
|
||||
|
||||
if not user_model:
|
||||
await message.answer(_(
|
||||
|
||||
Reference in New Issue
Block a user