- FastAPI приложение для отправки мониторинговых алертов в мессенджеры - Поддержка Telegram и MAX/VK - Интеграция с Grafana, Zabbix, AlertManager - Автоматическое создание тикетов в Jira - Управление группами мессенджеров через API - Декораторы для авторизации и скрытия эндпоинтов - Подробная документация в папке docs/ Автор: Сергей Антропов Сайт: https://devops.org.ru
512 lines
17 KiB
Python
512 lines
17 KiB
Python
"""
|
||
Эндпоинты для управления группами мессенджеров.
|
||
|
||
Автор: Сергей Антропов
|
||
Сайт: https://devops.org.ru
|
||
"""
|
||
import logging
|
||
from typing import Dict, Any, Optional
|
||
from fastapi import APIRouter, HTTPException, Query, Path, Body, Request
|
||
|
||
from app.core.metrics import metrics
|
||
from app.core.groups import groups_config
|
||
from app.core.auth import require_api_key, require_api_key_dependency, require_api_key_optional
|
||
from app.models.group import (
|
||
CreateGroupRequest,
|
||
UpdateGroupRequest,
|
||
DeleteGroupRequest,
|
||
GroupInfo
|
||
)
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
router = APIRouter(prefix="/groups", tags=["groups"])
|
||
|
||
|
||
@router.get(
|
||
"/messengers",
|
||
name="Получить список поддерживаемых мессенджеров",
|
||
response_model=Dict[str, Any],
|
||
responses={
|
||
200: {
|
||
"description": "Список поддерживаемых мессенджеров",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"status": "ok",
|
||
"messengers": [
|
||
{
|
||
"type": "telegram",
|
||
"name": "Telegram",
|
||
"supports_threads": True,
|
||
"enabled": True
|
||
},
|
||
{
|
||
"type": "max",
|
||
"name": "MAX/VK",
|
||
"supports_threads": False,
|
||
"enabled": False
|
||
}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
)
|
||
async def get_messengers() -> Dict[str, Any]:
|
||
"""
|
||
Получить список поддерживаемых мессенджеров.
|
||
|
||
Returns:
|
||
Список поддерживаемых мессенджеров с их характеристиками.
|
||
|
||
Подробная документация: см. docs/api/groups.md
|
||
"""
|
||
from app.core.config import get_settings
|
||
settings = get_settings()
|
||
|
||
messengers = [
|
||
{
|
||
"type": "telegram",
|
||
"name": "Telegram",
|
||
"supports_threads": True,
|
||
"enabled": settings.telegram_enabled
|
||
},
|
||
{
|
||
"type": "max",
|
||
"name": "MAX/VK",
|
||
"supports_threads": False,
|
||
"enabled": settings.max_enabled
|
||
}
|
||
]
|
||
|
||
return {
|
||
"status": "ok",
|
||
"messengers": messengers
|
||
}
|
||
|
||
|
||
@router.get(
|
||
"",
|
||
name="Получить список групп",
|
||
response_model=Dict[str, Any],
|
||
responses={
|
||
200: {
|
||
"description": "Список групп успешно получен",
|
||
"content": {
|
||
"application/json": {
|
||
"examples": {
|
||
"without_api_key": {
|
||
"summary": "Без API ключа (только названия)",
|
||
"value": {
|
||
"status": "ok",
|
||
"groups": [
|
||
{"name": "monitoring", "chat_id": None},
|
||
{"name": "alerts", "chat_id": None}
|
||
],
|
||
"count": 2
|
||
}
|
||
},
|
||
"with_api_key": {
|
||
"summary": "С API ключом (полная информация)",
|
||
"value": {
|
||
"status": "ok",
|
||
"groups": [
|
||
{
|
||
"name": "monitoring",
|
||
"messenger": "telegram",
|
||
"chat_id": -1001234567890,
|
||
"thread_id": 0
|
||
},
|
||
{
|
||
"name": "alerts_max",
|
||
"messenger": "max",
|
||
"chat_id": "123456789",
|
||
"thread_id": None
|
||
}
|
||
],
|
||
"count": 2
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
401: {
|
||
"description": "Неверный API ключ",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Неверный или отсутствующий API ключ"}
|
||
}
|
||
}
|
||
},
|
||
500: {
|
||
"description": "Ошибка сервера",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Ошибка получения списка групп"}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
)
|
||
async def get_groups(
|
||
request: Request,
|
||
api_key_header: Optional[bool] = require_api_key_optional
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
Получить список всех групп.
|
||
|
||
Без API ключа: возвращает только названия групп без ID.
|
||
С API ключом (заголовок X-API-Key): возвращает полную информацию о группах включая ID.
|
||
|
||
Подробная документация: см. docs/api/groups.md
|
||
"""
|
||
metrics.increment_api_endpoint("groups_list")
|
||
|
||
# Если API ключ валиден, возвращаем полную информацию
|
||
include_ids = api_key_header is True
|
||
|
||
# Получаем группы
|
||
try:
|
||
groups_dict = await groups_config.get_all_groups(include_ids=include_ids)
|
||
|
||
# Формируем список групп
|
||
groups = []
|
||
for name, group_config in groups_dict.items():
|
||
if include_ids:
|
||
# Возвращаем полную информацию о группе
|
||
if isinstance(group_config, dict) and group_config is not None:
|
||
groups.append(GroupInfo(
|
||
name=name,
|
||
messenger=group_config.get("messenger"),
|
||
chat_id=group_config.get("chat_id"),
|
||
thread_id=group_config.get("thread_id")
|
||
))
|
||
else:
|
||
# Если group_config is None, значит include_ids=False, но мы здесь не должны быть
|
||
groups.append(GroupInfo(
|
||
name=name,
|
||
messenger=None,
|
||
chat_id=None,
|
||
thread_id=None
|
||
))
|
||
else:
|
||
# Возвращаем только название группы (group_config будет None)
|
||
groups.append(GroupInfo(
|
||
name=name,
|
||
messenger=None,
|
||
chat_id=None,
|
||
thread_id=None
|
||
))
|
||
|
||
return {
|
||
"status": "ok",
|
||
"groups": [group.model_dump() for group in groups],
|
||
"count": len(groups)
|
||
}
|
||
except Exception as e:
|
||
logger.error(f"Ошибка получения списка групп: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Ошибка получения списка групп: {str(e)}")
|
||
|
||
|
||
@require_api_key
|
||
@router.post(
|
||
"",
|
||
name="Создать группу",
|
||
response_model=Dict[str, Any],
|
||
dependencies=[require_api_key_dependency],
|
||
responses={
|
||
200: {
|
||
"description": "Группа успешно создана",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"status": "ok",
|
||
"message": "Группа 'monitoring' создана с ID -1001234567890"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
400: {
|
||
"description": "Ошибка запроса (группа уже существует или неверные данные)",
|
||
"content": {
|
||
"application/json": {
|
||
"examples": {
|
||
"group_exists": {
|
||
"summary": "Группа уже существует",
|
||
"value": {"detail": "Группа 'monitoring' уже существует"}
|
||
},
|
||
"invalid_data": {
|
||
"summary": "Неверные данные",
|
||
"value": {"detail": "Неверный формат данных"}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
401: {
|
||
"description": "Ошибка авторизации (неверный API ключ)",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Неверный или отсутствующий API ключ"}
|
||
}
|
||
}
|
||
},
|
||
500: {
|
||
"description": "Ошибка сервера",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Ошибка создания группы"}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
)
|
||
async def create_group(
|
||
request: Request,
|
||
body: CreateGroupRequest = Body(
|
||
...,
|
||
examples=[
|
||
{
|
||
"group_name": "monitoring",
|
||
"messenger": "telegram",
|
||
"chat_id": -1001234567890,
|
||
"thread_id": 0
|
||
},
|
||
{
|
||
"group_name": "max_alerts",
|
||
"messenger": "max",
|
||
"chat_id": "123456789",
|
||
"thread_id": 0,
|
||
"config": {
|
||
"access_token": "your_max_access_token"
|
||
}
|
||
}
|
||
]
|
||
)
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
Создать новую группу в конфигурации.
|
||
|
||
Требуется API ключ в заголовке X-API-Key.
|
||
|
||
Подробная документация: см. docs/api/groups.md
|
||
"""
|
||
metrics.increment_api_endpoint("groups_create")
|
||
|
||
# Создаем группу
|
||
try:
|
||
success = await groups_config.create_group(
|
||
group_name=body.group_name,
|
||
chat_id=body.chat_id,
|
||
messenger=body.messenger,
|
||
thread_id=body.thread_id,
|
||
config=body.config
|
||
)
|
||
|
||
if not success:
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail=f"Группа '{body.group_name}' уже существует"
|
||
)
|
||
|
||
return {
|
||
"status": "ok",
|
||
"message": f"Группа '{body.group_name}' создана с мессенджером '{body.messenger}' и ID {body.chat_id}"
|
||
}
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.error(f"Ошибка создания группы: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Ошибка создания группы: {str(e)}")
|
||
|
||
|
||
@require_api_key
|
||
@router.put(
|
||
"/{group_name}",
|
||
name="Обновить группу",
|
||
response_model=Dict[str, Any],
|
||
dependencies=[require_api_key_dependency],
|
||
responses={
|
||
200: {
|
||
"description": "Группа успешно обновлена",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"status": "ok",
|
||
"message": "Группа 'monitoring' обновлена с ID -1001234567891"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
400: {
|
||
"description": "Ошибка запроса (группа не найдена или неверные данные)",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Группа 'monitoring' не найдена"}
|
||
}
|
||
}
|
||
},
|
||
401: {
|
||
"description": "Ошибка авторизации (неверный API ключ)",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Неверный или отсутствующий API ключ"}
|
||
}
|
||
}
|
||
},
|
||
500: {
|
||
"description": "Ошибка сервера",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Ошибка обновления группы"}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
)
|
||
async def update_group(
|
||
request: Request,
|
||
group_name: str = Path(
|
||
...,
|
||
description="Имя группы для обновления",
|
||
examples=["monitoring", "alerts", "devops"]
|
||
),
|
||
body: UpdateGroupRequest = Body(
|
||
...,
|
||
examples=[
|
||
{
|
||
"chat_id": -1001234567891,
|
||
"messenger": "telegram",
|
||
"thread_id": 0
|
||
},
|
||
{
|
||
"chat_id": "123456789",
|
||
"messenger": "max",
|
||
"config": {
|
||
"access_token": "your_access_token",
|
||
"api_version": "5.131"
|
||
}
|
||
}
|
||
]
|
||
)
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
Обновить существующую группу в конфигурации.
|
||
|
||
Требуется API ключ в заголовке X-API-Key.
|
||
|
||
Подробная документация: см. docs/api/groups.md
|
||
"""
|
||
metrics.increment_api_endpoint("groups_update")
|
||
|
||
# Обновляем группу
|
||
try:
|
||
success = await groups_config.update_group(
|
||
group_name=group_name,
|
||
chat_id=body.chat_id,
|
||
messenger=body.messenger,
|
||
thread_id=body.thread_id,
|
||
config=body.config
|
||
)
|
||
|
||
if not success:
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail=f"Группа '{group_name}' не найдена"
|
||
)
|
||
|
||
return {
|
||
"status": "ok",
|
||
"message": f"Группа '{group_name}' обновлена"
|
||
}
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.error(f"Ошибка обновления группы: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Ошибка обновления группы: {str(e)}")
|
||
|
||
|
||
@require_api_key
|
||
@router.delete(
|
||
"/{group_name}",
|
||
name="Удалить группу",
|
||
response_model=Dict[str, Any],
|
||
dependencies=[require_api_key_dependency],
|
||
responses={
|
||
200: {
|
||
"description": "Группа успешно удалена",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"status": "ok",
|
||
"message": "Группа 'monitoring' удалена"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
400: {
|
||
"description": "Ошибка запроса (группа не найдена)",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Группа 'monitoring' не найдена"}
|
||
}
|
||
}
|
||
},
|
||
401: {
|
||
"description": "Ошибка авторизации (неверный API ключ)",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Неверный или отсутствующий API ключ"}
|
||
}
|
||
}
|
||
},
|
||
500: {
|
||
"description": "Ошибка сервера",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {"detail": "Ошибка удаления группы"}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
)
|
||
async def delete_group(
|
||
request: Request,
|
||
group_name: str = Path(
|
||
...,
|
||
description="Имя группы для удаления",
|
||
examples=["monitoring", "alerts", "devops"]
|
||
)
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
Удалить группу из конфигурации.
|
||
|
||
Требуется API ключ в заголовке X-API-Key.
|
||
|
||
Подробная документация: см. docs/api/groups.md
|
||
"""
|
||
metrics.increment_api_endpoint("groups_delete")
|
||
|
||
# Удаляем группу
|
||
try:
|
||
success = await groups_config.delete_group(group_name)
|
||
|
||
if not success:
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail=f"Группа '{group_name}' не найдена"
|
||
)
|
||
|
||
return {
|
||
"status": "ok",
|
||
"message": f"Группа '{group_name}' удалена"
|
||
}
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.error(f"Ошибка удаления группы: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Ошибка удаления группы: {str(e)}")
|