- 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
141 lines
3.6 KiB
Python
141 lines
3.6 KiB
Python
from collections.abc import AsyncGenerator
|
|
|
|
from fastapi import Depends, HTTPException, status
|
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.database import async_session_factory
|
|
from app.repositories import (
|
|
UserRepository,
|
|
PaymentRepository,
|
|
ServerRepository,
|
|
TariffRepository,
|
|
NotificationRepository,
|
|
AdminRepository,
|
|
)
|
|
from app.services import (
|
|
UserService,
|
|
PaymentService,
|
|
BillingService,
|
|
NotificationService,
|
|
ServerService,
|
|
VPNService,
|
|
)
|
|
from app.services.auth import AuthService
|
|
|
|
security = HTTPBearer()
|
|
|
|
|
|
async def get_db() -> AsyncGenerator[AsyncSession, None]:
|
|
async with async_session_factory() as session:
|
|
try:
|
|
yield session
|
|
await session.commit()
|
|
except Exception:
|
|
await session.rollback()
|
|
raise
|
|
|
|
|
|
def get_user_service(db: AsyncSession = Depends(get_db)) -> UserService:
|
|
return UserService(UserRepository(db))
|
|
|
|
|
|
def get_payment_service(db: AsyncSession = Depends(get_db)) -> PaymentService:
|
|
return PaymentService(
|
|
PaymentRepository(db),
|
|
UserRepository(db),
|
|
TariffRepository(db),
|
|
)
|
|
|
|
|
|
def get_billing_service(db: AsyncSession = Depends(get_db)) -> BillingService:
|
|
return BillingService(
|
|
PaymentRepository(db),
|
|
TariffRepository(db),
|
|
UserRepository(db),
|
|
)
|
|
|
|
|
|
def get_notification_service(
|
|
db: AsyncSession = Depends(get_db),
|
|
) -> NotificationService:
|
|
return NotificationService(
|
|
NotificationRepository(db),
|
|
UserRepository(db),
|
|
)
|
|
|
|
|
|
def get_server_service(db: AsyncSession = Depends(get_db)) -> ServerService:
|
|
return ServerService(ServerRepository(db))
|
|
|
|
|
|
def get_vpn_service(db: AsyncSession = Depends(get_db)) -> VPNService:
|
|
return VPNService(
|
|
ServerRepository(db),
|
|
UserRepository(db),
|
|
)
|
|
|
|
|
|
def get_tariff_repo(db: AsyncSession = Depends(get_db)) -> TariffRepository:
|
|
return TariffRepository(db)
|
|
|
|
|
|
def get_server_repo(db: AsyncSession = Depends(get_db)) -> ServerRepository:
|
|
return ServerRepository(db)
|
|
|
|
|
|
def get_auth_service(db: AsyncSession = Depends(get_db)) -> AuthService:
|
|
return AuthService(AdminRepository(db))
|
|
|
|
|
|
async def get_current_admin(
|
|
credentials: HTTPAuthorizationCredentials = Depends(security),
|
|
db: AsyncSession = Depends(get_db),
|
|
) -> dict:
|
|
from app.services.auth import AuthService
|
|
|
|
auth_svc = AuthService(AdminRepository(db))
|
|
|
|
try:
|
|
payload = auth_svc.decode_token(credentials.credentials)
|
|
except ValueError as e:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail=str(e),
|
|
)
|
|
|
|
if payload.get("type") != "access":
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Invalid token type",
|
|
)
|
|
|
|
telegram_id = int(payload["sub"])
|
|
admin = await AdminRepository(db).get_by_telegram_id(telegram_id)
|
|
|
|
if admin is None or not admin.is_active:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Admin not found or deactivated",
|
|
)
|
|
|
|
return {
|
|
"admin_id": admin.id,
|
|
"telegram_id": admin.telegram_id,
|
|
"role": admin.role.value,
|
|
"username": admin.username,
|
|
}
|
|
|
|
|
|
def require_admin(payload: dict = Depends(get_current_admin)) -> dict:
|
|
return payload
|
|
|
|
|
|
def require_superadmin(payload: dict = Depends(get_current_admin)) -> dict:
|
|
if payload["role"] != "superadmin":
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Superadmin role required",
|
|
)
|
|
return payload
|