from flask import Flask, abort, redirect, jsonify, request from flask_cors import CORS from datetime import datetime, timezone import psycopg2 app = Flask(__name__) CORS(app) DB_CONFIG = { "host": "postgres_hst", "port": 5432, "database": "hst_images", "user": "directus", "password": "directus_hst_2024" } BASE_URL = "https://tzrtech.org" MRF_BIBLIOTECA = "b7149f9e2106c566032aeb29a26e4c6cdd5f5c16b4421025c58166ee345740d1" def get_db(): return psycopg2.connect(**DB_CONFIG) def get_discovered_tables(): return ['flg', 'hst', 'spe', 'vsn', 'vue'] def build_tag(row, table=None): ref, nombre_es, nombre_en, rootref, grupo, mrf, img, als = row[:8] img = (img or "").strip() return { "ref": ref or "", "als": als or ref or "", "nombre_es": nombre_es or "", "nombre_en": nombre_en or "", "rootref": rootref or "", "grupo": grupo or table or "hst", "mrf": mrf or "", "h_maestro": mrf or "", "img": img, "imagen_url": f"{BASE_URL}/{img}.png" if img else None, "thumb_url": f"{BASE_URL}/thumb/{img}.png" if img else None } def get_parent(mrf): conn = get_db() cur = conn.cursor() cur.execute("SELECT h_padre FROM tree WHERE h_hijo = %s", (mrf,)) row = cur.fetchone() cur.close() conn.close() return row[0] if row else None def get_children(mrf): conn = get_db() cur = conn.cursor() cur.execute(""" SELECT h.ref, h.nombre_es, h.nombre_en, h.rootref, h.grupo, h.mrf, h.img, h.als FROM tree t JOIN hst h ON t.h_hijo = h.mrf WHERE t.h_padre = %s """, (mrf,)) children = [build_tag(row) for row in cur.fetchall()] cur.close() conn.close() return children def get_related(mrf): conn = get_db() cur = conn.cursor() cur.execute(""" SELECT h.ref, h.nombre_es, h.nombre_en, h.rootref, h.grupo, h.mrf, h.img, h.als, g.weight FROM graph g JOIN hst h ON (g.h_b = h.mrf AND g.h_a = %s) OR (g.h_a = h.mrf AND g.h_b = %s) ORDER BY g.weight DESC """, (mrf, mrf)) related = [] for row in cur.fetchall(): tag = build_tag(row) tag["weight"] = row[8] related.append(tag) cur.close() conn.close() return related def get_table_data(table): conn = get_db() cur = conn.cursor() cur.execute(f"SELECT ref, nombre_es, nombre_en, rootref, grupo, mrf, img, als FROM {table} ORDER BY ref") records = [build_tag(row, table) for row in cur.fetchall()] cur.close() conn.close() return {"count": len(records), "records": records} def get_tag_by_mrf(mrf): conn = get_db() cur = conn.cursor() for table in get_discovered_tables(): try: cur.execute(f"SELECT ref, nombre_es, nombre_en, rootref, grupo, mrf, img, als FROM {table} WHERE mrf = %s", (mrf,)) row = cur.fetchone() if row: tag = build_tag(row, table) tag["table"] = table tag["padre_mrf"] = get_parent(mrf) cur.close() conn.close() return tag except: conn.rollback() cur.close() conn.close() return None @app.route("/api/index.json") def api_index(): tables = get_discovered_tables() total_tags = 0 table_data = {} for t in tables: try: data = get_table_data(t) table_data[t] = data total_tags += data["count"] except Exception as e: table_data[t] = {"count": 0, "records": [], "error": str(e)} return jsonify({ "_meta": { "mrf_biblioteca": MRF_BIBLIOTECA, "nombre": "HST", "publica": True, "version": "5.2", "updated": datetime.now(timezone.utc).isoformat(), "base_url": BASE_URL, "tables": tables, "total_tags": total_tags }, **table_data }) @app.route("/api/tags/") def api_tag(mrf): tag = get_tag_by_mrf(mrf) if not tag: return jsonify({"error": "not found", "mrf": mrf}), 404 return jsonify(tag) @app.route("/api/tags//children") def api_tag_children(mrf): return jsonify({"mrf_padre": mrf, "count": len(get_children(mrf)), "children": get_children(mrf)}) @app.route("/api/tags//related") def api_tag_related(mrf): related = get_related(mrf) return jsonify({"mrf": mrf, "count": len(related), "related": related}) @app.route("/api/tags") def api_tags_search(): grupo_param = request.args.get("grupo") grupos_param = request.args.get("grupos") grupos_filtro = [g.strip() for g in grupos_param.split(",")] if grupos_param else [grupo_param] if grupo_param else [] q = request.args.get("q", "").lower() results = [] conn = get_db() cur = conn.cursor() for table in get_discovered_tables(): try: cur.execute(f"SELECT ref, nombre_es, nombre_en, rootref, grupo, mrf, img, als FROM {table}") for row in cur.fetchall(): tag = build_tag(row, table) if grupos_filtro and tag["grupo"] not in grupos_filtro: continue if q and q not in (tag["nombre_es"] or "").lower() and q not in (tag["nombre_en"] or "").lower() and q not in (tag["ref"] or "").lower(): continue results.append(tag) except: conn.rollback() cur.close() conn.close() return jsonify({"biblioteca": {"mrf": MRF_BIBLIOTECA, "nombre": "HST", "publica": True}, "count": len(results), "results": results}) @app.route("/api/grupos") def api_grupos(): grupos = {} conn = get_db() cur = conn.cursor() for table in get_discovered_tables(): try: cur.execute(f"SELECT grupo, COUNT(*) FROM {table} GROUP BY grupo") for row in cur.fetchall(): grupos[row[0] or table] = grupos.get(row[0] or table, 0) + row[1] except: conn.rollback() cur.close() conn.close() return jsonify({"grupos": [{"nombre": k, "count": v} for k, v in sorted(grupos.items())]}) @app.route("/api/tree") def api_tree(): conn = get_db() cur = conn.cursor() cur.execute("SELECT h_padre, h_hijo FROM tree") relations = [{"mrf_padre": r[0], "mrf_hijo": r[1]} for r in cur.fetchall()] cur.close() conn.close() return jsonify({"count": len(relations), "relations": relations}) @app.route("/api/graph") def api_graph(): conn = get_db() cur = conn.cursor() cur.execute("SELECT h_a, h_b, weight FROM graph ORDER BY weight DESC") relations = [{"mrf_a": r[0], "mrf_b": r[1], "weight": r[2]} for r in cur.fetchall()] cur.close() conn.close() return jsonify({"count": len(relations), "relations": relations}) @app.route("/api/library") def api_library_list(): conn = get_db() cur = conn.cursor() cur.execute("SELECT l.h_biblioteca, COUNT(*), h.nombre_es FROM library l LEFT JOIN hst h ON l.h_biblioteca = h.mrf GROUP BY l.h_biblioteca, h.nombre_es") libraries = [{"mrf": r[0], "count": r[1], "nombre": r[2] or ""} for r in cur.fetchall()] cur.close() conn.close() return jsonify({"count": len(libraries), "libraries": libraries}) @app.route("/health") def health(): return jsonify({"status": "ok", "version": "5.2"}) @app.route("/api/biblioteca") def api_biblioteca(): return jsonify({ "biblioteca": {"mrf": MRF_BIBLIOTECA, "nombre": "HST", "publica": True, "version": "5.2"}, "endpoints": {"tags": "/api/tags", "grupos": "/api/grupos", "tree": "/api/tree", "graph": "/api/graph", "library": "/api/library"}, "base_url": BASE_URL }) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000) @app.route("/api/graph/edges") def api_graph_edges(): conn = get_db() cur = conn.cursor() cur.execute("SELECT id, h_a, h_b, weight, edge_type FROM graph ORDER BY weight DESC") edges = [{"id": r[0], "source_h": r[1], "target_h": r[2], "weight": r[3], "type": r[4] or "db"} for r in cur.fetchall()] cur.close() conn.close() return jsonify({"count": len(edges), "edges": edges}) @app.route("/api/graph/edges", methods=["POST"]) def api_graph_edges_create(): data = request.get_json() source_h = data.get("source_h") target_h = data.get("target_h") weight = data.get("weight", 0.5) edge_type = data.get("type", "manual") if not source_h or not target_h: return jsonify({"error": "source_h and target_h required"}), 400 conn = get_db() cur = conn.cursor() cur.execute("INSERT INTO graph (h_a, h_b, weight, edge_type) VALUES (%s, %s, %s, %s) RETURNING id", (source_h, target_h, weight, edge_type)) new_id = cur.fetchone()[0] conn.commit() cur.close() conn.close() return jsonify({"id": new_id, "source_h": source_h, "target_h": target_h, "weight": weight, "type": edge_type}) @app.route("/api/graph/edges/", methods=["DELETE"]) def api_graph_edges_delete(edge_id): conn = get_db() cur = conn.cursor() cur.execute("DELETE FROM graph WHERE id = %s", (edge_id,)) conn.commit() cur.close() conn.close() return jsonify({"deleted": edge_id})