from jinja2 import Environment, FileSystemLoader from loguru import logger from fastapi import APIRouter, Form, Request from fastapi.responses import HTMLResponse, RedirectResponse, Response from sqlalchemy import func, select from app.database import async_session_factory from app.models.payment import Payment, PaymentStatus from app.models.server import Server, ServerProtocol from app.models.tariff import Tariff from app.models.user import User from app.repositories.payment import PaymentRepository from app.repositories.server import ServerRepository from app.repositories.tariff import TariffRepository from app.repositories.user import UserRepository from app.services.payment import PaymentService from app.services.server import ServerService from app.services.user import UserService router = APIRouter(prefix="/admin", tags=["admin"]) _env = Environment(loader=FileSystemLoader("app/templates"), auto_reload=False) def _render(name: str, **context) -> str: template = _env.get_template(name) return template.render(**context) @router.get("/", response_class=HTMLResponse) async def dashboard(request: Request): async with async_session_factory() as session: async def _count(model, *filters): stmt = select(func.count(model.id)) for f in filters: stmt = stmt.where(f) return (await session.execute(stmt)).scalar_one() total_users = await _count(User) active_users = await _count(User, User.is_active.is_(True)) total_tariffs = await _count(Tariff) total_servers = await _count(Server) active_servers = await _count(Server, Server.is_active.is_(True)) total_payments = await _count(Payment) confirmed_payments = await _count( Payment, Payment.status == PaymentStatus.CONFIRMED ) rev_stmt = select(func.coalesce(func.sum(Payment.amount), 0)).where( Payment.status == PaymentStatus.CONFIRMED ) total_revenue = float((await session.execute(rev_stmt)).scalar_one()) logger.info( "Dashboard data: users={}/{} tariffs={} servers={}/{} payments={}/{} revenue={}", active_users, total_users, total_tariffs, active_servers, total_servers, confirmed_payments, total_payments, total_revenue, ) return Response( content=_render( "dashboard.html", request=request, total_users=total_users, active_users=active_users, total_tariffs=total_tariffs, total_servers=total_servers, active_servers=active_servers, total_payments=total_payments, confirmed_payments=confirmed_payments, total_revenue=total_revenue, ), media_type="text/html", headers={"Cache-Control": "no-store, must-revalidate"}, ) @router.get("/users", response_class=HTMLResponse) async def users_page(request: Request, error: str = ""): async with async_session_factory() as session: repo = UserRepository(session) users = await repo.get_all() return HTMLResponse( _render("users.html", request=request, users=users, error=error) ) @router.post("/users/create", response_class=HTMLResponse) async def users_create( request: Request, telegram_id: int = Form(...), username: str = Form(""), full_name: str = Form(""), language_code: str = Form("ru"), ): async with async_session_factory() as session: user_repo = UserRepository(session) user_service = UserService(user_repo) existing = await user_repo.get_by_telegram_id(telegram_id) if existing is not None: return HTMLResponse( _render( "users.html", request=request, users=await user_repo.get_all(), error=f"User with telegram_id {telegram_id} already exists", ) ) await user_service.register( telegram_id=telegram_id, username=username or None, full_name=full_name or str(telegram_id), language_code=language_code, ) return RedirectResponse(url="/admin/users", status_code=303) async def _get_payments_with_tariffs(session): stmt = select(Payment).order_by(Payment.created_at.desc()) result = await session.execute(stmt) payments = list(result.scalars().all()) tariff_repo = TariffRepository(session) tariffs_map = {} for p in payments: if p.tariff_id not in tariffs_map: t = await tariff_repo.get(p.tariff_id) tariffs_map[p.tariff_id] = t.name if t else "—" return payments, tariffs_map @router.get("/payments", response_class=HTMLResponse) async def payments_page(request: Request): async with async_session_factory() as session: user_repo = UserRepository(session) tariff_repo = TariffRepository(session) payments, tariffs_map = await _get_payments_with_tariffs(session) users = await user_repo.get_all() tariffs = await tariff_repo.get_all() return HTMLResponse( _render( "payments.html", request=request, payments=payments, tariffs_map=tariffs_map, users=users, tariffs=tariffs, ) ) @router.post("/payments/create", response_class=HTMLResponse) async def payments_create( request: Request, user_id: int = Form(...), tariff_id: int = Form(...), amount: float = Form(...), currency: str = Form("RUB"), provider: str = Form("manual"), ): async with async_session_factory() as session: payment_repo = PaymentRepository(session) user_repo = UserRepository(session) tariff_repo = TariffRepository(session) payment_service = PaymentService(payment_repo, user_repo, tariff_repo) try: payment = await payment_service.create_payment( user_id=user_id, tariff_id=tariff_id, amount=amount, currency=currency, provider=provider, ) logger.info("Admin created payment id={}", payment.id) except ValueError as e: payments, tariffs_map = await _get_payments_with_tariffs(session) return HTMLResponse( _render( "payments.html", request=request, payments=payments, tariffs_map=tariffs_map, users=await user_repo.get_all(), tariffs=await tariff_repo.get_all(), error=str(e), ) ) return RedirectResponse(url="/admin/payments", status_code=303) @router.get("/servers", response_class=HTMLResponse) async def servers_page(request: Request): async with async_session_factory() as session: repo = ServerRepository(session) servers = await repo.get_all() return HTMLResponse( _render( "servers.html", request=request, servers=servers, protocols=[p.value for p in ServerProtocol], ) ) @router.post("/servers/create", response_class=HTMLResponse) async def servers_create( request: Request, name: str = Form(...), host: str = Form(...), port: int = Form(...), protocol: str = Form(...), location: str = Form(...), country_code: str = Form(...), max_users: int = Form(100), ): async with async_session_factory() as session: server_repo = ServerRepository(session) server_service = ServerService(server_repo) try: server = await server_service.create( name=name, host=host, port=port, protocol=ServerProtocol(protocol), location=location, country_code=country_code, max_users=max_users, ) logger.info("Admin created server id={}", server.id) except ValueError as e: repo = ServerRepository(session) return HTMLResponse( _render( "servers.html", request=request, servers=await repo.get_all(), protocols=[p.value for p in ServerProtocol], error=str(e), ) ) return RedirectResponse(url="/admin/servers", status_code=303) @router.get("/tariffs", response_class=HTMLResponse) async def tariffs_page(request: Request): async with async_session_factory() as session: repo = TariffRepository(session) tariffs = await repo.get_all() return HTMLResponse( _render("tariffs.html", request=request, tariffs=tariffs) ) @router.post("/tariffs/create", response_class=HTMLResponse) async def tariffs_create( request: Request, name: str = Form(...), duration_days: int = Form(...), price: float = Form(...), currency: str = Form("RUB"), max_devices: int = Form(1), traffic_gb: int = Form(0), ): async with async_session_factory() as session: tariff_repo = TariffRepository(session) traffic = traffic_gb if traffic_gb > 0 else None tariff = await tariff_repo.create( name=name, duration_days=duration_days, price=price, currency=currency, max_devices=max_devices, traffic_gb=traffic, ) logger.info("Admin created tariff id={}", tariff.id) return RedirectResponse(url="/admin/tariffs", status_code=303)