Files
mason/app.py

281 lines
8.9 KiB
Python
Raw Permalink Normal View History

from flask import Flask, request, jsonify
from functools import wraps
import psycopg2
from psycopg2.extras import RealDictCursor
import os
import hashlib
import json
from datetime import datetime
app = Flask(__name__)
H_INSTANCIA = os.environ.get('H_INSTANCIA')
DB_HOST = os.environ.get('DB_HOST', '172.17.0.1')
DB_PORT = os.environ.get('DB_PORT', '5432')
DB_NAME = os.environ.get('DB_NAME', 'corp')
DB_USER = os.environ.get('DB_USER', 'corp')
DB_PASSWORD = os.environ.get('DB_PASSWORD', 'corp')
PORT = int(os.environ.get('PORT', 5053))
def get_db():
return psycopg2.connect(
host=DB_HOST, port=DB_PORT, database=DB_NAME,
user=DB_USER, password=DB_PASSWORD,
cursor_factory=RealDictCursor
)
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth_key = request.headers.get('X-Auth-Key')
if not auth_key or auth_key != H_INSTANCIA:
return jsonify({'error': 'Unauthorized'}), 401
return f(*args, **kwargs)
return decorated
def generate_hash(data):
return hashlib.sha256(f"{data}{datetime.now().isoformat()}".encode()).hexdigest()[:64]
@app.route('/health', methods=['GET'])
def health():
try:
conn = get_db()
cur = conn.cursor()
cur.execute('SELECT 1')
cur.close()
conn.close()
return jsonify({'status': 'healthy', 'service': 'mason', 'version': '1.0.0'})
except Exception as e:
return jsonify({'status': 'unhealthy', 'error': str(e)}), 500
@app.route('/s-contract', methods=['GET'])
def s_contract():
return jsonify({
'service': 'mason',
'version': '1.0.0',
'contract_version': 'S-CONTRACT v2.1',
'description': 'Editing workspace for data with incidencias - review and correct before forwarding',
'endpoints': {
'/health': {'method': 'GET', 'auth': False},
'/recibir': {'method': 'POST', 'auth': True, 'desc': 'Receive data with incidencia'},
'/pendientes': {'method': 'GET', 'auth': True, 'desc': 'List pending items to review'},
'/item/<id>': {'method': 'GET', 'auth': True, 'desc': 'Get item details'},
'/item/<id>': {'method': 'PUT', 'auth': True, 'desc': 'Edit item data'},
'/item/<id>/resolver': {'method': 'POST', 'auth': True, 'desc': 'Mark as resolved and forward to FELDMAN'},
'/item/<id>/descartar': {'method': 'POST', 'auth': True, 'desc': 'Discard item'},
'/historial': {'method': 'GET', 'auth': True, 'desc': 'Resolved/discarded items'},
'/stats': {'method': 'GET', 'auth': True, 'desc': 'Statistics'}
}
})
# Receive data with incidencia (from ALFRED/JARED routing)
@app.route('/recibir', methods=['POST'])
@require_auth
def recibir():
data = request.get_json() or {}
h_registro = generate_hash(str(data))
conn = get_db()
cur = conn.cursor()
cur.execute('''
INSERT INTO incidencias
(h_incidencia, h_instancia_origen, h_ejecucion, tipo, descripcion, datos, estado)
VALUES (%s, %s, %s, %s, %s, %s, %s)
RETURNING id, h_incidencia, created_at
''', (
h_registro,
data.get('h_instancia_origen', 'unknown'),
data.get('h_ejecucion', ''),
data.get('tipo', 'revision'),
data.get('descripcion', 'Pendiente de revision'),
psycopg2.extras.Json(data),
'pendiente'
))
result = cur.fetchone()
conn.commit()
cur.close()
conn.close()
return jsonify({
'success': True,
'mensaje': 'Recibido para revision',
'registro': dict(result)
})
# List pending items (need review/editing)
@app.route('/pendientes', methods=['GET'])
@require_auth
def pendientes():
conn = get_db()
cur = conn.cursor()
cur.execute('''
SELECT id, h_incidencia, h_instancia_origen, tipo, descripcion, datos, created_at
FROM incidencias
WHERE estado = 'pendiente'
ORDER BY created_at ASC
''')
items = cur.fetchall()
cur.close()
conn.close()
return jsonify({'pendientes': [dict(i) for i in items], 'count': len(items)})
# Get item details
@app.route('/item/<int:item_id>', methods=['GET'])
@require_auth
def get_item(item_id):
conn = get_db()
cur = conn.cursor()
cur.execute('SELECT * FROM incidencias WHERE id = %s', (item_id,))
item = cur.fetchone()
cur.close()
conn.close()
if not item:
return jsonify({'error': 'Item not found'}), 404
return jsonify({'item': dict(item)})
# Edit item data
@app.route('/item/<int:item_id>', methods=['PUT'])
@require_auth
def edit_item(item_id):
data = request.get_json() or {}
conn = get_db()
cur = conn.cursor()
# Get current item
cur.execute('SELECT * FROM incidencias WHERE id = %s AND estado = %s', (item_id, 'pendiente'))
item = cur.fetchone()
if not item:
cur.close()
conn.close()
return jsonify({'error': 'Item not found or already processed'}), 404
# Merge edited data
current_datos = item['datos'] or {}
if 'datos' in data:
current_datos.update(data['datos'])
cur.execute('''
UPDATE incidencias
SET datos = %s, descripcion = %s, updated_at = NOW()
WHERE id = %s
RETURNING id, datos, updated_at
''', (
psycopg2.extras.Json(current_datos),
data.get('descripcion', item['descripcion']),
item_id
))
result = cur.fetchone()
conn.commit()
cur.close()
conn.close()
return jsonify({'success': True, 'mensaje': 'Item actualizado', 'item': dict(result)})
# Resolve and forward to FELDMAN
@app.route('/item/<int:item_id>/resolver', methods=['POST'])
@require_auth
def resolver_item(item_id):
data = request.get_json() or {}
conn = get_db()
cur = conn.cursor()
cur.execute('SELECT * FROM incidencias WHERE id = %s AND estado = %s', (item_id, 'pendiente'))
item = cur.fetchone()
if not item:
cur.close()
conn.close()
return jsonify({'error': 'Item not found or already processed'}), 404
# Mark as resolved
cur.execute('''
UPDATE incidencias
SET estado = 'resuelto', resolucion = %s, resolved_at = NOW(), updated_at = NOW()
WHERE id = %s
RETURNING id, h_incidencia, estado, resolved_at
''', (data.get('resolucion', 'Corregido manualmente'), item_id))
result = cur.fetchone()
# Insert into completados (FELDMAN table) with corrected data
h_completado = generate_hash(str(item['datos']))
cur.execute('''
INSERT INTO completados
(h_completado, h_instancia_origen, h_ejecucion, flujo_nombre, datos, notas)
VALUES (%s, %s, %s, %s, %s, %s)
''', (
h_completado,
item['h_instancia_origen'],
item['h_ejecucion'],
item['datos'].get('flujo_nombre', ''),
psycopg2.extras.Json(item['datos']),
f"Corregido en MASON: {data.get('resolucion', '')}"
))
conn.commit()
cur.close()
conn.close()
return jsonify({
'success': True,
'mensaje': 'Item resuelto y enviado a FELDMAN',
'item': dict(result)
})
# Discard item
@app.route('/item/<int:item_id>/descartar', methods=['POST'])
@require_auth
def descartar_item(item_id):
data = request.get_json() or {}
conn = get_db()
cur = conn.cursor()
cur.execute('''
UPDATE incidencias
SET estado = 'descartado', resolucion = %s, resolved_at = NOW(), updated_at = NOW()
WHERE id = %s AND estado = 'pendiente'
RETURNING id, h_incidencia, estado
''', (data.get('motivo', 'Descartado'), item_id))
result = cur.fetchone()
conn.commit()
cur.close()
conn.close()
if not result:
return jsonify({'error': 'Item not found or already processed'}), 404
return jsonify({'success': True, 'mensaje': 'Item descartado', 'item': dict(result)})
# History of resolved/discarded
@app.route('/historial', methods=['GET'])
@require_auth
def historial():
limit = request.args.get('limit', 50, type=int)
conn = get_db()
cur = conn.cursor()
cur.execute('''
SELECT * FROM incidencias
WHERE estado IN ('resuelto', 'descartado')
ORDER BY resolved_at DESC LIMIT %s
''', (limit,))
items = cur.fetchall()
cur.close()
conn.close()
return jsonify({'historial': [dict(i) for i in items], 'count': len(items)})
# Stats
@app.route('/stats', methods=['GET'])
@require_auth
def stats():
conn = get_db()
cur = conn.cursor()
cur.execute('SELECT estado, COUNT(*) as count FROM incidencias GROUP BY estado')
por_estado = {r['estado']: r['count'] for r in cur.fetchall()}
cur.execute('SELECT COUNT(*) as total FROM incidencias WHERE estado = %s', ('pendiente',))
pendientes = cur.fetchone()['total']
cur.close()
conn.close()
return jsonify({'pendientes': pendientes, 'por_estado': por_estado})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=PORT, debug=False)