From 85b46a94b99e7e31f15d13b9a977aeb9cb93c806 Mon Sep 17 00:00:00 2001 From: VPN SaaS Dev Date: Fri, 15 May 2026 04:45:54 +0900 Subject: [PATCH] Notify moderators about service center applications --- app/api/admin.py | 16 +--------------- app/api/service_centers.py | 13 ++++++++++++- app/services/notifications.py | 27 +++++++++++++++++++++++++++ tests/test_product_readiness.py | 26 +++++++++++++++++++++++++- 4 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 app/services/notifications.py diff --git a/app/api/admin.py b/app/api/admin.py index a3e0c3a..8bb15ba 100644 --- a/app/api/admin.py +++ b/app/api/admin.py @@ -1,12 +1,10 @@ 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, @@ -17,6 +15,7 @@ from app.models.car import ( ) from app.models.user import User from app.schemas.service_center import AdminModerationDecision, ServiceCenterRead, ServiceVisitRead +from app.services.notifications import notify_user router = APIRouter(prefix="/admin", tags=["admin"]) @@ -261,16 +260,3 @@ async def ensure_owner_employee(session: AsyncSession, service_center_id: int, o 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 diff --git a/app/api/service_centers.py b/app/api/service_centers.py index 2c4ff4a..f36f0ef 100644 --- a/app/api/service_centers.py +++ b/app/api/service_centers.py @@ -46,6 +46,7 @@ from app.schemas.service_center import ( VehicleSearchRequest, VehicleSearchResult, ) +from app.services.notifications import notify_platform_moderators from app.services.odometer import validate_odometer_change from app.services.vehicle_identity import mask_license_plate, mask_vin @@ -101,9 +102,19 @@ async def create_service_center( status="active", ) session.add(employee) - await log_audit(session, actor=current_user, action="service_center.create", target_type="service_center", target_id=center.id) + await log_audit( + session, + actor=current_user, + action="service_center.create", + target_type="service_center", + target_id=center.id, + ) await session.commit() await session.refresh(center) + await notify_platform_moderators( + session, + f"Новая заявка СТО #{center.id}: {center.display_name or center.name}. Откройте /admin_sto_pending для модерации.", + ) return center diff --git a/app/services/notifications.py b/app/services/notifications.py new file mode 100644 index 0000000..1d1caec --- /dev/null +++ b/app/services/notifications.py @@ -0,0 +1,27 @@ +import httpx +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession + +from app.core.config import settings +from app.models.user import User + +MODERATOR_ROLES = {"admin", "verifier", "moderator"} + + +async def notify_user(user: User, text: str) -> None: + if not settings.bot_token or settings.app_env == "test": + 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 + + +async def notify_platform_moderators(session: AsyncSession, text: str) -> None: + result = await session.execute(select(User).where(User.platform_role.in_(MODERATOR_ROLES))) + for user in result.scalars(): + await notify_user(user, text) diff --git a/tests/test_product_readiness.py b/tests/test_product_readiness.py index 207da6d..5789d22 100644 --- a/tests/test_product_readiness.py +++ b/tests/test_product_readiness.py @@ -14,11 +14,12 @@ async def test_license_plate_can_be_saved_and_edited(client, auth_headers) -> No updated = await client.patch( f"/api/cars/{car['id']}", headers=auth_headers, - json={"plate_number": "34 나 7890"}, + json={"plate_number": "34 나 7890", "body_type": "SUV"}, ) assert updated.status_code == 200 assert updated.json()["plate_number"] == "34 나 7890" + assert updated.json()["body_type"] == "SUV" @pytest.mark.asyncio @@ -312,3 +313,26 @@ async def test_admin_request_changes_keeps_application_visible_to_moderation( assert response.status_code == 200 assert response.json()["verification_status"] == "needs_changes" + + +@pytest.mark.asyncio +async def test_service_center_application_is_visible_to_moderators( + client, auth_headers, admin_auth_headers, internal_headers +) -> None: + center = ( + await client.post( + "/api/service-centers", + headers=auth_headers, + json={"display_name": "Pending Review Service", "country": "KR"}, + ) + ).json() + await client.post( + "/api/users", + headers=internal_headers, + json={"telegram_id": 9001, "platform_role": "moderator"}, + ) + + pending = await client.get("/api/admin/service-centers/pending", headers=admin_auth_headers) + + assert pending.status_code == 200 + assert center["id"] in [item["id"] for item in pending.json()]