from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from typing import Annotated, 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 GoalListResponse, Goal, GoalCreateRequest, GoalCreateResponse, CreatedGoal router = APIRouter() bearer_scheme = HTTPBearer() # --- Эндпоинт для получения списка счетчиков --- @router.get("/", response_model=CounterListResponse, summary="Получение списка счетчиков") async def get_counters(credentials: Annotated[HTTPAuthorizationCredentials, Depends(bearer_scheme)]): """ Получает список счетчиков Яндекс.Метрики, доступных для данного токена. Токен должен быть передан в заголовке 'Authorization: Bearer '. """ 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)] ): """ Создает новый счетчик Яндекс.Метрики с настройками по умолчанию. Требует 'Authorization: Bearer ' заголовок. """ 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(**created_counter_data) 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.get( "/{counter_id}/goals", response_model=GoalListResponse, summary="Получение списка целей для счетчика" ) async def get_goals_for_counter( counter_id: int, credentials: Annotated[HTTPAuthorizationCredentials, Depends(bearer_scheme)] ): """ Получает список всех целей для указанного счетчика. """ token = credentials.credentials logger.info(f"Fetching goals for counter ID: {counter_id}") url = f"{settings.YANDEX_METRIKA_API_URL}/management/v1/counter/{counter_id}/goals" headers = {'Authorization': f'OAuth {token}'} try: async with httpx.AsyncClient() as client: response = await client.get(url, headers=headers) response.raise_for_status() data = response.json() # Pydantic автоматически отфильтрует только нужные нам поля (id, name, type) goals_list = [Goal(**g) for g in data.get('goals', [])] logger.success(f"Successfully fetched {len(goals_list)} goals for counter {counter_id}.") return GoalListResponse(goals=goals_list) except httpx.HTTPStatusError as e: error_details = e.response.json() logger.error(f"Yandex Metrika API error during goals fetching: {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 goals.") 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_custom_goals_for_counter( counter_id: int, request_body: GoalCreateRequest, credentials: Annotated[HTTPAuthorizationCredentials, Depends(bearer_scheme)] ): """ Создает в указанном счетчике цели типа 'javascript-событие' на основе переданного списка объектов (identifier, name). """ token = credentials.credentials logger.info(f"Attempting to create {len(request_body.goals)} custom 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[CreatedGoal] = [] async with httpx.AsyncClient() as client: # Итерируемся по списку целей из запроса for goal_in in request_body.goals: goal_payload = { "name": goal_in.name, "type": "action", # Создаем только цели типа "JavaScript-событие" "conditions": [{"type": "exact", "url": goal_in.identifier}] } payload = {"goal": goal_payload} logger.debug(f"Sending payload for goal '{goal_in.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(CreatedGoal(**created_goal_data)) except httpx.HTTPStatusError as e: error_details = e.response.json() logger.error(f"Failed to create goal '{goal_in.name}'. Reason: {error_details.get('message', 'Unknown error')}") except Exception as e: logger.opt(exception=True).error(f"An unexpected error occurred while creating goal '{goal_in.name}'.") if not created_goals_list: 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) # --- НОВЫЙ ЭНДПОИНТ: Удаление цели --- @router.delete( "/{counter_id}/goals/{goal_id}", status_code=status.HTTP_204_NO_CONTENT, summary="Удаление цели в счетчике" ) async def delete_goal_in_counter( counter_id: int, goal_id: int, credentials: Annotated[HTTPAuthorizationCredentials, Depends(bearer_scheme)] ): """ Удаляет указанную цель в указанном счетчике. В случае успеха возвращает пустой ответ со статусом 204. """ token = credentials.credentials logger.info(f"Attempting to delete goal ID: {goal_id} from counter ID: {counter_id}") url = f"{settings.YANDEX_METRIKA_API_URL}/management/v1/counter/{counter_id}/goal/{goal_id}" headers = {'Authorization': f'OAuth {token}'} try: async with httpx.AsyncClient() as client: response = await client.delete(url, headers=headers) response.raise_for_status() logger.success(f"Successfully deleted goal ID: {goal_id} from counter {counter_id}.") # При успехе стандартно возвращается пустой ответ return except httpx.HTTPStatusError as e: error_details = e.response.json() logger.error(f"Yandex Metrika API error during goal deletion: {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 goal deletion.") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="An internal server error occurred." )