293 lines
9.5 KiB
Python
293 lines
9.5 KiB
Python
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)
|