Files
TIP/frontend/src/api.py
T
2026-06-09 21:18:13 -03:00

439 lines
15 KiB
Python

# src/api.py
import requests
import streamlit as st
from typing import List, Optional, Dict, Any
import os
class APIClient:
"""Cliente simple para interactuar con la API"""
def __init__(self, user_url: str, feeder_url: str, backup_url: str):
self.user_url = user_url
self.feeder_url = feeder_url
self.backup_url = backup_url
def _get_headers(self) -> Optional[Dict[str, str]]:
if "token" not in st.session_state:
return None
return {
"Authorization": f"Bearer {st.session_state['token']}",
"accept": "application/json",
"Content-Type": "application/json"
}
def _request(self, method: str, url: str, **kwargs) -> Optional[Any]:
"""Realiza petición HTTP con manejo común de errores y headers.
Args:
method: 'get', 'post', 'put', 'delete'
url: URL completa
**kwargs: parámetros (json, params, etc.) para requests
"""
headers = self._get_headers()
if headers is None:
return None
try:
func = getattr(requests, method)
response = func(url, headers=headers, timeout=10, **kwargs)
if response.status_code in (200, 201, 204):
if response.content:
return response.json()
return {}
return None
except Exception as e:
st.error(f"Error en {method.upper()} {url}: {str(e)}")
return None
## Verificaciones ##
def has_connection(self):
"""Verifica la conexión con la API de Telegram (Para funcionamiento del feeder)"""
try:
response = requests.get(f"{self.feeder_url}/manage/connection-status", timeout=5)
return response.status_code == 200
except Exception:
return False
## USERS ##
def login(self, email: str, password: str) -> Optional[Dict[str, Any]]:
"""Realiza login en la API"""
try:
response = requests.post(
f"{self.user_url}/api/v1/login",
json={"email": email, "password": password},
timeout=10
)
return response.json() if response.status_code == 200 else None
except Exception:
return None
def get_pending_users(self, skip: int = 0, limit: int = 100) -> Optional[List[Dict[str, Any]]]:
return self._request(
"get",
f"{self.user_url}/api/v1/users/pending",
params={"skip": skip, "limit": limit}
)
def activate_user(self, user_id: int) -> Optional[Dict[str, Any]]:
return self._request(
"post",
f"{self.user_url}/api/v1/users/{user_id}/activate"
)
def delete_user(self, user_id: int) -> Optional[Dict[str, Any]]:
return self._request(
"delete",
f"{self.user_url}/api/v1/users/{user_id}"
)
def update_user(self, user_id: int, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
return self._request(
"put",
f"{self.user_url}/api/v1/users/{user_id}",
json=payload
)
def get_user(self, user_id: int) -> Optional[Dict[str, Any]]:
return self._request(
"get",
f"{self.user_url}/api/v1/users/{user_id}"
)
def search_users(self, q: str = "", skip: int = 0, limit: int = 50) -> Optional[List[Dict[str, Any]]]:
return self._request(
"get",
f"{self.user_url}/api/v1/users",
params={"q": q, "skip": skip, "limit": limit}
)
def search_messages(self, q: str = "", group_id=None, date_from=None, date_to=None, skip: int = 0, limit: int = 100):
if not q.strip() and not group_id and not date_from and not date_to:
return self._request("get", f"{self.feeder_url}/messages/",
params={"skip": skip, "limit": limit})
params = {"q": q, "skip": skip, "limit": limit}
if group_id:
params["group_id"] = group_id
if date_from:
params["date_from"] = date_from
if date_to:
params["date_to"] = date_to
return self._request("get", f"{self.feeder_url}/messages/search/", params=params)
def get_user_modifications(self, user_id: Optional[int] = None, skip: int = 0, limit: int = 100) -> Optional[List[Dict[str, Any]]]:
return self._request(
"get",
f"{self.user_url}/api/v1/logs/users",
params={"skip": skip, "limit": limit}
)
### FEEDER ###
## Alerts ##
def get_alerts(self, skip: int = 0, limit: int = 100, status=None, severity=None, date_from=None, date_to=None):
params = {"skip": skip, "limit": limit}
if status:
params["status"] = status
if severity:
params["severity"] = severity
if date_from:
params["date_from"] = date_from
if date_to:
params["date_to"] = date_to
return self._request("get", f"{self.feeder_url}/alerts/", params=params)
def get_message(self, group_id: int, message_id: int) -> Optional[Dict[str, Any]]:
return self._request(
"get",
f"{self.feeder_url}/groups/{group_id}/messages/{message_id}"
)
def add_note_to_alert(self, alert_id: int, user_id: int, content: str) -> Optional[Dict[str, Any]]:
payload: Dict[str, Any] = {"alert_id": alert_id,"user_id": user_id, "content": f"{content}"}
return self._request(
"post",
f"{self.feeder_url}/notes/",
json=payload
)
def get_notes_for_alert(self, alert_id: int) -> Optional[List[Dict[str, Any]]]:
return self._request(
"get",
f"{self.feeder_url}/alerts/{alert_id}/notes"
)
def mark_alert_as_resolved(self, alert_id: int) -> Optional[Dict[str, Any]]:
return self._request(
"post",
f"{self.feeder_url}/alerts/{alert_id}/resolve"
)
def mark_alert_as_pending(self, alert_id: int) -> Optional[Dict[str, Any]]:
return self._request(
"post",
f"{self.feeder_url}/alerts/{alert_id}/reopen"
)
def set_alert_in_progress(self, alert_id: int) -> Optional[Dict[str, Any]]:
return self._request(
"post",
f"{self.feeder_url}/alerts/{alert_id}/in-progress"
)
## Groups ##
def get_groups(self) -> Optional[List[Dict[str, Any]]]:
return self._request(
"get",
f"{self.feeder_url}/groups/"
)
def get_group(self, group_id: int) -> Optional[Dict[str, Any]]:
return self._request(
"get",
f"{self.feeder_url}/groups/{group_id}"
)
def add_channel(self, channel_id: int) -> Optional[Dict[str, Any]]:
return self._request(
"post",
f"{self.feeder_url}/manage/",
params={"channel": channel_id}
)
## Telegram Session ##
def telegram_status(self) -> Optional[Dict[str, Any]]:
return self._request(
"get",
f"{self.feeder_url}/telegram-status"
)
def init_session_telegram(self) -> Optional[Dict[str, Any]]:
return self._request(
"get",
f"{self.feeder_url}/manage/init-session"
)
def verify_code(self, flow_id: str, code: str, password: Optional[str] = None) -> Optional[Dict[str, Any]]:
payload: Dict[str, Any] = {"flow_id": flow_id, "code": code}
if password:
payload["password"] = password
return self._request(
"post",
f"{self.feeder_url}/manage/verify-code",
json=payload
)
## Rules ##
def get_rules(self, skip: int = 0, limit: int = 100) -> Optional[List[Dict[str, Any]]]:
return self._request(
"get",
f"{self.feeder_url}/rules/",
params={"skip": skip, "limit": limit}
)
def get_rule(self, rule_id: int) -> Optional[Dict[str, Any]]:
return self._request(
"get",
f"{self.feeder_url}/rules/{rule_id}"
)
def search_rules(self, q: str = "", skip: int = 0, limit: int = 100) -> Optional[List[Dict[str, Any]]]:
return self._request(
"get",
f"{self.feeder_url}/rules/search",
params={"q": q, "skip": skip, "limit": limit}
)
def create_rule(self, rule_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
return self._request(
"post",
f"{self.feeder_url}/rules/",
json=rule_data
)
def update_rule(self, rule_id: int, rule_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
return self._request(
"put",
f"{self.feeder_url}/rules/{rule_id}",
json=rule_data
)
##Attachment
def get_attachment(self, attachment_id: int) -> Optional[Dict[str, Any]]:
"""Obtiene los metadatos de un adjunto por su ID."""
return self._request(
"get",
f"{self.feeder_url}/attachments/{attachment_id}"
)
def download_attachment(self, attachment_id: int) -> Optional[bytes]:
"""
Descarga el contenido binario de un adjunto directamente desde Telegram vía la API.
Devuelve los bytes del archivo, o None si falla.
"""
headers = self._get_headers()
if headers is None:
return None
# El endpoint de descarga no requiere Content-Type JSON
download_headers = {
"Authorization": headers["Authorization"],
"accept": "*/*"
}
try:
response = requests.get(
f"{self.feeder_url}/attachments/{attachment_id}/download",
headers=download_headers,
timeout=(10, 600) # tiempo de espera más alto para archivos grandes
)
if response.status_code == 200:
return response.content
return None
except Exception as e:
st.error(f"Error al descargar adjunto {attachment_id}: {str(e)}")
return None
def get_attachments_by_message(self, message_id: int, group_id: int) -> Optional[list]:
"""
Obtiene los adjuntos asociados a un mensaje específico.
Filtra desde el listado general de adjuntos por message_id y group_id.
Nota: si en el futuro la API expone un endpoint dedicado, reemplazar esta implementación.
"""
# Por ahora los adjuntos vienen embebidos en el mensaje via MessageResponse.attachments
# Este método es un helper para obtenerlos si solo tenemos los IDs
message = self.get_message(group_id, message_id)
if message is None:
return None
return message.get("attachments", [])
## Audit ##
def get_audit_logs(
self,
entity_type: Optional[str] = None,
entity_id: Optional[str] = None,
action: Optional[str] = None,
user_id: Optional[int] = None,
date_from=None,
date_to=None,
skip: int = 0,
limit: int = 100,
) -> Optional[List[Dict[str, Any]]]:
params = {"skip": skip, "limit": limit}
if entity_type:
params["entity_type"] = entity_type
if entity_id:
params["entity_id"] = entity_id
if action:
params["action"] = action
if user_id:
params["user_id"] = user_id
if date_from:
params["date_from"] = date_from
if date_to:
params["date_to"] = date_to
return self._request("get", f"{self.feeder_url}/audit/", params=params)
## Stats ##
def get_stats(
self,
date_from: str = None,
date_to: str = None,
) -> Optional[Dict[str, Any]]:
"""Obtiene todas las estadísticas agregadas del sistema."""
params = {}
if date_from: params["date_from"] = date_from
if date_to: params["date_to"] = date_to
return self._request(
"get",
f"{self.feeder_url}/stats/",
params=params
)
## Senders ##
def get_senders(self, skip: int = 0, limit: int = 100) -> Optional[List[Dict[str, Any]]]:
"""Obtiene la lista de remitentes cargados por el feeder."""
return self._request(
"get",
f"{self.feeder_url}/senders/",
params={"skip": skip, "limit": limit}
)
def get_sender(self, sender_id: int) -> Optional[Dict[str, Any]]:
"""Obtiene un remitente por su ID de Telegram."""
return self._request(
"get",
f"{self.feeder_url}/senders/{sender_id}"
)
def get_messages_by_sender(
self,
sender_id: int,
skip: int = 0,
limit: int = 100
) -> Optional[List[Dict[str, Any]]]:
"""Obtiene todos los mensajes enviados por un sender específico."""
return self._request(
"get",
f"{self.feeder_url}/senders/{sender_id}/messages/",
params={"skip": skip, "limit": limit}
)
## Backup ##
def get_backup_status(self) -> Optional[Dict[str, Any]]:
"""
Consulta el estado de disponibilidad de cada componente de backup
sin generar el archivo.
"""
if not self.backup_url:
return None
return self._request("get", f"{self.backup_url}/backup/status")
def download_backup(
self,
include_db: bool = True,
include_config: bool = True,
include_ssl: bool = True,
include_sessions: bool = True,
) -> Optional[bytes]:
"""
Llama al endpoint /backup del microservicio y devuelve los bytes del ZIP.
Usa un timeout largo (10 min) porque mysqldump puede tardar.
"""
if not self.backup_url:
return None
headers = self._get_headers()
if headers is None:
return None
dl_headers = {
"Authorization": headers["Authorization"],
"accept": "application/zip",
}
params = {
"include_db": str(include_db).lower(),
"include_config": str(include_config).lower(),
"include_ssl": str(include_ssl).lower(),
"include_sessions": str(include_sessions).lower(),
}
try:
response = requests.get(
f"{self.backup_url}/backup",
headers=dl_headers,
params=params,
timeout=(15, 600), # (connect, read) — dump puede tardar
)
if response.status_code == 200:
return response.content
return None
except requests.exceptions.Timeout:
return None
except Exception:
return None
# Crear instancia global
api_client = APIClient(str(os.getenv('USERS_URL')),str(os.getenv('FEEDER_URL')), str(os.getenv('BACKUP_URL')))