- 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
119 lines
4.0 KiB
Python
119 lines
4.0 KiB
Python
from fastapi import APIRouter, Request
|
|
from fastapi.responses import HTMLResponse
|
|
from fastapi.templating import Jinja2Templates
|
|
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
|
|
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
|
|
|
|
router = APIRouter(prefix="/admin", tags=["admin"])
|
|
templates = Jinja2Templates(directory="app/templates")
|
|
|
|
router = APIRouter(prefix="/admin", tags=["admin"])
|
|
templates = Jinja2Templates(directory="app/templates")
|
|
|
|
|
|
@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())
|
|
|
|
return templates.TemplateResponse(
|
|
"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,
|
|
},
|
|
)
|
|
|
|
|
|
@router.get("/users", response_class=HTMLResponse)
|
|
async def users_page(request: Request):
|
|
async with async_session_factory() as session:
|
|
repo = UserRepository(session)
|
|
users = await repo.get_all()
|
|
|
|
return templates.TemplateResponse(
|
|
"users.html",
|
|
{"request": request, "users": users},
|
|
)
|
|
|
|
|
|
@router.get("/payments", response_class=HTMLResponse)
|
|
async def payments_page(request: Request):
|
|
async with async_session_factory() as session:
|
|
tariff_repo = TariffRepository(session)
|
|
|
|
stmt = select(Payment).order_by(Payment.created_at.desc())
|
|
result = await session.execute(stmt)
|
|
payments = list(result.scalars().all())
|
|
|
|
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 templates.TemplateResponse(
|
|
"payments.html",
|
|
{"request": request, "payments": payments, "tariffs_map": tariffs_map},
|
|
)
|
|
|
|
|
|
@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 templates.TemplateResponse(
|
|
"servers.html",
|
|
{"request": request, "servers": servers},
|
|
)
|
|
|
|
|
|
@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 templates.TemplateResponse(
|
|
"tariffs.html",
|
|
{"request": request, "tariffs": tariffs},
|
|
)
|