226 lines
10 KiB
Python
226 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""CRUD de webhooks salientes y log de entregas (Configuración -> Webhooks)."""
|
|
|
|
import json
|
|
import secrets
|
|
|
|
from odoo import http
|
|
from odoo.http import request, Response
|
|
|
|
from odoo.addons.skeen_whatsapp.controllers.auth import require_role, require_perm
|
|
from odoo.addons.skeen_plataforma.models.webhook import EVENTOS, ESTADOS
|
|
|
|
BASE = '/skeen/frontend/v1'
|
|
|
|
|
|
def _json(data, status=200):
|
|
return Response(
|
|
json.dumps(data, default=str),
|
|
status=status,
|
|
mimetype='application/json',
|
|
headers={
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
},
|
|
)
|
|
|
|
|
|
def _body():
|
|
try:
|
|
raw = request.httprequest.get_data(as_text=True)
|
|
return json.loads(raw) if raw else {}
|
|
except Exception:
|
|
return {}
|
|
|
|
|
|
def _login():
|
|
return getattr(request.frontend_user, 'login', '') if hasattr(request, 'frontend_user') else ''
|
|
|
|
|
|
def _eventos_list(data):
|
|
if isinstance(data.get('eventos'), list):
|
|
return [str(e).strip() for e in data['eventos'] if str(e).strip()]
|
|
if isinstance(data.get('eventos'), str):
|
|
return [e.strip() for e in data['eventos'].split(',') if e.strip()]
|
|
return []
|
|
|
|
|
|
class SkeenWebhooksController(http.Controller):
|
|
|
|
@http.route(BASE + '/webhooks', type='http', auth='none',
|
|
methods=['GET', 'OPTIONS'], csrf=False)
|
|
@require_role('gerencia')
|
|
@require_perm('webhooks.editar')
|
|
def list_webhooks(self, **kw):
|
|
try:
|
|
service = request.env['skeen.webhook.service'].sudo()
|
|
hooks = request.env['skeen.webhook'].sudo().search([])
|
|
catalogo = [{'clave': c, 'label': l,
|
|
'ejemplo': service.payload_ejemplo(c)} for c, l in EVENTOS]
|
|
return _json({
|
|
'status': 'success',
|
|
'webhooks': [w.to_public_dict() for w in hooks],
|
|
'eventos': catalogo,
|
|
'estados': [{'clave': c, 'label': l} for c, l in ESTADOS],
|
|
'formato_firma': 'X-Skeen-Signature: sha256=<HMAC-SHA256 del cuerpo con el secreto>',
|
|
})
|
|
except Exception as e:
|
|
return _json({'status': 'error', 'message': str(e)}, 500)
|
|
|
|
@http.route(BASE + '/webhooks', type='http', auth='none',
|
|
methods=['POST', 'OPTIONS'], csrf=False)
|
|
@require_role('gerencia')
|
|
@require_perm('webhooks.editar')
|
|
def create_webhook(self, **kw):
|
|
try:
|
|
data = _body()
|
|
url = (data.get('url') or '').strip()
|
|
if not (url.startswith('http://') or url.startswith('https://')):
|
|
return _json({'status': 'error',
|
|
'message': 'La URL debe empezar por http:// o https://'}, 400)
|
|
secreto = (data.get('secreto') or '').strip() or secrets.token_urlsafe(32)
|
|
headers = data.get('headers')
|
|
if isinstance(headers, dict):
|
|
headers = json.dumps(headers, ensure_ascii=False)
|
|
else:
|
|
headers = (headers or '').strip()
|
|
webhook = request.env['skeen.webhook'].sudo().create({
|
|
'name': (data.get('name') or '').strip() or 'Webhook',
|
|
'url': url,
|
|
'eventos': ','.join(_eventos_list(data)),
|
|
'activo': bool(data.get('activo', True)),
|
|
'secreto': secreto,
|
|
'headers': headers,
|
|
'notas': data.get('notas') or '',
|
|
'creado_por_login': _login(),
|
|
})
|
|
return _json({'status': 'success', 'webhook': webhook.to_public_dict(),
|
|
'secreto': secreto,
|
|
'message': 'Webhook creado.'})
|
|
except Exception as e:
|
|
request.env.cr.rollback()
|
|
return _json({'status': 'error', 'message': str(e)}, 500)
|
|
|
|
@http.route(BASE + '/webhooks/<int:webhook_id>', type='http', auth='none',
|
|
methods=['PUT', 'OPTIONS'], csrf=False)
|
|
@require_role('gerencia')
|
|
@require_perm('webhooks.editar')
|
|
def update_webhook(self, webhook_id, **kw):
|
|
try:
|
|
webhook = request.env['skeen.webhook'].sudo().browse(webhook_id)
|
|
if not webhook.exists():
|
|
return _json({'status': 'error', 'message': 'Webhook no encontrado'}, 404)
|
|
data = _body()
|
|
vals = {}
|
|
if 'name' in data and (data.get('name') or '').strip():
|
|
vals['name'] = data['name'].strip()
|
|
if 'url' in data and (data.get('url') or '').strip():
|
|
url = data['url'].strip()
|
|
if not (url.startswith('http://') or url.startswith('https://')):
|
|
return _json({'status': 'error',
|
|
'message': 'La URL debe empezar por http:// o https://'}, 400)
|
|
vals['url'] = url
|
|
if 'eventos' in data:
|
|
vals['eventos'] = ','.join(_eventos_list(data))
|
|
if 'activo' in data:
|
|
vals['activo'] = bool(data['activo'])
|
|
if vals['activo']:
|
|
vals['fallos_consecutivos'] = 0
|
|
if 'secreto' in data and (data.get('secreto') or '').strip():
|
|
vals['secreto'] = data['secreto'].strip()
|
|
if 'headers' in data:
|
|
headers = data['headers']
|
|
vals['headers'] = json.dumps(headers, ensure_ascii=False) \
|
|
if isinstance(headers, dict) else (headers or '')
|
|
if 'notas' in data:
|
|
vals['notas'] = data.get('notas') or ''
|
|
if vals:
|
|
webhook.write(vals)
|
|
return _json({'status': 'success', 'webhook': webhook.to_public_dict()})
|
|
except Exception as e:
|
|
request.env.cr.rollback()
|
|
return _json({'status': 'error', 'message': str(e)}, 500)
|
|
|
|
@http.route(BASE + '/webhooks/<int:webhook_id>', type='http', auth='none',
|
|
methods=['DELETE'], csrf=False)
|
|
@require_role('gerencia')
|
|
@require_perm('webhooks.editar')
|
|
def delete_webhook(self, webhook_id, **kw):
|
|
try:
|
|
webhook = request.env['skeen.webhook'].sudo().browse(webhook_id)
|
|
if not webhook.exists():
|
|
return _json({'status': 'error', 'message': 'Webhook no encontrado'}, 404)
|
|
webhook.unlink()
|
|
return _json({'status': 'success', 'message': 'Webhook eliminado.'})
|
|
except Exception as e:
|
|
request.env.cr.rollback()
|
|
return _json({'status': 'error', 'message': str(e)}, 500)
|
|
|
|
@http.route(BASE + '/webhooks/<int:webhook_id>/eventos', type='http', auth='none',
|
|
methods=['GET', 'OPTIONS'], csrf=False)
|
|
@require_role('gerencia')
|
|
@require_perm('webhooks.editar')
|
|
def list_webhook_eventos(self, webhook_id, **kw):
|
|
try:
|
|
webhook = request.env['skeen.webhook'].sudo().browse(webhook_id)
|
|
if not webhook.exists():
|
|
return _json({'status': 'error', 'message': 'Webhook no encontrado'}, 404)
|
|
domain = [('webhook_id', '=', webhook.id)]
|
|
if kw.get('estado'):
|
|
domain.append(('estado', '=', kw['estado']))
|
|
if kw.get('evento'):
|
|
domain.append(('evento', '=', kw['evento']))
|
|
try:
|
|
limite = min(max(int(kw.get('limit') or 100), 1), 500)
|
|
except (TypeError, ValueError):
|
|
limite = 100
|
|
Evento = request.env['skeen.webhook.evento'].sudo()
|
|
registros = Evento.search(domain, order='create_date desc, id desc',
|
|
limit=limite)
|
|
resumen = {c: 0 for c, _l in ESTADOS}
|
|
for grupo in Evento.read_group(domain, ['estado'], ['estado'], lazy=False):
|
|
if grupo.get('estado'):
|
|
resumen[grupo['estado']] = grupo.get('__count', 0)
|
|
return _json({'status': 'success',
|
|
'eventos': [e.to_public_dict() for e in registros],
|
|
'total': Evento.search_count(domain),
|
|
'resumen': resumen})
|
|
except Exception as e:
|
|
return _json({'status': 'error', 'message': str(e)}, 500)
|
|
|
|
@http.route(BASE + '/webhooks/<int:webhook_id>/probar', type='http', auth='none',
|
|
methods=['POST', 'OPTIONS'], csrf=False)
|
|
@require_role('gerencia')
|
|
@require_perm('webhooks.editar')
|
|
def probar_webhook(self, webhook_id, **kw):
|
|
try:
|
|
webhook = request.env['skeen.webhook'].sudo().browse(webhook_id)
|
|
if not webhook.exists():
|
|
return _json({'status': 'error', 'message': 'Webhook no encontrado'}, 404)
|
|
ok, mensaje, evento = request.env['skeen.webhook.service'].sudo().probar(
|
|
webhook, login=_login())
|
|
return _json({'status': 'success' if ok else 'error', 'ok': ok,
|
|
'message': mensaje,
|
|
'evento': evento.to_public_dict() if evento else None},
|
|
200 if ok else 502)
|
|
except Exception as e:
|
|
request.env.cr.rollback()
|
|
return _json({'status': 'error', 'message': str(e)}, 500)
|
|
|
|
@http.route(BASE + '/webhook_eventos/<int:evento_id>/reintentar', type='http',
|
|
auth='none', methods=['POST', 'OPTIONS'], csrf=False)
|
|
@require_role('gerencia')
|
|
@require_perm('webhooks.editar')
|
|
def reintentar_evento(self, evento_id, **kw):
|
|
try:
|
|
evento = request.env['skeen.webhook.evento'].sudo().browse(evento_id)
|
|
if not evento.exists():
|
|
return _json({'status': 'error', 'message': 'Evento no encontrado'}, 404)
|
|
estado = request.env['skeen.webhook.service'].sudo().reintentar(evento)
|
|
return _json({'status': 'success', 'estado': estado,
|
|
'evento': evento.to_public_dict()})
|
|
except Exception as e:
|
|
request.env.cr.rollback()
|
|
return _json({'status': 'error', 'message': str(e)}, 500)
|