feat(gitea): G1 DB schema + 实例 CRUD + 连接测试
新增 gitea_instances / gitea_repos 两张表(db.rs migrate); 实现 save/list/delete/update/test_connection 命令(ureq 调 GET /api/v1/user 验证 token); gitea_list_linked_repos / gitea_get_repo_link 查询绑定状态; 前端 commands.ts 完整类型封装;4 个单元测试全绿。
This commit is contained in:
parent
6fdac2ec2f
commit
dcbf1f9355
@ -375,8 +375,8 @@ modules:
|
|||||||
- id: gitea-integration
|
- id: gitea-integration
|
||||||
name: Gitea 集成
|
name: Gitea 集成
|
||||||
area: backend
|
area: backend
|
||||||
status: planned
|
status: in_progress
|
||||||
progress: 0
|
progress: 20
|
||||||
position: [3500, 150]
|
position: [3500, 150]
|
||||||
updated: 2026-06-30
|
updated: 2026-06-30
|
||||||
|
|
||||||
|
|||||||
@ -106,8 +106,8 @@ Gitea push → POST /webhook/gitea/:project_id
|
|||||||
|
|
||||||
## 任务卡(可执行,拆自 2026-06-30 /architect)
|
## 任务卡(可执行,拆自 2026-06-30 /architect)
|
||||||
|
|
||||||
### 📋 G1. DB schema + Gitea 实例 CRUD
|
### ✅ G1. DB schema + Gitea 实例 CRUD
|
||||||
- status: todo
|
- status: done
|
||||||
- complexity: M
|
- complexity: M
|
||||||
- files: src-tauri/src/db.rs, src-tauri/src/commands/gitea.rs, src-tauri/src/lib.rs, src/lib/commands.ts
|
- files: src-tauri/src/db.rs, src-tauri/src/commands/gitea.rs, src-tauri/src/lib.rs, src/lib/commands.ts
|
||||||
- acceptance: gitea_instances + gitea_repos 表建好(migrate);save/list/delete/test_connection 命令可调;test_connection 调 GET /api/v1/user 返回 Gitea 用户名验证 token 有效性
|
- acceptance: gitea_instances + gitea_repos 表建好(migrate);save/list/delete/test_connection 命令可调;test_connection 调 GET /api/v1/user 返回 Gitea 用户名验证 token 有效性
|
||||||
|
|||||||
337
src-tauri/src/commands/gitea.rs
Normal file
337
src-tauri/src/commands/gitea.rs
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
//! Gitea 集成——控制面:实例管理 + 仓库操作 + webhook 注册。
|
||||||
|
//!
|
||||||
|
//! 观测面(webhook 接收端)在 mcp_server.rs 实现。
|
||||||
|
|
||||||
|
use rusqlite::params;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
// ── 数据结构 ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct GiteaInstance {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub url: String,
|
||||||
|
pub access_token: String,
|
||||||
|
pub is_default: bool,
|
||||||
|
pub created_at: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct SaveGiteaInstanceInput {
|
||||||
|
pub name: String,
|
||||||
|
pub url: String,
|
||||||
|
pub access_token: String,
|
||||||
|
pub is_default: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct GiteaRepo {
|
||||||
|
pub id: String,
|
||||||
|
pub instance_id: String,
|
||||||
|
pub project_id: String,
|
||||||
|
pub gitea_owner: String,
|
||||||
|
pub gitea_repo_name: String,
|
||||||
|
pub clone_url: Option<String>,
|
||||||
|
pub webhook_secret: Option<String>,
|
||||||
|
pub webhook_id: Option<i64>,
|
||||||
|
pub linked_at: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct GiteaUser {
|
||||||
|
pub login: String,
|
||||||
|
pub full_name: String,
|
||||||
|
pub email: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 辅助函数 ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn row_to_instance(row: &rusqlite::Row<'_>) -> rusqlite::Result<GiteaInstance> {
|
||||||
|
Ok(GiteaInstance {
|
||||||
|
id: row.get(0)?,
|
||||||
|
name: row.get(1)?,
|
||||||
|
url: row.get(2)?,
|
||||||
|
access_token: row.get(3)?,
|
||||||
|
is_default: row.get::<_, i64>(4)? != 0,
|
||||||
|
created_at: row.get(5)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row_to_repo(row: &rusqlite::Row<'_>) -> rusqlite::Result<GiteaRepo> {
|
||||||
|
Ok(GiteaRepo {
|
||||||
|
id: row.get(0)?,
|
||||||
|
instance_id: row.get(1)?,
|
||||||
|
project_id: row.get(2)?,
|
||||||
|
gitea_owner: row.get(3)?,
|
||||||
|
gitea_repo_name: row.get(4)?,
|
||||||
|
clone_url: row.get(5)?,
|
||||||
|
webhook_secret: row.get(6)?,
|
||||||
|
webhook_id: row.get(7)?,
|
||||||
|
linked_at: row.get(8)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 规范化 Gitea URL:去掉末尾 /
|
||||||
|
fn normalize_url(url: &str) -> String {
|
||||||
|
url.trim_end_matches('/').to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 实例管理命令 ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// 保存 Gitea 实例(新增或覆盖同名实例)
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn gitea_save_instance(input: SaveGiteaInstanceInput) -> Result<GiteaInstance, String> {
|
||||||
|
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
|
||||||
|
let id = uuid::Uuid::new_v4().to_string();
|
||||||
|
let url = normalize_url(&input.url);
|
||||||
|
let is_default = input.is_default.unwrap_or(false) as i64;
|
||||||
|
|
||||||
|
// 若设为默认,先取消其他默认
|
||||||
|
if is_default != 0 {
|
||||||
|
let _ = conn.execute("UPDATE gitea_instances SET is_default = 0", []);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO gitea_instances (id, name, url, access_token, is_default)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5)",
|
||||||
|
params![id, input.name, url, input.access_token, is_default],
|
||||||
|
).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
conn.query_row(
|
||||||
|
"SELECT id, name, url, access_token, is_default, created_at FROM gitea_instances WHERE id = ?1",
|
||||||
|
params![id],
|
||||||
|
row_to_instance,
|
||||||
|
).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 列出所有 Gitea 实例
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn gitea_list_instances() -> Result<Vec<GiteaInstance>, String> {
|
||||||
|
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
|
||||||
|
let mut stmt = conn.prepare(
|
||||||
|
"SELECT id, name, url, access_token, is_default, created_at FROM gitea_instances ORDER BY is_default DESC, created_at ASC"
|
||||||
|
).map_err(|e| e.to_string())?;
|
||||||
|
let rows = stmt.query_map([], row_to_instance)
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.collect::<rusqlite::Result<Vec<_>>>()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 删除 Gitea 实例(级联删除 gitea_repos)
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn gitea_delete_instance(id: String) -> Result<(), String> {
|
||||||
|
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
|
||||||
|
conn.execute("DELETE FROM gitea_instances WHERE id = ?1", params![id])
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新 Gitea 实例(仅更新非空字段)
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn gitea_update_instance(
|
||||||
|
id: String,
|
||||||
|
name: Option<String>,
|
||||||
|
url: Option<String>,
|
||||||
|
access_token: Option<String>,
|
||||||
|
is_default: Option<bool>,
|
||||||
|
) -> Result<GiteaInstance, String> {
|
||||||
|
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
if let Some(ref _n) = name {
|
||||||
|
conn.execute("UPDATE gitea_instances SET name = ?1 WHERE id = ?2", params![_n, id])
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
if let Some(ref u) = url {
|
||||||
|
let u = normalize_url(u);
|
||||||
|
conn.execute("UPDATE gitea_instances SET url = ?1 WHERE id = ?2", params![u, id])
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
if let Some(ref t) = access_token {
|
||||||
|
conn.execute("UPDATE gitea_instances SET access_token = ?1 WHERE id = ?2", params![t, id])
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
if let Some(def) = is_default {
|
||||||
|
if def {
|
||||||
|
let _ = conn.execute("UPDATE gitea_instances SET is_default = 0", []);
|
||||||
|
}
|
||||||
|
conn.execute("UPDATE gitea_instances SET is_default = ?1 WHERE id = ?2",
|
||||||
|
params![def as i64, id])
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.query_row(
|
||||||
|
"SELECT id, name, url, access_token, is_default, created_at FROM gitea_instances WHERE id = ?1",
|
||||||
|
params![id],
|
||||||
|
row_to_instance,
|
||||||
|
).map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 测试 Gitea 连接:调 GET /api/v1/user,返回用户信息验证 token 有效性
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn gitea_test_connection(instance_id: String) -> Result<GiteaUser, String> {
|
||||||
|
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
|
||||||
|
let (url, token): (String, String) = conn.query_row(
|
||||||
|
"SELECT url, access_token FROM gitea_instances WHERE id = ?1",
|
||||||
|
params![instance_id],
|
||||||
|
|r| Ok((r.get(0)?, r.get(1)?)),
|
||||||
|
).map_err(|_| "实例不存在".to_string())?;
|
||||||
|
|
||||||
|
gitea_api_get_user(&url, &token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Gitea API 辅助 ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// GET /api/v1/user — 验证 token 并返回用户信息
|
||||||
|
pub fn gitea_api_get_user(base_url: &str, token: &str) -> Result<GiteaUser, String> {
|
||||||
|
let url = format!("{}/api/v1/user", base_url);
|
||||||
|
let resp = ureq::get(&url)
|
||||||
|
.set("Authorization", &format!("token {token}"))
|
||||||
|
.set("Content-Type", "application/json")
|
||||||
|
.call()
|
||||||
|
.map_err(|e| format!("Gitea 连接失败: {e}"))?;
|
||||||
|
|
||||||
|
let json: serde_json::Value = resp.into_json()
|
||||||
|
.map_err(|e| format!("解析响应失败: {e}"))?;
|
||||||
|
|
||||||
|
let login = json["login"].as_str()
|
||||||
|
.ok_or_else(|| "token 无效或权限不足".to_string())?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Ok(GiteaUser {
|
||||||
|
login,
|
||||||
|
full_name: json["full_name"].as_str().unwrap_or("").to_string(),
|
||||||
|
email: json["email"].as_str().unwrap_or("").to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET /api/v1/repos/:owner/:repo — 获取仓库信息(G2 仓库绑定时使用)
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn gitea_api_get_repo(
|
||||||
|
base_url: &str,
|
||||||
|
token: &str,
|
||||||
|
owner: &str,
|
||||||
|
repo: &str,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let url = format!("{}/api/v1/repos/{owner}/{repo}", base_url);
|
||||||
|
let resp = ureq::get(&url)
|
||||||
|
.set("Authorization", &format!("token {token}"))
|
||||||
|
.call()
|
||||||
|
.map_err(|e| format!("获取仓库信息失败: {e}"))?;
|
||||||
|
resp.into_json().map_err(|e| format!("解析失败: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 仓库绑定查询命令 ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// 查询所有已绑定的仓库(含实例名)
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn gitea_list_linked_repos() -> Result<Vec<serde_json::Value>, String> {
|
||||||
|
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
|
||||||
|
let mut stmt = conn.prepare(
|
||||||
|
"SELECT gr.id, gr.instance_id, gi.name as instance_name, gi.url as instance_url,
|
||||||
|
gr.project_id, gr.gitea_owner, gr.gitea_repo_name,
|
||||||
|
gr.clone_url, gr.webhook_secret, gr.webhook_id, gr.linked_at
|
||||||
|
FROM gitea_repos gr
|
||||||
|
JOIN gitea_instances gi ON gi.id = gr.instance_id
|
||||||
|
ORDER BY gr.linked_at DESC",
|
||||||
|
).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let rows = stmt.query_map([], |row| {
|
||||||
|
Ok(serde_json::json!({
|
||||||
|
"id": row.get::<_, String>(0)?,
|
||||||
|
"instance_id": row.get::<_, String>(1)?,
|
||||||
|
"instance_name": row.get::<_, String>(2)?,
|
||||||
|
"instance_url": row.get::<_, String>(3)?,
|
||||||
|
"project_id": row.get::<_, String>(4)?,
|
||||||
|
"gitea_owner": row.get::<_, String>(5)?,
|
||||||
|
"gitea_repo_name": row.get::<_, String>(6)?,
|
||||||
|
"clone_url": row.get::<_, Option<String>>(7)?,
|
||||||
|
"has_webhook": row.get::<_, Option<String>>(8)?.is_some(),
|
||||||
|
"webhook_id": row.get::<_, Option<i64>>(9)?,
|
||||||
|
"linked_at": row.get::<_, Option<String>>(10)?,
|
||||||
|
}))
|
||||||
|
}).map_err(|e| e.to_string())?
|
||||||
|
.collect::<rusqlite::Result<Vec<_>>>()
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查询单个项目的 Gitea 绑定
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn gitea_get_repo_link(project_id: String) -> Result<Option<GiteaRepo>, String> {
|
||||||
|
let conn = crate::db::pool().get().map_err(|e| e.to_string())?;
|
||||||
|
match conn.query_row(
|
||||||
|
"SELECT id, instance_id, project_id, gitea_owner, gitea_repo_name,
|
||||||
|
clone_url, webhook_secret, webhook_id, linked_at
|
||||||
|
FROM gitea_repos WHERE project_id = ?1",
|
||||||
|
params![project_id],
|
||||||
|
row_to_repo,
|
||||||
|
) {
|
||||||
|
Ok(r) => Ok(Some(r)),
|
||||||
|
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||||
|
Err(e) => Err(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn normalize_url_strips_trailing_slash() {
|
||||||
|
assert_eq!(normalize_url("https://gitea.example.com/"), "https://gitea.example.com");
|
||||||
|
assert_eq!(normalize_url("https://gitea.example.com"), "https://gitea.example.com");
|
||||||
|
assert_eq!(normalize_url("https://gitea.example.com///"), "https://gitea.example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gitea_tables_exist_after_migrate() {
|
||||||
|
let conn = crate::db::conn_with_schema();
|
||||||
|
let count: i64 = conn.query_row(
|
||||||
|
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name IN ('gitea_instances','gitea_repos')",
|
||||||
|
[],
|
||||||
|
|r| r.get(0),
|
||||||
|
).unwrap();
|
||||||
|
assert_eq!(count, 2, "gitea_instances 或 gitea_repos 表未建");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn save_and_list_instance() {
|
||||||
|
let conn = crate::db::conn_with_schema();
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO gitea_instances (id, name, url, access_token, is_default)
|
||||||
|
VALUES ('inst-1', '测试实例', 'https://gitea.test', 'token123', 0)",
|
||||||
|
[],
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let count: i64 = conn.query_row(
|
||||||
|
"SELECT COUNT(*) FROM gitea_instances WHERE id = 'inst-1'",
|
||||||
|
[],
|
||||||
|
|r| r.get(0),
|
||||||
|
).unwrap();
|
||||||
|
assert_eq!(count, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unique_index_on_project_id() {
|
||||||
|
let conn = crate::db::conn_with_schema();
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO gitea_instances (id, name, url, access_token) VALUES ('i1', 'a', 'u', 't')",
|
||||||
|
[],
|
||||||
|
).unwrap();
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO gitea_repos (id, instance_id, project_id, gitea_owner, gitea_repo_name)
|
||||||
|
VALUES ('r1', 'i1', 'proj-uuid-1', 'owner', 'repo')",
|
||||||
|
[],
|
||||||
|
).unwrap();
|
||||||
|
// 同一 project_id 第二次应失败
|
||||||
|
let result = conn.execute(
|
||||||
|
"INSERT INTO gitea_repos (id, instance_id, project_id, gitea_owner, gitea_repo_name)
|
||||||
|
VALUES ('r2', 'i1', 'proj-uuid-1', 'owner', 'repo2')",
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
assert!(result.is_err(), "project_id 唯一索引未生效");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -35,3 +35,4 @@ pub mod cloud_products;
|
|||||||
pub mod cloud_db_databases;
|
pub mod cloud_db_databases;
|
||||||
pub mod docker_apps;
|
pub mod docker_apps;
|
||||||
pub mod agent_infra;
|
pub mod agent_infra;
|
||||||
|
pub mod gitea;
|
||||||
|
|||||||
@ -844,6 +844,31 @@ fn migrate(conn: &rusqlite::Connection) {
|
|||||||
checked_at TEXT NOT NULL DEFAULT (datetime('now'))
|
checked_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
);
|
);
|
||||||
").expect("create doc_freshness_cache table");
|
").expect("create doc_freshness_cache table");
|
||||||
|
|
||||||
|
// 增量迁移:Gitea 实例 + 项目-仓库绑定
|
||||||
|
conn.execute_batch("
|
||||||
|
CREATE TABLE IF NOT EXISTS gitea_instances (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
access_token TEXT NOT NULL,
|
||||||
|
is_default INTEGER NOT NULL DEFAULT 0,
|
||||||
|
created_at TEXT DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS gitea_repos (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
instance_id TEXT NOT NULL REFERENCES gitea_instances(id) ON DELETE CASCADE,
|
||||||
|
project_id TEXT NOT NULL,
|
||||||
|
gitea_owner TEXT NOT NULL,
|
||||||
|
gitea_repo_name TEXT NOT NULL,
|
||||||
|
clone_url TEXT,
|
||||||
|
webhook_secret TEXT,
|
||||||
|
webhook_id INTEGER,
|
||||||
|
linked_at TEXT DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_gitea_repos_project ON gitea_repos(project_id);
|
||||||
|
").expect("create gitea tables");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@ -335,6 +335,14 @@ pub fn run() {
|
|||||||
commands::service_config::add_service_config,
|
commands::service_config::add_service_config,
|
||||||
commands::service_config::edit_service_config,
|
commands::service_config::edit_service_config,
|
||||||
commands::service_config::delete_service_config,
|
commands::service_config::delete_service_config,
|
||||||
|
// gitea integration
|
||||||
|
commands::gitea::gitea_save_instance,
|
||||||
|
commands::gitea::gitea_list_instances,
|
||||||
|
commands::gitea::gitea_delete_instance,
|
||||||
|
commands::gitea::gitea_update_instance,
|
||||||
|
commands::gitea::gitea_test_connection,
|
||||||
|
commands::gitea::gitea_list_linked_repos,
|
||||||
|
commands::gitea::gitea_get_repo_link,
|
||||||
// beast status (read-only)
|
// beast status (read-only)
|
||||||
get_beast_global_status,
|
get_beast_global_status,
|
||||||
// source library
|
// source library
|
||||||
|
|||||||
@ -1721,6 +1721,67 @@ export interface CommitHealthWithName extends ProjectCommitHealth {
|
|||||||
export const getAllCommitHealth = () =>
|
export const getAllCommitHealth = () =>
|
||||||
invoke<CommitHealthWithName[]>("get_all_commit_health");
|
invoke<CommitHealthWithName[]>("get_all_commit_health");
|
||||||
|
|
||||||
|
// ── Gitea Integration ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface GiteaInstance {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
access_token: string;
|
||||||
|
is_default: boolean;
|
||||||
|
created_at?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SaveGiteaInstanceInput {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
access_token: string;
|
||||||
|
is_default?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GiteaUser {
|
||||||
|
login: string;
|
||||||
|
full_name: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GiteaLinkedRepo {
|
||||||
|
id: string;
|
||||||
|
instance_id: string;
|
||||||
|
instance_name: string;
|
||||||
|
instance_url: string;
|
||||||
|
project_id: string;
|
||||||
|
gitea_owner: string;
|
||||||
|
gitea_repo_name: string;
|
||||||
|
clone_url?: string;
|
||||||
|
has_webhook: boolean;
|
||||||
|
webhook_id?: number;
|
||||||
|
linked_at?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const giteaSaveInstance = (input: SaveGiteaInstanceInput) =>
|
||||||
|
invoke<GiteaInstance>("gitea_save_instance", { input });
|
||||||
|
|
||||||
|
export const giteaListInstances = () =>
|
||||||
|
invoke<GiteaInstance[]>("gitea_list_instances");
|
||||||
|
|
||||||
|
export const giteaDeleteInstance = (id: string) =>
|
||||||
|
invoke<void>("gitea_delete_instance", { id });
|
||||||
|
|
||||||
|
export const giteaUpdateInstance = (
|
||||||
|
id: string,
|
||||||
|
fields: { name?: string; url?: string; access_token?: string; is_default?: boolean }
|
||||||
|
) => invoke<GiteaInstance>("gitea_update_instance", { id, ...fields });
|
||||||
|
|
||||||
|
export const giteaTestConnection = (instanceId: string) =>
|
||||||
|
invoke<GiteaUser>("gitea_test_connection", { instanceId });
|
||||||
|
|
||||||
|
export const giteaListLinkedRepos = () =>
|
||||||
|
invoke<GiteaLinkedRepo[]>("gitea_list_linked_repos");
|
||||||
|
|
||||||
|
export const giteaGetRepoLink = (projectId: string) =>
|
||||||
|
invoke<GiteaLinkedRepo | null>("gitea_get_repo_link", { projectId });
|
||||||
|
|
||||||
// ── Beast Global Status (read-only) ───────────────────────────────────────────
|
// ── Beast Global Status (read-only) ───────────────────────────────────────────
|
||||||
|
|
||||||
export interface BeastGlobalStatus {
|
export interface BeastGlobalStatus {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user