Files
MessageGateway/app/api/v1/endpoints/groups.py
Sergey Antropov b90def35ed Initial commit: Message Gateway project
- FastAPI приложение для отправки мониторинговых алертов в мессенджеры
- Поддержка Telegram и MAX/VK
- Интеграция с Grafana, Zabbix, AlertManager
- Автоматическое создание тикетов в Jira
- Управление группами мессенджеров через API
- Декораторы для авторизации и скрытия эндпоинтов
- Подробная документация в папке docs/

Автор: Сергей Антропов
Сайт: https://devops.org.ru
2025-11-12 20:25:11 +03:00

512 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Эндпоинты для управления группами мессенджеров.
Автор: Сергей Антропов
Сайт: 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)}")