Files
marquiz-metrics/app/api/v1/endpoints/counters.py

174 lines
7.5 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from typing import Annotated, Dict, List
import httpx
from loguru import logger
from app.core.config import settings
from app.api.v1.schemas.counter import CounterListResponse, Counter, CounterCreateRequest, CounterCreateResponse
from app.api.v1.schemas.goal import GoalCreateRequest, GoalCreateResponse, Goal
router = APIRouter()
bearer_scheme = HTTPBearer()
# ФИНАЛЬНАЯ ВЕРСИЯ: Ключи используют правильное написание "Marquiz" (в нижнем регистре для сравнения).
PREDEFINED_GOALS: Dict[str, str] = {
"marquiz-start": "Посетитель открыл квиз",
"marquiz-startquiz": "Посетитель нажал на кнопку стартовой страницы",
"marquiz-form": "Посетитель дошёл до формы контактов",
"marquiz-result": "Посетитель увидел результат",
"marquiz-contacts1": "Посетитель заполнил и отправил форму контактов",
}
# --- КОД ЭНДПОИНТОВ get_counters и create_counter ОСТАЕТСЯ БЕЗ ИЗМЕНЕНИЙ ---
@router.get("/", response_model=CounterListResponse, summary="Получение списка счетчиков")
async def get_counters(credentials: Annotated[HTTPAuthorizationCredentials, Depends(bearer_scheme)]):
token = credentials.credentials
logger.info("Fetching list of counters from Yandex.Metrika API.")
url = f"{settings.YANDEX_METRIKA_API_URL}/management/v1/counters"
headers = {'Authorization': f'OAuth {token}', 'Content-Type': 'application/json'}
try:
async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers)
response.raise_for_status()
data = response.json()
counters_list = [
Counter(id=c['id'], name=c['name'], site=c['site'])
for c in data.get('counters', [])
]
logger.success(f"Successfully fetched {len(counters_list)} counters.")
return CounterListResponse(counters=counters_list)
except httpx.HTTPStatusError as e:
error_details = e.response.json()
logger.error(f"Yandex Metrika API error: {e.response.status_code} - {error_details}")
raise HTTPException(
status_code=e.response.status_code,
detail=f"Yandex API Error: {error_details.get('message', 'Unknown error')}"
)
except Exception as e:
logger.opt(exception=True).error("An unexpected error occurred while fetching counters.")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="An internal server error occurred."
)
@router.post(
"/",
response_model=CounterCreateResponse,
status_code=status.HTTP_201_CREATED,
summary="Создание нового счетчика"
)
async def create_counter(
request_body: CounterCreateRequest,
credentials: Annotated[HTTPAuthorizationCredentials, Depends(bearer_scheme)]
):
token = credentials.credentials
site_url = str(request_body.site_url).strip('/')
site_name = site_url.replace("https://", "").replace("http://", "")
logger.info(f"Attempting to create a new counter for site: {site_name}")
url = f"{settings.YANDEX_METRIKA_API_URL}/management/v1/counters"
headers = {'Authorization': f'OAuth {token}', 'Content-Type': 'application/json'}
payload = {
"counter": {
"name": site_name,
"site": site_name
}
}
try:
async with httpx.AsyncClient() as client:
response = await client.post(url, headers=headers, json=payload)
response.raise_for_status()
created_counter_data = response.json().get("counter", {})
logger.success(f"Successfully created counter ID: {created_counter_data.get('id')}")
return CounterCreateResponse(
id=created_counter_data.get('id'),
name=created_counter_data.get('name'),
code_status=created_counter_data.get('code_status')
)
except httpx.HTTPStatusError as e:
error_details = e.response.json()
logger.error(f"Yandex Metrika API error during counter creation: {e.response.status_code} - {error_details}")
raise HTTPException(
status_code=e.response.status_code,
detail=f"Yandex API Error: {error_details.get('message', 'Unknown error')}"
)
except Exception as e:
logger.opt(exception=True).error("An unexpected error occurred during counter creation.")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="An internal server error occurred."
)
@router.post(
"/{counter_id}/goals",
response_model=GoalCreateResponse,
status_code=status.HTTP_201_CREATED,
summary="Создание стандартного набора целей в счетчике"
)
async def create_goals_for_counter(
counter_id: int,
request_body: GoalCreateRequest,
credentials: Annotated[HTTPAuthorizationCredentials, Depends(bearer_scheme)]
):
token = credentials.credentials
logger.info(f"Attempting to create goals for counter ID: {counter_id}")
url = f"{settings.YANDEX_METRIKA_API_URL}/management/v1/counter/{counter_id}/goals"
headers = {'Authorization': f'OAuth {token}', 'Content-Type': 'application/json'}
created_goals_list: List[Goal] = []
async with httpx.AsyncClient() as client:
for identifier in request_body.goal_identifiers:
# Приводим входящий идентификатор к нижнему регистру для надежного поиска
goal_name = PREDEFINED_GOALS.get(identifier.lower())
if not goal_name:
logger.warning(f"Unknown goal identifier '{identifier}' skipped.")
continue
goal_payload = {
"name": goal_name,
"type": "action",
"conditions": [{"type": "exact", "url": identifier}]
}
payload = {"goal": goal_payload}
logger.debug(f"Sending payload for goal '{goal_name}': {payload}")
try:
response = await client.post(url, headers=headers, json=payload)
response.raise_for_status()
created_goal_data = response.json().get("goal", {})
if created_goal_data:
logger.success(f"Successfully created goal: {created_goal_data.get('name')} (ID: {created_goal_data.get('id')})")
created_goals_list.append(
Goal(id=created_goal_data['id'], name=created_goal_data['name'])
)
except httpx.HTTPStatusError as e:
error_details = e.response.json()
logger.error(f"Failed to create goal '{goal_name}'. Reason: {error_details.get('message', 'Unknown error')}")
except Exception:
logger.opt(exception=True).error(f"An unexpected error occurred while creating goal '{goal_name}'.")
if not created_goals_list:
logger.warning("No goals were created.")
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Could not create any of the requested goals. Check logs for details."
)
return GoalCreateResponse(created_goals=created_goals_list)