1use crate::{error::GiteeError, GiteeClient};
2use std::path::Path;
3use std::fs;
4use git2::{Repository as GitRepo, RemoteCallbacks, Cred, PushOptions};
5use tempfile::tempdir;
6
7mod models;
8pub use models::*;
9
10impl GiteeClient {
11 fn get_wiki_url(&self, owner: &str, repo: &str) -> String {
12 format!("https://oauth2:{}@gitee.com/{}/{}.wiki.git", self.token(), owner, repo)
14 }
15
16 pub async fn list_repo_wikis(&self, owner: &str, repo: &str) -> Result<Vec<WikiPage>, GiteeError> {
18 let url = self.get_wiki_url(owner, repo);
19 let dir = tempdir().map_err(|e| GiteeError::ApiError(e.to_string()))?;
20
21 GitRepo::clone(&url, dir.path()).map_err(|e| GiteeError::ApiError(format!("Failed to clone wiki: {}", e)))?;
22
23 let mut wikis = Vec::new();
24 for entry in fs::read_dir(dir.path()).map_err(|e| GiteeError::ApiError(e.to_string()))? {
25 let entry = entry.map_err(|e| GiteeError::ApiError(e.to_string()))?;
26 let path = entry.path();
27 if path.extension().and_then(|s| s.to_str()) == Some("md") {
28 let file_name = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
29 wikis.push(WikiPage {
30 title: file_name.to_string(),
31 body: None,
32 html_url: None,
33 slug: Some(file_name.to_string()),
34 });
35 }
36 }
37 Ok(wikis)
38 }
39
40 pub async fn get_repo_wiki(&self, owner: &str, repo: &str, slug: &str) -> Result<WikiPage, GiteeError> {
42 let url = self.get_wiki_url(owner, repo);
43 let dir = tempdir().map_err(|e| GiteeError::ApiError(e.to_string()))?;
44
45 GitRepo::clone(&url, dir.path()).map_err(|e| GiteeError::ApiError(format!("Failed to clone wiki: {}", e)))?;
46
47 let file_path = dir.path().join(format!("{}.md", slug));
48 if !file_path.exists() {
49 return Err(GiteeError::ApiError(format!("Wiki page {} not found", slug)));
50 }
51
52 let body = fs::read_to_string(file_path).map_err(|e| GiteeError::ApiError(e.to_string()))?;
53
54 Ok(WikiPage {
55 title: slug.to_string(),
56 body: Some(body),
57 html_url: None,
58 slug: Some(slug.to_string()),
59 })
60 }
61
62 pub async fn create_repo_wiki(&self, owner: &str, repo: &str, title: &str, body: &str) -> Result<WikiPage, GiteeError> {
64 let url = self.get_wiki_url(owner, repo);
65 let dir = tempdir().map_err(|e| GiteeError::ApiError(e.to_string()))?;
66
67 let git_repo = GitRepo::clone(&url, dir.path()).map_err(|e| GiteeError::ApiError(format!("Failed to clone wiki: {}", e)))?;
68
69 let file_path = dir.path().join(format!("{}.md", title));
70 fs::write(&file_path, body).map_err(|e| GiteeError::ApiError(e.to_string()))?;
71
72 let mut index = git_repo.index().map_err(|e| GiteeError::ApiError(e.to_string()))?;
74 index.add_path(Path::new(&format!("{}.md", title))).map_err(|e| GiteeError::ApiError(e.to_string()))?;
75 index.write().map_err(|e| GiteeError::ApiError(e.to_string()))?;
76
77 let oid = index.write_tree().map_err(|e| GiteeError::ApiError(e.to_string()))?;
78 let tree = git_repo.find_tree(oid).map_err(|e| GiteeError::ApiError(e.to_string()))?;
79
80 let signature = git_repo.signature().unwrap_or_else(|_| {
81 git2::Signature::now("GiteeBot", "bot@gitee.com").unwrap()
82 });
83
84 let parent_commit = git_repo.head().ok().and_then(|h| h.peel_to_commit().ok());
85
86 if let Some(parent) = parent_commit {
87 git_repo.commit(
88 Some("HEAD"),
89 &signature,
90 &signature,
91 &format!("Update wiki page: {}", title),
92 &tree,
93 &[&parent],
94 ).map_err(|e| GiteeError::ApiError(format!("Failed to commit: {}", e)))?;
95 } else {
96 git_repo.commit(
97 Some("HEAD"),
98 &signature,
99 &signature,
100 &format!("Initial wiki page: {}", title),
101 &tree,
102 &[],
103 ).map_err(|e| GiteeError::ApiError(format!("Failed to commit: {}", e)))?;
104 }
105
106 let mut remote = git_repo.find_remote("origin").map_err(|e| GiteeError::ApiError(e.to_string()))?;
108 let mut callbacks = RemoteCallbacks::new();
109 callbacks.credentials(|_url, _username_from_url, _allowed_types| {
110 Cred::userpass_plaintext("oauth2", self.token())
111 });
112
113 let mut push_opts = PushOptions::new();
114 push_opts.remote_callbacks(callbacks);
115
116 remote.push(&["refs/heads/master:refs/heads/master"], Some(&mut push_opts))
117 .map_err(|e| GiteeError::ApiError(format!("Failed to push wiki: {}", e)))?;
118
119 Ok(WikiPage {
120 title: title.to_string(),
121 body: Some(body.to_string()),
122 html_url: None,
123 slug: Some(title.to_string()),
124 })
125 }
126
127 pub async fn update_repo_wiki(&self, owner: &str, repo: &str, _slug: &str, title: &str, body: &str) -> Result<WikiPage, GiteeError> {
129 self.create_repo_wiki(owner, repo, title, body).await
130 }
131
132 pub async fn delete_repo_wiki(&self, owner: &str, repo: &str, slug: &str) -> Result<(), GiteeError> {
134 let url = self.get_wiki_url(owner, repo);
135 let dir = tempdir().map_err(|e| GiteeError::ApiError(e.to_string()))?;
136
137 let git_repo = GitRepo::clone(&url, dir.path()).map_err(|e| GiteeError::ApiError(format!("Failed to clone wiki: {}", e)))?;
138
139 let file_name = format!("{}.md", slug);
140 let file_path = dir.path().join(&file_name);
141 if !file_path.exists() {
142 return Ok(());
143 }
144
145 fs::remove_file(&file_path).map_err(|e| GiteeError::ApiError(e.to_string()))?;
146
147 let mut index = git_repo.index().map_err(|e| GiteeError::ApiError(e.to_string()))?;
148 index.remove_path(Path::new(&file_name)).map_err(|e| GiteeError::ApiError(e.to_string()))?;
149 index.write().map_err(|e| GiteeError::ApiError(e.to_string()))?;
150
151 let oid = index.write_tree().map_err(|e| GiteeError::ApiError(e.to_string()))?;
152 let tree = git_repo.find_tree(oid).map_err(|e| GiteeError::ApiError(e.to_string()))?;
153
154 let signature = git_repo.signature().unwrap_or_else(|_| {
155 git2::Signature::now("GiteeBot", "bot@gitee.com").unwrap()
156 });
157
158 let parent_commit = git_repo.head().map_err(|e| GiteeError::ApiError(e.to_string()))?.peel_to_commit().map_err(|e| GiteeError::ApiError(e.to_string()))?;
159
160 git_repo.commit(
161 Some("HEAD"),
162 &signature,
163 &signature,
164 &format!("Delete wiki page: {}", slug),
165 &tree,
166 &[&parent_commit],
167 ).map_err(|e| GiteeError::ApiError(e.to_string()))?;
168
169 let mut remote = git_repo.find_remote("origin").map_err(|e| GiteeError::ApiError(e.to_string()))?;
170 let mut callbacks = RemoteCallbacks::new();
171 callbacks.credentials(|_url, _username_from_url, _allowed_types| {
172 Cred::userpass_plaintext("oauth2", self.token())
173 });
174
175 let mut push_opts = PushOptions::new();
176 push_opts.remote_callbacks(callbacks);
177
178 remote.push(&["refs/heads/master:refs/heads/master"], Some(&mut push_opts))
179 .map_err(|e| GiteeError::ApiError(format!("Failed to push wiki: {}", e)))?;
180
181 Ok(())
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_wiki_page_deserialization() {
191 let wiki_json = serde_json::json!({
192 "title": "Welcome",
193 "body": "This is the wiki body",
194 "html_url": "https://gitee.com/owner/repo/wikis/Welcome",
195 "slug": "Welcome"
196 });
197
198 let wiki: WikiPage = serde_json::from_value(wiki_json).unwrap();
199 assert_eq!(wiki.title, "Welcome");
200 assert_eq!(wiki.slug.unwrap(), "Welcome");
201 }
202}