GitHub 仓库选择菜单:安装仓库列表 + PATCH 补仓库 + 控制台下拉

This commit is contained in:
host committed 2026-10-27 05:43:56 +08:00
1 parent 5ba4d35839
commit 6ba671e051
4 files changed
+343

No files matched your search

+85
View File
@@ -930,6 +930,11 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
def do_DELETE(self):
self._dispatch("DELETE")
def do_PATCH(self):
# PATCH 之前没有入口(BaseHTTPRequestHandler 默认回 501),
# 项目补仓库是第一个 PATCH 路由;与其它方法一样走 _dispatch。
self._dispatch("PATCH")
def _dispatch(self, method):
parsed = urlparse(self.path)
path = parsed.path
@@ -1099,6 +1104,8 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
self._models_overview(user_id)
elif path == "/relay/models" and method == "GET":
self._relay_models(user_id)
elif path == "/github/repos" and method == "GET":
self._github_repos_list(user_id)
elif path.startswith("/projects/"):
self._dispatch_project(user_id, method, path, query)
elif path == "/environments" and method == "POST":
@@ -1420,6 +1427,81 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
store.audit(user_id, project_id, "create_project", name)
self._send_json(201, project)
def _update_project(self, user_id, project_id):
"""PATCH /projects/{id}:项目 owner 可改 repo_url(建后补仓
库,解“建项目时没填仓库、之后建环境一直 400”的死结)。
repo_url 缺 key 时 400;null 或空串表示清空;字符串走与
创建时同一套 validate_repo_url 校验(带凭证的 URL 拒绝)。
只改 projects 行,不动已建环境的快照 repo_url——已建环境
仍用建环境时的仓库,新环境才用新值。改完记 update_project
审计。"""
err = self._check_project(user_id, project_id, "manage_project")
if err:
self._send_json(err[0], {"error": err[1]})
return
body, jerr = self._read_json()
if jerr:
self._send_json(400, {"error": jerr})
return
if "repo_url" not in body:
self._send_json(400, {"error": "repo_url required"})
return
repo_url = body.get("repo_url")
if repo_url is not None and repo_url != "":
if not isinstance(repo_url, str):
self._send_json(400, {"error": "repo_url must be a string"})
return
try:
repo_url = validate_repo_url(repo_url)
except ValueError as exc:
self._send_json(400, {"error": str(exc)})
return
else:
repo_url = None
conn = self._db()
try:
row = conn.execute(
"SELECT id, name, owner_id, created_at, repo_url, auto_pr"
" FROM projects WHERE id = ?",
(project_id,)).fetchone()
if row is None:
self._send_json(404, {"error": "project not found"})
return
conn.execute(
"UPDATE projects SET repo_url = ? WHERE id = ?",
(repo_url, project_id))
conn.commit()
row = conn.execute(
"SELECT id, name, owner_id, created_at, repo_url, auto_pr"
" FROM projects WHERE id = ?",
(project_id,)).fetchone()
finally:
conn.close()
with _auth_store(self.server.db_path) as store:
store.audit(user_id, project_id, "update_project",
repo_url or "(cleared)")
self._send_json(200, {"project": _project_to_dict(row)})
def _github_repos_list(self, user_id):
"""GET /github/repos:列出本服务 GitHub App 安装记录里的仓
库(installation_repositories 事件同步进 github_app_
installations 表),给控制台建项目/补仓库时的下拉菜单用。
任何已认证用户可列(只含仓库名,不含密钥);选了哪个仓库
只影响调用者自己有管理权的项目(PATCH 仍按 owner 校验)。
手填 URL 的能力不变——没进 App 安装的公开仓库仍可手动填。"""
conn = self._db()
try:
rows = list_installations(conn)
finally:
conn.close()
repos = [{
"installation_id": r["installation_id"],
"account_login": r["account_login"],
"repo_full_name": r["repo_full_name"],
"clone_url": "https://github.com/%s.git" % r["repo_full_name"],
} for r in rows]
self._send_json(200, {"repos": repos})
def _list_projects(self, user_id):
conn = self._db()
try:
@@ -1467,6 +1549,9 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
if not project_id:
self._send_json(404, {"error": "not found"})
return
if len(parts) == 1 and method == "PATCH":
self._update_project(user_id, project_id)
return
# /projects/{id}/memory
if len(parts) == 2 and parts[1] == "memory":
if method == "POST":