Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
187 lines
7.9 KiB
Plaintext
187 lines
7.9 KiB
Plaintext
# Gitea API Client — Library Package
|
|
#
|
|
# Shared Gitea client providing connection-backed API helpers.
|
|
# Consumers call lib.require("gitea-client") and pass a conn dict
|
|
# obtained from connections.get("gitea").
|
|
#
|
|
# Entry points:
|
|
# on_request(req) — REST API routes for browser-side consumers
|
|
# Exported functions — Starlark consumers via lib.require()
|
|
#
|
|
# Modules available (via library permissions):
|
|
# http — outbound HTTP (api.http)
|
|
# connections — credential resolution (connections.read)
|
|
# db — ext_gitea_client_* tables (db.read, db.write)
|
|
# json — encode/decode (universal)
|
|
|
|
load("star/http_helpers.star", "gitea_get", "gitea_post", "gitea_patch", "auth_headers", "resp")
|
|
load("star/repos.star", _repos_get = "get_repos")
|
|
load("star/issues.star", _issues_get = "get_issues", _issue_get = "get_issue", _issue_create = "create_issue", _issue_update = "update_issue", _comment_add = "add_comment")
|
|
load("star/ci.star", _ci_get = "get_ci_status")
|
|
|
|
# Re-export loaded functions so lib.require() can find them in globals.
|
|
# Starlark load() names are file-local; explicit assignment makes them global.
|
|
get_repos = _repos_get
|
|
get_issues = _issues_get
|
|
get_issue = _issue_get
|
|
create_issue = _issue_create
|
|
update_issue = _issue_update
|
|
add_comment = _comment_add
|
|
get_ci_status = _ci_get
|
|
|
|
|
|
# ═══════════════════════════════════════════════
|
|
# REST API routes (/s/gitea-client/api/*)
|
|
# ═══════════════════════════════════════════════
|
|
|
|
def on_request(req):
|
|
path = req["path"]
|
|
method = req["method"]
|
|
|
|
conn = _resolve_conn()
|
|
if conn == None:
|
|
return resp(400, {"error": "no gitea connection configured"})
|
|
|
|
if method == "GET" and path == "/repos":
|
|
repos = get_repos(conn)
|
|
if repos == None:
|
|
return resp(502, {"error": "gitea request failed"})
|
|
return resp(200, {"data": repos})
|
|
|
|
elif method == "GET" and path == "/issues":
|
|
q = req.get("query", {})
|
|
owner = _str(q.get("owner", ""))
|
|
repo = _str(q.get("repo", ""))
|
|
state = _str(q.get("state", "")) or "open"
|
|
if not owner or not repo:
|
|
return resp(400, {"error": "owner and repo query params required"})
|
|
issues = get_issues(conn, owner, repo, state)
|
|
if issues == None:
|
|
return resp(502, {"error": "gitea request failed"})
|
|
return resp(200, {"data": issues, "count": len(issues)})
|
|
|
|
elif method == "GET" and path.startswith("/issues/"):
|
|
parts = path[len("/issues/"):].split("/", 2)
|
|
if len(parts) < 3:
|
|
return resp(400, {"error": "path must be /issues/:owner/:repo/:number"})
|
|
owner, repo, num = parts[0], parts[1], int(parts[2])
|
|
issue = get_issue(conn, owner, repo, num)
|
|
if issue == None:
|
|
return resp(404, {"error": "issue not found"})
|
|
return resp(200, issue)
|
|
|
|
elif method == "POST" and path == "/issues":
|
|
body = json.decode(req.get("body", "{}"))
|
|
result = create_issue(conn, body.get("owner", ""), body.get("repo", ""),
|
|
body.get("title", ""), body.get("body", ""),
|
|
body.get("labels", ""))
|
|
if result == None:
|
|
return resp(502, {"error": "failed to create issue"})
|
|
return resp(201, result)
|
|
|
|
elif method == "GET" and path == "/prs":
|
|
q = req.get("query", {})
|
|
owner = _str(q.get("owner", ""))
|
|
repo = _str(q.get("repo", ""))
|
|
state = _str(q.get("state", "")) or "open"
|
|
if not owner or not repo:
|
|
return resp(400, {"error": "owner and repo query params required"})
|
|
prs = get_prs(conn, owner, repo, state)
|
|
if prs == None:
|
|
return resp(502, {"error": "gitea request failed"})
|
|
return resp(200, {"data": prs, "count": len(prs)})
|
|
|
|
elif method == "GET" and path.startswith("/ci/"):
|
|
parts = path[len("/ci/"):].split("/", 2)
|
|
if len(parts) < 3:
|
|
return resp(400, {"error": "path must be /ci/:owner/:repo/:ref"})
|
|
owner, repo, ref = parts[0], parts[1], parts[2]
|
|
result = get_ci_status(conn, owner, repo, ref)
|
|
if result == None:
|
|
return resp(502, {"error": "CI status request failed"})
|
|
return resp(200, result)
|
|
|
|
elif method == "POST" and path == "/sync":
|
|
return _handle_sync(conn, req)
|
|
|
|
return resp(404, {"error": "not found"})
|
|
|
|
|
|
# ═══════════════════════════════════════════════
|
|
# PR helper (not large enough for its own file)
|
|
# ═══════════════════════════════════════════════
|
|
|
|
def get_prs(conn, owner, repo, state):
|
|
"""List pull requests for a repository."""
|
|
state = state or "open"
|
|
path = "/repos/" + owner + "/" + repo + "/pulls?state=" + state + "&limit=50"
|
|
data = gitea_get(conn, path)
|
|
if data == None:
|
|
return None
|
|
items = []
|
|
for p in data:
|
|
items.append({
|
|
"number": p.get("number", 0),
|
|
"title": p.get("title", ""),
|
|
"state": p.get("state", ""),
|
|
"head": p.get("head", {}).get("ref", ""),
|
|
"base": p.get("base", {}).get("ref", ""),
|
|
"user": (p.get("user") or {}).get("login", ""),
|
|
"created_at": p.get("created_at", ""),
|
|
"html_url": p.get("html_url", ""),
|
|
"mergeable": p.get("mergeable", None),
|
|
})
|
|
return items
|
|
|
|
|
|
# ═══════════════════════════════════════════════
|
|
# Sync handler — cache repos/issues to db tables
|
|
# ═══════════════════════════════════════════════
|
|
|
|
def _handle_sync(conn, req):
|
|
"""Sync repos and issues into local cache tables."""
|
|
repos = get_repos(conn)
|
|
if repos == None:
|
|
return resp(502, {"error": "failed to fetch repos"})
|
|
|
|
conn_id = conn.get("id", "default")
|
|
|
|
# Clear and re-insert repos
|
|
db.exec("DELETE FROM repos WHERE connection_id = ?", [conn_id])
|
|
for r in repos:
|
|
db.exec(
|
|
"INSERT INTO repos (connection_id, full_name, owner, name, description, html_url, open_issues, synced_at) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))",
|
|
[conn_id, r["full_name"], r["owner"], r["name"], r.get("description", ""), r.get("html_url", ""), r.get("open_issues", 0)]
|
|
)
|
|
|
|
# Sync open issues for each repo
|
|
issue_count = 0
|
|
db.exec("DELETE FROM issues WHERE connection_id = ?", [conn_id])
|
|
for r in repos:
|
|
issues = get_issues(conn, r["owner"], r["name"], "open")
|
|
if issues == None:
|
|
continue
|
|
for i in issues:
|
|
labels_str = ",".join(i.get("labels", []))
|
|
db.exec(
|
|
"INSERT INTO issues (connection_id, repo_full_name, number, title, state, labels, assignee, body, created_at, synced_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))",
|
|
[conn_id, r["full_name"], i["number"], i["title"], i["state"], labels_str, i.get("assignee", ""), "", i.get("created_at", "")]
|
|
)
|
|
issue_count += 1
|
|
|
|
return resp(200, {"repos": len(repos), "issues": issue_count})
|
|
|
|
|
|
# ═══════════════════════════════════════════════
|
|
# Helpers
|
|
# ═══════════════════════════════════════════════
|
|
|
|
def _resolve_conn():
|
|
"""Resolve the first available gitea connection."""
|
|
return connections.get("gitea")
|
|
|
|
def _str(v):
|
|
if v == None:
|
|
return ""
|
|
return str(v)
|