277 lines
10 KiB
Python
277 lines
10 KiB
Python
from datetime import UTC, datetime
|
||
|
||
import httpx
|
||
from fastapi import APIRouter, Depends, HTTPException
|
||
from sqlalchemy import select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.api.deps import get_current_telegram_user, log_audit, require_platform_role
|
||
from app.core.config import settings
|
||
from app.db.session import get_session
|
||
from app.models.car import (
|
||
AuditLog,
|
||
ServiceCenter,
|
||
ServiceCenterVerification,
|
||
ServiceEmployee,
|
||
ServiceVisit,
|
||
)
|
||
from app.models.user import User
|
||
from app.schemas.service_center import AdminModerationDecision, ServiceCenterRead, ServiceVisitRead
|
||
|
||
router = APIRouter(prefix="/admin", tags=["admin"])
|
||
|
||
|
||
def require_admin_or_verifier(user: User) -> None:
|
||
require_platform_role(user, {"admin", "verifier", "moderator"})
|
||
|
||
|
||
@router.get("/service-centers/pending", response_model=list[ServiceCenterRead])
|
||
async def pending_service_centers(
|
||
session: AsyncSession = Depends(get_session),
|
||
current_user: User = Depends(get_current_telegram_user),
|
||
) -> list[ServiceCenter]:
|
||
require_admin_or_verifier(current_user)
|
||
result = await session.execute(
|
||
select(ServiceCenter)
|
||
.where(ServiceCenter.verification_status == "pending")
|
||
.order_by(ServiceCenter.created_at.asc())
|
||
)
|
||
return list(result.scalars())
|
||
|
||
|
||
@router.get("/service-centers/{service_center_id}", response_model=ServiceCenterRead)
|
||
async def admin_service_center_detail(
|
||
service_center_id: int,
|
||
session: AsyncSession = Depends(get_session),
|
||
current_user: User = Depends(get_current_telegram_user),
|
||
) -> ServiceCenter:
|
||
require_admin_or_verifier(current_user)
|
||
center = await session.get(ServiceCenter, service_center_id)
|
||
if center is None:
|
||
raise HTTPException(status_code=404, detail="Service center not found")
|
||
return center
|
||
|
||
|
||
@router.post("/service-centers/{service_center_id}/verify", response_model=ServiceCenterRead)
|
||
async def verify_service_center(
|
||
service_center_id: int,
|
||
payload: AdminModerationDecision | None = None,
|
||
session: AsyncSession = Depends(get_session),
|
||
current_user: User = Depends(get_current_telegram_user),
|
||
) -> ServiceCenter:
|
||
require_admin_or_verifier(current_user)
|
||
center = await session.get(ServiceCenter, service_center_id)
|
||
if center is None:
|
||
raise HTTPException(status_code=404, detail="Service center not found")
|
||
center.verification_status = "approved"
|
||
center.verified_at = datetime.now(UTC)
|
||
if center.owner_user_id:
|
||
owner = await session.get(User, center.owner_user_id)
|
||
if owner:
|
||
owner.platform_role = "service_owner"
|
||
await ensure_owner_employee(session, center.id, owner.id)
|
||
await notify_user(owner, f"Заявка СТО «{center.display_name or center.name}» одобрена. Панель СТО доступна в CarPass.")
|
||
await mark_latest_verification(session, center.id, "approved", current_user.id, payload)
|
||
await log_audit(
|
||
session,
|
||
actor=current_user,
|
||
action="service_center.verify",
|
||
target_type="service_center",
|
||
target_id=center.id,
|
||
metadata={"comment": payload.comment if payload else None},
|
||
)
|
||
await session.commit()
|
||
await session.refresh(center)
|
||
return center
|
||
|
||
|
||
@router.post("/service-centers/{service_center_id}/reject", response_model=ServiceCenterRead)
|
||
async def reject_service_center(
|
||
service_center_id: int,
|
||
payload: AdminModerationDecision | None = None,
|
||
session: AsyncSession = Depends(get_session),
|
||
current_user: User = Depends(get_current_telegram_user),
|
||
) -> ServiceCenter:
|
||
require_admin_or_verifier(current_user)
|
||
center = await session.get(ServiceCenter, service_center_id)
|
||
if center is None:
|
||
raise HTTPException(status_code=404, detail="Service center not found")
|
||
center.verification_status = "rejected"
|
||
if center.owner_user_id:
|
||
owner = await session.get(User, center.owner_user_id)
|
||
if owner:
|
||
reason = payload.reason or payload.comment if payload else None
|
||
await notify_user(owner, f"Заявка СТО «{center.display_name or center.name}» отклонена.{f' Причина: {reason}' if reason else ''}")
|
||
await mark_latest_verification(session, center.id, "rejected", current_user.id, payload)
|
||
await log_audit(
|
||
session,
|
||
actor=current_user,
|
||
action="service_center.reject",
|
||
target_type="service_center",
|
||
target_id=center.id,
|
||
metadata={"reason": payload.reason if payload else None, "comment": payload.comment if payload else None},
|
||
)
|
||
await session.commit()
|
||
await session.refresh(center)
|
||
return center
|
||
|
||
|
||
@router.post("/service-centers/{service_center_id}/suspend", response_model=ServiceCenterRead)
|
||
async def suspend_service_center(
|
||
service_center_id: int,
|
||
payload: AdminModerationDecision | None = None,
|
||
session: AsyncSession = Depends(get_session),
|
||
current_user: User = Depends(get_current_telegram_user),
|
||
) -> ServiceCenter:
|
||
require_platform_role(current_user, {"admin"})
|
||
center = await session.get(ServiceCenter, service_center_id)
|
||
if center is None:
|
||
raise HTTPException(status_code=404, detail="Service center not found")
|
||
center.verification_status = "suspended"
|
||
center.suspended_at = datetime.now(UTC)
|
||
if center.owner_user_id:
|
||
owner = await session.get(User, center.owner_user_id)
|
||
if owner:
|
||
reason = payload.reason or payload.comment if payload else None
|
||
await notify_user(owner, f"СТО «{center.display_name or center.name}» временно заблокировано.{f' Причина: {reason}' if reason else ''}")
|
||
await log_audit(
|
||
session,
|
||
actor=current_user,
|
||
action="service_center.suspend",
|
||
target_type="service_center",
|
||
target_id=center.id,
|
||
metadata={"reason": payload.reason if payload else None, "comment": payload.comment if payload else None},
|
||
)
|
||
await session.commit()
|
||
await session.refresh(center)
|
||
return center
|
||
|
||
|
||
@router.post("/service-centers/{service_center_id}/request-changes", response_model=ServiceCenterRead)
|
||
async def request_service_center_changes(
|
||
service_center_id: int,
|
||
payload: AdminModerationDecision,
|
||
session: AsyncSession = Depends(get_session),
|
||
current_user: User = Depends(get_current_telegram_user),
|
||
) -> ServiceCenter:
|
||
require_admin_or_verifier(current_user)
|
||
center = await session.get(ServiceCenter, service_center_id)
|
||
if center is None:
|
||
raise HTTPException(status_code=404, detail="Service center not found")
|
||
center.verification_status = "needs_changes"
|
||
if center.owner_user_id:
|
||
owner = await session.get(User, center.owner_user_id)
|
||
if owner:
|
||
reason = payload.reason or payload.comment or "Администратор попросил уточнить данные заявки."
|
||
await notify_user(owner, f"По заявке СТО «{center.display_name or center.name}» нужны правки: {reason}")
|
||
await mark_latest_verification(session, center.id, "needs_changes", current_user.id, payload)
|
||
await log_audit(
|
||
session,
|
||
actor=current_user,
|
||
action="service_center.request_changes",
|
||
target_type="service_center",
|
||
target_id=center.id,
|
||
metadata={"reason": payload.reason, "comment": payload.comment},
|
||
)
|
||
await session.commit()
|
||
await session.refresh(center)
|
||
return center
|
||
|
||
|
||
@router.get("/audit-log")
|
||
async def audit_log(
|
||
limit: int = 100,
|
||
offset: int = 0,
|
||
session: AsyncSession = Depends(get_session),
|
||
current_user: User = Depends(get_current_telegram_user),
|
||
) -> list[dict]:
|
||
require_platform_role(current_user, {"admin", "verifier", "moderator"})
|
||
limit = min(max(limit, 1), 200)
|
||
result = await session.execute(
|
||
select(AuditLog).order_by(AuditLog.created_at.desc()).limit(limit).offset(max(offset, 0))
|
||
)
|
||
return [
|
||
{
|
||
"id": item.id,
|
||
"actor_user_id": item.actor_user_id,
|
||
"actor_role": item.actor_role,
|
||
"action": item.action,
|
||
"target_type": item.target_type,
|
||
"target_id": item.target_id,
|
||
"metadata_json": item.metadata_json,
|
||
"created_at": item.created_at,
|
||
}
|
||
for item in result.scalars()
|
||
]
|
||
|
||
|
||
@router.get("/disputes", response_model=list[ServiceVisitRead])
|
||
async def disputes(
|
||
session: AsyncSession = Depends(get_session),
|
||
current_user: User = Depends(get_current_telegram_user),
|
||
) -> list[ServiceVisit]:
|
||
require_admin_or_verifier(current_user)
|
||
result = await session.execute(
|
||
select(ServiceVisit).where(ServiceVisit.status == "disputed").order_by(ServiceVisit.updated_at.desc())
|
||
)
|
||
return list(result.scalars())
|
||
|
||
|
||
async def mark_latest_verification(
|
||
session: AsyncSession,
|
||
service_center_id: int,
|
||
status: str,
|
||
reviewed_by: int,
|
||
payload: AdminModerationDecision | None = None,
|
||
) -> None:
|
||
result = await session.execute(
|
||
select(ServiceCenterVerification)
|
||
.where(ServiceCenterVerification.service_center_id == service_center_id)
|
||
.order_by(ServiceCenterVerification.created_at.desc())
|
||
.limit(1)
|
||
)
|
||
verification = result.scalar_one_or_none()
|
||
if verification:
|
||
verification.status = status
|
||
verification.reviewed_by = reviewed_by
|
||
verification.reviewed_at = datetime.now(UTC)
|
||
if payload and (payload.reason or payload.comment):
|
||
verification.comment = "\n".join(
|
||
item for item in [payload.reason, payload.comment] if item
|
||
)
|
||
|
||
|
||
async def ensure_owner_employee(session: AsyncSession, service_center_id: int, owner_user_id: int) -> None:
|
||
result = await session.execute(
|
||
select(ServiceEmployee).where(
|
||
ServiceEmployee.service_center_id == service_center_id,
|
||
ServiceEmployee.user_id == owner_user_id,
|
||
)
|
||
)
|
||
employee = result.scalar_one_or_none()
|
||
if employee is None:
|
||
session.add(
|
||
ServiceEmployee(
|
||
service_center_id=service_center_id,
|
||
user_id=owner_user_id,
|
||
role="owner",
|
||
status="active",
|
||
)
|
||
)
|
||
else:
|
||
employee.role = "owner"
|
||
employee.status = "active"
|
||
|
||
|
||
async def notify_user(user: User, text: str) -> None:
|
||
if not settings.bot_token:
|
||
return
|
||
try:
|
||
async with httpx.AsyncClient(timeout=5) as client:
|
||
await client.post(
|
||
f"https://api.telegram.org/bot{settings.bot_token}/sendMessage",
|
||
data={"chat_id": str(user.telegram_id), "text": text},
|
||
)
|
||
except Exception:
|
||
return
|