First commit
This commit is contained in:
@@ -0,0 +1,439 @@
|
||||
# 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')))
|
||||
Reference in New Issue
Block a user