- FastAPI REST API with JWT auth - aiogram 3 Telegram bot with admin middleware - APScheduler daily tasks (expiry, reminders, revoke, sync) - SQLAlchemy 2 async ORM with Alembic migrations - Jinja2 admin panel (Dashboard, Users, Payments, Servers, Tariffs) - VPN provider abstraction with MockProvider - Stats service with revenue/subscription analytics - Docker Compose (PostgreSQL + Redis + app) - Healthcheck endpoint
118 lines
3.6 KiB
Python
118 lines
3.6 KiB
Python
from datetime import datetime
|
|
|
|
from aiogram import Router
|
|
from aiogram.filters import Command, CommandObject
|
|
from aiogram.types import Message
|
|
from loguru import logger
|
|
|
|
from app.bot.texts import (
|
|
user_info,
|
|
user_short,
|
|
USER_NOT_FOUND,
|
|
INVALID_ARGS,
|
|
NO_DATA,
|
|
)
|
|
from app.database import async_session_factory
|
|
from app.repositories.user import UserRepository
|
|
from app.repositories.payment import PaymentRepository
|
|
from app.repositories.tariff import TariffRepository
|
|
from app.services.billing import BillingService
|
|
from app.services.user import UserService
|
|
|
|
router = Router(name="users")
|
|
|
|
|
|
@router.message(Command("users"))
|
|
async def cmd_users(message: Message) -> None:
|
|
async with async_session_factory() as session:
|
|
user_service = UserService(UserRepository(session))
|
|
users = await user_service.user_repo.get_active_users()
|
|
|
|
if not users:
|
|
await message.answer(NO_DATA)
|
|
return
|
|
|
|
lines = [f"👥 <b>Active users ({len(users)}):</b>", ""]
|
|
for u in users:
|
|
lines.append(user_short(u.id, u.telegram_id, u.username))
|
|
|
|
await message.answer("\n".join(lines), parse_mode="HTML")
|
|
|
|
|
|
@router.message(Command("user"))
|
|
async def cmd_user(message: Message, command: CommandObject) -> None:
|
|
if not command.args or not command.args.strip().isdigit():
|
|
await message.answer(
|
|
INVALID_ARGS.format("/user <telegram_id>"),
|
|
parse_mode="HTML",
|
|
)
|
|
return
|
|
|
|
telegram_id = int(command.args.strip())
|
|
|
|
async with async_session_factory() as session:
|
|
user_repo = UserRepository(session)
|
|
user = await user_repo.get_by_telegram_id(telegram_id)
|
|
|
|
if user is None:
|
|
await message.answer(
|
|
USER_NOT_FOUND.format(telegram_id), parse_mode="HTML"
|
|
)
|
|
return
|
|
|
|
billing = BillingService(
|
|
PaymentRepository(session),
|
|
TariffRepository(session),
|
|
user_repo,
|
|
)
|
|
sub = await billing.get_active_subscription(user.id)
|
|
|
|
await message.answer(
|
|
user_info(
|
|
user_id=user.id,
|
|
telegram_id=user.telegram_id,
|
|
username=user.username,
|
|
full_name=user.full_name,
|
|
is_active=user.is_active,
|
|
created_at=user.created_at.strftime("%Y-%m-%d"),
|
|
subscription_active=sub is not None and sub.is_active,
|
|
tariff_name=sub.tariff.name if sub and sub.tariff else None,
|
|
remaining_days=sub.remaining_days if sub else 0,
|
|
),
|
|
parse_mode="HTML",
|
|
)
|
|
|
|
|
|
@router.message(Command("delete"))
|
|
async def cmd_delete(message: Message, command: CommandObject) -> None:
|
|
if not command.args or not command.args.strip().isdigit():
|
|
await message.answer(
|
|
INVALID_ARGS.format("/delete <telegram_id>"),
|
|
parse_mode="HTML",
|
|
)
|
|
return
|
|
|
|
telegram_id = int(command.args.strip())
|
|
|
|
async with async_session_factory() as session:
|
|
user_repo = UserRepository(session)
|
|
user = await user_repo.get_by_telegram_id(telegram_id)
|
|
|
|
if user is None:
|
|
await message.answer(
|
|
USER_NOT_FOUND.format(telegram_id), parse_mode="HTML"
|
|
)
|
|
return
|
|
|
|
user_service = UserService(user_repo)
|
|
ok = await user_service.deactivate(user.id)
|
|
|
|
if ok:
|
|
await message.answer(
|
|
f"✅ User <code>{telegram_id}</code> deactivated.",
|
|
parse_mode="HTML",
|
|
)
|
|
logger.info("Bot: user deactivated tg={}", telegram_id)
|
|
else:
|
|
await message.answer("❌ Failed to deactivate user.")
|