221 lines
7.6 KiB
Python
221 lines
7.6 KiB
Python
import json
|
|
import urllib.request
|
|
import urllib.error
|
|
from datetime import datetime
|
|
|
|
from django.db import connection
|
|
from django.utils import timezone
|
|
from common.utils import clean_sql_string, clean_sql_int
|
|
|
|
|
|
# =========================================================
|
|
# Helper HTTP (stdlib) — evita dependencia extra en requirements
|
|
# =========================================================
|
|
def _http_get_json(url, timeout=8):
|
|
"""GET a un servicio externo que responde JSON. Usa stdlib para no añadir deps."""
|
|
req = urllib.request.Request(url, headers={'User-Agent': 'django-core-base/1.0'})
|
|
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
raw = response.read().decode('utf-8')
|
|
try:
|
|
return json.loads(raw)
|
|
except json.JSONDecodeError:
|
|
return {'raw': raw}
|
|
|
|
|
|
def _http_get_text(url, timeout=8):
|
|
"""GET a un servicio externo que responde texto plano."""
|
|
req = urllib.request.Request(url, headers={'User-Agent': 'django-core-base/1.0'})
|
|
with urllib.request.urlopen(req, timeout=timeout) as response:
|
|
return response.read().decode('utf-8').strip()
|
|
|
|
|
|
# =========================================================
|
|
# ACCIÓN 1 — Base de datos (INSERT histórico de ejecución)
|
|
# set_parameterized
|
|
# =========================================================
|
|
def setEjecucion(params):
|
|
"""
|
|
Inserta una fila en automatizacion_ejecuciones registrando una ejecución.
|
|
params esperados: nombre, descripcion, estado, origen, resultado, error
|
|
"""
|
|
query = """
|
|
INSERT INTO automatizacion_ejecuciones
|
|
(nombre, descripcion, estado, origen, resultado, error, fecha_inicio, fecha_fin, activo)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
RETURNING id
|
|
"""
|
|
|
|
ahora = timezone.now()
|
|
resultado_json = json.dumps(params.get('resultado')) if params.get('resultado') is not None else None
|
|
|
|
parameter_dict = [
|
|
clean_sql_string(params.get('nombre')),
|
|
clean_sql_string(params.get('descripcion')),
|
|
clean_sql_string(params.get('estado') or 'ok'),
|
|
clean_sql_string(params.get('origen') or 'manual'),
|
|
resultado_json,
|
|
clean_sql_string(params.get('error')) if params.get('error') else None,
|
|
ahora,
|
|
ahora,
|
|
bool(params.get('activo', True)),
|
|
]
|
|
|
|
with connection.cursor() as cursor:
|
|
cursor.execute(query, parameter_dict)
|
|
row = cursor.fetchone()
|
|
nuevo_id = row[0] if row else None
|
|
|
|
return {'id': nuevo_id, 'fecha': ahora.isoformat()}
|
|
|
|
|
|
# =========================================================
|
|
# ACCIÓN 2 — Servicio externo: Cat Fact API
|
|
# https://catfact.ninja/fact (gratis, sin auth)
|
|
# =========================================================
|
|
def getCatFact():
|
|
url = 'https://catfact.ninja/fact'
|
|
data = _http_get_json(url)
|
|
return {
|
|
'servicio': 'catfact.ninja',
|
|
'url': url,
|
|
'fact': data.get('fact'),
|
|
'length': data.get('length'),
|
|
}
|
|
|
|
|
|
# =========================================================
|
|
# ACCIÓN 3 — Servicio externo: GitHub Zen
|
|
# https://api.github.com/zen (gratis, sin auth)
|
|
# =========================================================
|
|
def getGithubZen():
|
|
url = 'https://api.github.com/zen'
|
|
texto = _http_get_text(url)
|
|
return {
|
|
'servicio': 'api.github.com/zen',
|
|
'url': url,
|
|
'zen': texto,
|
|
}
|
|
|
|
|
|
# =========================================================
|
|
# Orquestador — ejecuta las 3 acciones en secuencia
|
|
# =========================================================
|
|
def ejecutarAutomatizaciones(params):
|
|
"""
|
|
Ejecuta las 3 acciones automatizadas:
|
|
1) Llamada a servicio externo: catfact.ninja
|
|
2) Llamada a servicio externo: api.github.com/zen
|
|
3) Persistencia en BD: inserta la ejecución en automatizacion_ejecuciones
|
|
"""
|
|
resultados = {'acciones': []}
|
|
errores = []
|
|
|
|
# Acción 2 — catfact
|
|
try:
|
|
resultados['acciones'].append({'step': 1, 'ok': True, 'data': getCatFact()})
|
|
except Exception as err:
|
|
errores.append(f'catfact: {err}')
|
|
resultados['acciones'].append({'step': 1, 'ok': False, 'error': str(err)})
|
|
|
|
# Acción 3 — github zen
|
|
try:
|
|
resultados['acciones'].append({'step': 2, 'ok': True, 'data': getGithubZen()})
|
|
except Exception as err:
|
|
errores.append(f'github_zen: {err}')
|
|
resultados['acciones'].append({'step': 2, 'ok': False, 'error': str(err)})
|
|
|
|
# Acción 1 — persistir en BD
|
|
estado_final = 'ok' if not errores else 'error'
|
|
nombre = clean_sql_string(params.get('nombre')) or f'Ejecucion {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
|
|
origen = clean_sql_string(params.get('origen')) or 'manual'
|
|
|
|
registro = setEjecucion({
|
|
'nombre': nombre,
|
|
'descripcion': params.get('descripcion') or 'Ejecución orquestada por /api/automatizados/ejecutar/',
|
|
'estado': estado_final,
|
|
'origen': origen,
|
|
'resultado': resultados,
|
|
'error': '; '.join(errores) if errores else None,
|
|
'activo': True,
|
|
})
|
|
|
|
resultados['acciones'].append({'step': 3, 'ok': True, 'data': registro})
|
|
|
|
return {
|
|
'ejecucion_id': registro.get('id'),
|
|
'estado': estado_final,
|
|
'total_acciones': len(resultados['acciones']),
|
|
'resultados': resultados,
|
|
'errores': errores,
|
|
}
|
|
|
|
|
|
# =========================================================
|
|
# Lectura — historial (get_parameterized)
|
|
# =========================================================
|
|
def getHistorial(params):
|
|
"""
|
|
Devuelve el histórico de ejecuciones. Permite filtrar por estado y limit.
|
|
"""
|
|
query = """
|
|
SELECT id, nombre, descripcion, estado, origen, resultado, error,
|
|
fecha_inicio, fecha_fin, activo
|
|
FROM automatizacion_ejecuciones
|
|
WHERE (%s = '' OR estado = %s)
|
|
ORDER BY fecha_inicio DESC
|
|
LIMIT %s
|
|
"""
|
|
|
|
estado = clean_sql_string(params.get('estado')) if params.get('estado') else ''
|
|
limite = clean_sql_int(params.get('limit')) or 20
|
|
|
|
parameter_dict = [estado, estado, limite]
|
|
|
|
with connection.cursor() as cursor:
|
|
cursor.execute(query, parameter_dict)
|
|
columns = [col[0] for col in cursor.description]
|
|
rows = [dict(zip(columns, row)) for row in cursor.fetchall()]
|
|
|
|
# Normalizamos el campo resultado (JSON serializado en SQLite)
|
|
for row in rows:
|
|
if isinstance(row.get('resultado'), str):
|
|
try:
|
|
row['resultado'] = json.loads(row['resultado'])
|
|
except (ValueError, TypeError):
|
|
pass
|
|
|
|
return {'total': len(rows), 'data': rows}
|
|
|
|
|
|
# =========================================================
|
|
# Estado del módulo
|
|
# =========================================================
|
|
def getEstado():
|
|
"""
|
|
Devuelve el estado general del módulo de automatizaciones:
|
|
cuántas ejecuciones hay, última ejecución, endpoints disponibles.
|
|
"""
|
|
query = """
|
|
SELECT COUNT(*) AS total,
|
|
SUM(CASE WHEN estado = 'ok' THEN 1 ELSE 0 END) AS ok,
|
|
SUM(CASE WHEN estado = 'error' THEN 1 ELSE 0 END) AS errores,
|
|
MAX(fecha_inicio) AS ultima_ejecucion
|
|
FROM automatizacion_ejecuciones
|
|
"""
|
|
with connection.cursor() as cursor:
|
|
cursor.execute(query)
|
|
columns = [col[0] for col in cursor.description]
|
|
row = cursor.fetchone()
|
|
resumen = dict(zip(columns, row)) if row else {}
|
|
|
|
return {
|
|
'status': 'ok',
|
|
'modulo': 'automatizados',
|
|
'endpoints': [
|
|
'POST /api/automatizados/ejecutar/',
|
|
'POST /api/automatizados/historial/',
|
|
'POST /api/automatizados/estado/',
|
|
],
|
|
'resumen': resumen,
|
|
}
|