routa_server/api/
github.rs1use axum::{
15 extract::Query,
16 routing::{get, post},
17 Json, Router,
18};
19use serde::Deserialize;
20
21use crate::state::AppState;
22
23pub fn router() -> Router<AppState> {
24 Router::new()
25 .route("/", get(list_workspaces))
26 .route("/import", post(import_repo))
27 .route("/tree", get(get_tree))
28 .route("/file", get(get_file))
29 .route("/search", get(search_files))
30 .route("/pr-comment", post(post_pr_comment))
31}
32
33async fn list_workspaces() -> Json<serde_json::Value> {
36 Json(serde_json::json!({ "workspaces": [] }))
38}
39
40#[derive(Debug, Deserialize)]
43struct ImportRequest {
44 owner: Option<String>,
45 repo: Option<String>,
46 #[serde(rename = "ref")]
47 git_ref: Option<String>,
48 url: Option<String>,
49}
50
51async fn import_repo(Json(body): Json<ImportRequest>) -> Json<serde_json::Value> {
52 let (owner, repo) = if let (Some(owner), Some(repo)) = (&body.owner, &body.repo) {
54 (owner.clone(), repo.clone())
55 } else if let Some(url) = &body.url {
56 let stripped = url
58 .trim_start_matches("https://github.com/")
59 .trim_start_matches("http://github.com/");
60 let parts: Vec<&str> = stripped.splitn(2, '/').collect();
61 if parts.len() == 2 {
62 (parts[0].to_string(), parts[1].to_string())
63 } else {
64 return Json(serde_json::json!({
65 "error": "Invalid GitHub URL. Expected: https://github.com/owner/repo or owner/repo",
66 "code": "BAD_REQUEST"
67 }));
68 }
69 } else {
70 return Json(serde_json::json!({
71 "error": "Missing 'owner' and 'repo' fields (or provide 'url')",
72 "code": "BAD_REQUEST"
73 }));
74 };
75
76 let git_ref = body.git_ref.as_deref().unwrap_or("HEAD");
77
78 Json(serde_json::json!({
81 "error": "GitHub virtual workspace import is not available in desktop mode. Use POST /api/clone to work with local repositories.",
82 "code": "NOT_IMPLEMENTED",
83 "hint": {
84 "owner": owner,
85 "repo": repo,
86 "ref": git_ref,
87 "alternative": "/api/clone"
88 }
89 }))
90}
91
92#[derive(Debug, Deserialize)]
95#[allow(dead_code)]
96struct RepoQuery {
97 owner: Option<String>,
98 repo: Option<String>,
99 #[serde(rename = "ref")]
100 git_ref: Option<String>,
101}
102
103fn not_imported(owner: &str, repo: &str) -> Json<serde_json::Value> {
104 Json(serde_json::json!({
105 "error": format!(
106 "Workspace not imported. POST /api/github/import first for {}/{}",
107 owner, repo
108 ),
109 "code": "NOT_FOUND"
110 }))
111}
112
113async fn get_tree(Query(q): Query<RepoQuery>) -> Json<serde_json::Value> {
116 let owner = q.owner.as_deref().unwrap_or("");
117 let repo = q.repo.as_deref().unwrap_or("");
118 if owner.is_empty() || repo.is_empty() {
119 return Json(serde_json::json!({
120 "error": "Missing 'owner' and 'repo' query parameters",
121 "code": "BAD_REQUEST"
122 }));
123 }
124 not_imported(owner, repo)
125}
126
127#[derive(Debug, Deserialize)]
130struct FileQuery {
131 owner: Option<String>,
132 repo: Option<String>,
133 path: Option<String>,
134 #[serde(rename = "ref")]
135 _git_ref: Option<String>,
136}
137
138async fn get_file(Query(q): Query<FileQuery>) -> Json<serde_json::Value> {
139 let owner = q.owner.as_deref().unwrap_or("");
140 let repo = q.repo.as_deref().unwrap_or("");
141 if owner.is_empty() || repo.is_empty() || q.path.is_none() {
142 return Json(serde_json::json!({
143 "error": "Missing 'owner', 'repo', or 'path' query parameters",
144 "code": "BAD_REQUEST"
145 }));
146 }
147 not_imported(owner, repo)
148}
149
150#[derive(Debug, Deserialize)]
153#[allow(dead_code)]
154struct SearchQuery {
155 owner: Option<String>,
156 repo: Option<String>,
157 q: Option<String>,
158 #[serde(rename = "ref")]
159 _git_ref: Option<String>,
160 limit: Option<usize>,
161}
162
163async fn search_files(Query(q): Query<SearchQuery>) -> Json<serde_json::Value> {
164 let owner = q.owner.as_deref().unwrap_or("");
165 let repo = q.repo.as_deref().unwrap_or("");
166 if owner.is_empty() || repo.is_empty() {
167 return Json(serde_json::json!({
168 "error": "Missing 'owner' and 'repo' query parameters",
169 "code": "BAD_REQUEST"
170 }));
171 }
172 Json(serde_json::json!({
174 "files": [],
175 "total": 0,
176 "query": q.q.as_deref().unwrap_or(""),
177 "note": "GitHub virtual workspaces are not available in desktop mode."
178 }))
179}
180
181#[derive(Debug, serde::Deserialize)]
184struct PrCommentRequest {
185 owner: Option<String>,
186 repo: Option<String>,
187 #[serde(rename = "prNumber")]
188 pr_number: Option<u64>,
189 body: Option<String>,
190}
191
192async fn post_pr_comment(Json(body): Json<PrCommentRequest>) -> Json<serde_json::Value> {
194 let owner = body.owner.as_deref().unwrap_or("");
195 let repo = body.repo.as_deref().unwrap_or("");
196 let pr_number = body.pr_number.unwrap_or(0);
197
198 if owner.is_empty() || repo.is_empty() || pr_number == 0 || body.body.is_none() {
199 return Json(serde_json::json!({
200 "error": "Missing required fields: owner, repo, prNumber, body",
201 "code": "BAD_REQUEST"
202 }));
203 }
204
205 Json(serde_json::json!({
207 "error": "GitHub PR comments are not available in desktop mode. Configure GITHUB_TOKEN and use the web backend.",
208 "code": "NOT_IMPLEMENTED",
209 "hint": {
210 "owner": owner,
211 "repo": repo,
212 "prNumber": pr_number,
213 }
214 }))
215}