Ihorog commited on
Commit
c0addcf
·
verified ·
1 Parent(s): 626aea8

Create bridge.py

Browse files
Files changed (1) hide show
  1. bridge.py +139 -0
bridge.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, json, uuid, sqlite3
2
+ from datetime import datetime, timezone
3
+ from flask import Blueprint, request, jsonify
4
+ from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
5
+ from functools import wraps
6
+
7
+ bridge = Blueprint("bridge", __name__)
8
+
9
+ DB_PATH = os.environ.get("CI_LEGEND_DB", "/data/legend.db")
10
+ API_KEY = os.environ.get("CI_API_KEY", "dev-key-change-me")
11
+
12
+ REQS = Counter("ci_bridge_requests_total", "Total requests", ["route","code"])
13
+ LAT = Histogram("ci_bridge_latency_seconds", "Latency", ["route"])
14
+
15
+ # --- tiny SQLite ---
16
+ def _db():
17
+ os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
18
+ con = sqlite3.connect(DB_PATH)
19
+ con.execute("""CREATE TABLE IF NOT EXISTS legend (
20
+ id TEXT PRIMARY KEY,
21
+ title TEXT,
22
+ tags TEXT,
23
+ body TEXT,
24
+ created_at TEXT,
25
+ updated_at TEXT
26
+ )""")
27
+ return con
28
+
29
+ def _now(): return datetime.now(timezone.utc).isoformat()
30
+ def _trace(): return f"ci-{uuid.uuid4().hex[:12]}"
31
+
32
+ def _auth():
33
+ hdr = request.headers.get("Authorization","")
34
+ return hdr.startswith("Bearer ") and hdr.split(" ",1)[1] == API_KEY
35
+
36
+ def secured(fn):
37
+ @wraps(fn)
38
+ def wrap(*a, **k):
39
+ route = request.path
40
+ with LAT.labels(route).time():
41
+ if not _auth():
42
+ REQS.labels(route,"401").inc()
43
+ return jsonify({"status":"unauthorized"}), 401
44
+ resp = fn(*a, **k)
45
+ try:
46
+ code = resp[1] if isinstance(resp, tuple) else 200
47
+ except Exception:
48
+ code = 200
49
+ REQS.labels(route,str(code)).inc()
50
+ return resp
51
+ return wrap
52
+
53
+ # --- Legend CRUD ---
54
+ def legend_upsert(item):
55
+ con = _db()
56
+ now = _now()
57
+ item_id = item.get("id") or uuid.uuid4().hex
58
+ con.execute(
59
+ "INSERT OR REPLACE INTO legend (id,title,tags,body,created_at,updated_at) "
60
+ "VALUES (?,?,?,?,COALESCE((SELECT created_at FROM legend WHERE id=?),?),?)",
61
+ (item_id, item.get("title",""), json.dumps(item.get("tags",[]), ensure_ascii=False),
62
+ item.get("body",""), item_id, now, now))
63
+ con.commit(); con.close()
64
+ return {"id": item_id}
65
+
66
+ def legend_get(item_id):
67
+ con = _db()
68
+ r = con.execute("SELECT id,title,tags,body,created_at,updated_at FROM legend WHERE id=?",(item_id,)).fetchone()
69
+ con.close()
70
+ if not r: return None
71
+ return {"id":r[0],"title":r[1],"tags":json.loads(r[2] or "[]"),"body":r[3],"created_at":r[4],"updated_at":r[5]}
72
+
73
+ def legend_search(q="", limit=10, tags=None):
74
+ con = _db()
75
+ rows = con.execute("SELECT id,title,tags,body,created_at,updated_at FROM legend").fetchall()
76
+ con.close()
77
+ q = (q or "").lower()
78
+ out=[]
79
+ for r in rows:
80
+ rec={"id":r[0],"title":r[1],"tags":json.loads(r[2] or "[]"),"body":r[3],"created_at":r[4],"updated_at":r[5]}
81
+ text=(rec["title"]+" "+rec["body"]).lower()
82
+ if q and q not in text: continue
83
+ if tags and not set(tags).issubset(set(rec["tags"])): continue
84
+ out.append(rec)
85
+ return out[:max(1,min(100,int(limit or 10)))]
86
+
87
+ # --- Endpoints ---
88
+ @bridge.route("/metrics")
89
+ def metrics():
90
+ return generate_latest(), 200, {"Content-Type": CONTENT_TYPE_LATEST}
91
+
92
+ @bridge.route("/api/command", methods=["POST"])
93
+ @secured
94
+ def api_command():
95
+ trace = _trace()
96
+ data = request.get_json(force=True, silent=True) or {}
97
+ intent = (data.get("intent") or "").strip()
98
+ payload= data.get("payload") or {}
99
+
100
+ if intent == "legend.upsert":
101
+ res = legend_upsert(payload)
102
+ return jsonify({"status":"ok","result":res,"trace_id":trace})
103
+ if intent == "legend.get":
104
+ res = legend_get(payload.get("id"))
105
+ return (jsonify({"status":"ok","result":res,"trace_id":trace}),200) if res else (jsonify({"status":"not_found","trace_id":trace}),404)
106
+ if intent == "legend.search":
107
+ res = legend_search(payload.get("q",""), payload.get("limit",10), payload.get("tags"))
108
+ return jsonify({"status":"ok","result":res,"trace_id":trace})
109
+
110
+ return jsonify({"status":"no_route","trace_id":trace}), 400
111
+
112
+ @bridge.route("/api/data/query", methods=["POST"])
113
+ @secured
114
+ def api_data_query():
115
+ trace=_trace()
116
+ q = request.get_json(force=True, silent=True) or {}
117
+ domain = q.get("domain","legend")
118
+ if domain=="legend":
119
+ res = legend_search(q.get("q",""), q.get("limit",10), q.get("tags"))
120
+ return jsonify({"status":"ok","result":res,"trace_id":trace})
121
+ return jsonify({"status":"unsupported_domain","trace_id":trace}),400
122
+
123
+ @bridge.route("/api/kazkar/legend", methods=["POST"])
124
+ @secured
125
+ def api_legend():
126
+ trace=_trace()
127
+ req = request.get_json(force=True, silent=True) or {}
128
+ action = (req.get("action") or "search").lower()
129
+ if action=="upsert": res=legend_upsert(req.get("item",{}))
130
+ elif action=="get": res=legend_get(req.get("id"))
131
+ elif action=="search":res=legend_search(req.get("q",""), req.get("limit",10), req.get("tags"))
132
+ else: return jsonify({"status":"bad_action","trace_id":trace}),400
133
+ return jsonify({"status":"ok","result":res,"trace_id":trace})
134
+
135
+ @bridge.route("/api/kazkar/legend/<item_id>", methods=["GET"])
136
+ def api_legend_get(item_id):
137
+ res = legend_get(item_id)
138
+ return (jsonify(res),200) if res else (jsonify({"status":"not_found"}),404)
139
+