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