Skip to main content

gitee_rs/wikis/
mod.rs

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        // Constructing authenticated URL: https://oauth2:token@gitee.com/owner/repo.wiki.git
13        format!("https://oauth2:{}@gitee.com/{}/{}.wiki.git", self.token(), owner, repo)
14    }
15
16    /// List all wiki pages for a repository by cloning it locally
17    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    /// Get a single wiki page content
41    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    /// Create or Update a wiki page
63    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        // Git add, commit and push
73        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        // Push
107        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    /// Update a wiki page (alias to create_repo_wiki)
128    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    /// Delete a wiki page
133    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}