1use std::collections::HashMap;
2
3use async_trait::async_trait;
4use octocrab::{Octocrab, Page, Result};
5use serde::{Deserialize, Serialize};
6
7use crate::resource::{CollaboratorPrincipal, Role};
8
9#[derive(Debug, Serialize, Deserialize)]
11pub struct GitHubBranchProtection {
12 pub required_status_checks: Option<GitHubRequiredStatusChecks>,
13 pub enforce_admins: GitHubEnforceAdmins,
14 pub required_pull_request_reviews: Option<GitHubPullRequestReviewEnforcement>,
15 pub restrictions: Option<GitHubBranchRestrictions>,
16 pub required_linear_history: Option<GitHubBooleanSetting>,
17 pub allow_force_pushes: Option<GitHubBooleanSetting>,
18 pub allow_deletions: Option<GitHubBooleanSetting>,
19 pub block_creations: Option<GitHubBooleanSetting>,
20 pub required_conversation_resolution: Option<GitHubBooleanSetting>,
21 pub lock_branch: Option<GitHubBooleanSetting>,
22 pub allow_fork_syncing: Option<GitHubBooleanSetting>,
23}
24
25#[derive(Debug, Serialize, Deserialize)]
26pub struct GitHubRequiredStatusChecks {
27 pub strict: bool,
28 pub contexts: Vec<String>,
29}
30
31#[derive(Debug, Serialize, Deserialize)]
32pub struct GitHubEnforceAdmins {
33 pub enabled: bool,
34}
35
36#[derive(Debug, Serialize, Deserialize)]
37pub struct GitHubPullRequestReviewEnforcement {
38 pub required_approving_review_count: Option<u32>,
39 pub dismiss_stale_reviews: Option<bool>,
40 pub require_code_owner_reviews: Option<bool>,
41 pub require_last_push_approval: Option<bool>,
42}
43
44#[derive(Debug, Serialize, Deserialize)]
45pub struct GitHubBranchRestrictions {
46 pub users: Vec<GitHubUser>,
47 pub teams: Vec<GitHubTeam>,
48 pub apps: Vec<GitHubApp>,
49}
50
51#[derive(Debug, Serialize, Deserialize)]
52pub struct GitHubUser {
53 pub login: String,
54}
55
56#[derive(Debug, Serialize, Deserialize)]
57pub struct GitHubTeam {
58 pub name: String,
59}
60
61#[derive(Debug, Serialize, Deserialize)]
62pub struct GitHubApp {
63 pub name: String,
64}
65
66#[derive(Debug, Serialize, Deserialize)]
67pub struct GitHubBooleanSetting {
68 pub enabled: bool,
69}
70
71#[derive(Debug, Serialize, Deserialize)]
73pub struct GitHubCollaboratorPermissions {
74 pub pull: bool,
75 pub triage: bool,
76 pub push: bool,
77 pub maintain: bool,
78 pub admin: bool,
79}
80
81#[derive(Debug, Serialize, Deserialize)]
82pub struct GitHubCollaborator {
83 pub permissions: GitHubCollaboratorPermissions,
84 pub role_name: String,
85}
86
87#[async_trait]
88pub trait BranchProtectionExt {
89 async fn get_branch_protection(&self, owner: &str, repo: &str, branch: &str) -> Result<GitHubBranchProtection>;
90}
91
92#[async_trait]
93impl BranchProtectionExt for Octocrab {
94 async fn get_branch_protection(&self, owner: &str, repo: &str, branch: &str) -> Result<GitHubBranchProtection> {
95 let route = format!("/repos/{}/{}/branches/{}/protection", owner, repo, branch);
96 self.get(route, None::<&()>).await
97 }
98}
99
100#[async_trait]
101pub trait CollaboratorExt {
102 async fn get_collaborator_permission(&self, owner: &str, repo: &str, username: &str) -> Result<GitHubCollaborator>;
103}
104
105#[async_trait]
106impl CollaboratorExt for Octocrab {
107 async fn get_collaborator_permission(&self, owner: &str, repo: &str, username: &str) -> Result<GitHubCollaborator> {
108 let route = format!("/repos/{}/{}/collaborators/{}/permission", owner, repo, username);
109 self.get(route, None::<&()>).await
110 }
111}
112
113#[derive(Debug, Serialize, Deserialize)]
115pub struct GitHubBranch {
116 pub name: String,
117 pub protected: bool,
118}
119
120#[derive(Debug, Serialize, Deserialize)]
121pub struct GitHubCollaboratorInfo {
122 pub login: String,
123 pub role_name: String,
124}
125
126#[async_trait]
127pub trait ListExt {
128 async fn list_repo_branches(&self, owner: &str, repo: &str) -> Result<octocrab::Page<GitHubBranch>>;
130 async fn list_repo_collaborators(
131 &self,
132 owner: &str,
133 repo: &str,
134 affiliation: Option<&str>,
135 ) -> Result<HashMap<CollaboratorPrincipal, Role>>;
136}
137
138#[async_trait]
139impl ListExt for Octocrab {
140 async fn list_repo_branches(&self, owner: &str, repo: &str) -> Result<octocrab::Page<GitHubBranch>> {
146 let route = format!("/repos/{}/{}/branches", owner, repo);
147 self.get(route, None::<&()>).await
148 }
149
150 async fn list_repo_collaborators(
151 &self,
152 owner: &str,
153 repo: &str,
154 affiliation: Option<&str>,
155 ) -> Result<HashMap<CollaboratorPrincipal, Role>> {
156 let mut res = HashMap::new();
157
158 #[derive(serde::Serialize)]
159 struct CollabQuery<'a> {
160 affiliation: &'a str, per_page: u8,
162 page: u32,
163 }
164
165 let route = format!("/repos/{}/{}/collaborators", owner, repo);
166 let users: Page<GitHubCollaboratorInfo> = self
167 .get(
168 route,
169 affiliation
170 .map(|affiliation| CollabQuery {
171 affiliation: affiliation,
172 per_page: 100,
173 page: 1,
174 })
175 .as_ref(),
176 )
177 .await?;
178
179 let users = self.all_pages(users).await?;
180
181 for user in users {
182 res.insert(CollaboratorPrincipal::User(user.login), Role::from_str(&user.role_name));
183 }
184
185 Ok(res)
186 }
187
188 }
215
216#[derive(Debug, Serialize, Deserialize)]
218pub struct CreateRepositoryRequest {
219 pub name: String,
220 pub description: Option<String>,
221 pub homepage: Option<String>,
222 pub private: bool,
223 pub has_issues: bool,
224 pub has_projects: bool,
225 pub has_wiki: bool,
226 pub allow_squash_merge: bool,
227 pub allow_merge_commit: bool,
228 pub allow_rebase_merge: bool,
229 pub allow_auto_merge: bool,
230 pub delete_branch_on_merge: bool,
231 pub default_branch: Option<String>,
232}
233
234#[derive(Debug, Serialize, Deserialize)]
235pub struct UpdateRepositoryRequest {
236 pub name: Option<String>,
237 pub description: Option<String>,
238 pub homepage: Option<String>,
239 pub private: Option<bool>,
240 pub has_issues: Option<bool>,
241 pub has_projects: Option<bool>,
242 pub has_wiki: Option<bool>,
243 pub allow_squash_merge: Option<bool>,
244 pub allow_merge_commit: Option<bool>,
245 pub allow_rebase_merge: Option<bool>,
246 pub allow_auto_merge: Option<bool>,
247 pub delete_branch_on_merge: Option<bool>,
248 pub default_branch: Option<String>,
249 pub archived: Option<bool>,
250}
251
252#[derive(Debug, Serialize, Deserialize)]
254pub struct CreateBranchProtectionRequest {
255 pub required_status_checks: Option<GitHubRequiredStatusChecks>,
256 pub enforce_admins: bool,
257 pub required_pull_request_reviews: Option<GitHubPullRequestReviewEnforcement>,
258 pub restrictions: Option<GitHubBranchRestrictions>,
259 pub required_linear_history: Option<bool>,
260 pub allow_force_pushes: Option<bool>,
261 pub allow_deletions: Option<bool>,
262 pub block_creations: Option<bool>,
263 pub required_conversation_resolution: Option<bool>,
264 pub lock_branch: Option<bool>,
265 pub allow_fork_syncing: Option<bool>,
266}
267
268#[derive(Debug, Serialize, Deserialize)]
270pub struct AddCollaboratorRequest {
271 pub permission: String, }
273
274#[derive(Debug, Serialize, Deserialize)]
275pub struct AddTeamCollaboratorRequest {
276 pub permission: String, }
278
279#[async_trait]
280pub trait RepositoryOpsExt {
281 async fn create_repository(&self, owner: &str, repo_data: &CreateRepositoryRequest)
282 -> Result<octocrab::models::Repository>;
283 async fn update_repository(
284 &self,
285 owner: &str,
286 repo: &str,
287 repo_data: &UpdateRepositoryRequest,
288 ) -> Result<octocrab::models::Repository>;
289 async fn delete_repository(&self, owner: &str, repo: &str) -> Result<()>;
290}
291
292#[async_trait]
293impl RepositoryOpsExt for Octocrab {
294 async fn create_repository(
295 &self,
296 _owner: &str,
297 repo_data: &CreateRepositoryRequest,
298 ) -> Result<octocrab::models::Repository> {
299 let route = format!("/user/repos");
300 self.post(route, Some(repo_data)).await
301 }
302
303 async fn update_repository(
304 &self,
305 owner: &str,
306 repo: &str,
307 repo_data: &UpdateRepositoryRequest,
308 ) -> Result<octocrab::models::Repository> {
309 let route = format!("/repos/{}/{}", owner, repo);
310 self.patch(route, Some(repo_data)).await
311 }
312
313 async fn delete_repository(&self, owner: &str, repo: &str) -> Result<()> {
314 let route = format!("/repos/{}/{}", owner, repo);
315 self.delete(route, None::<&()>).await
316 }
317}
318
319#[async_trait]
320pub trait BranchProtectionOpsExt {
321 async fn create_branch_protection(
322 &self,
323 owner: &str,
324 repo: &str,
325 branch: &str,
326 protection_data: &CreateBranchProtectionRequest,
327 ) -> Result<GitHubBranchProtection>;
328 async fn update_branch_protection(
329 &self,
330 owner: &str,
331 repo: &str,
332 branch: &str,
333 protection_data: &CreateBranchProtectionRequest,
334 ) -> Result<GitHubBranchProtection>;
335 async fn delete_branch_protection(&self, owner: &str, repo: &str, branch: &str) -> Result<()>;
336}
337
338#[async_trait]
339impl BranchProtectionOpsExt for Octocrab {
340 async fn create_branch_protection(
341 &self,
342 owner: &str,
343 repo: &str,
344 branch: &str,
345 protection_data: &CreateBranchProtectionRequest,
346 ) -> Result<GitHubBranchProtection> {
347 let route = format!("/repos/{}/{}/branches/{}/protection", owner, repo, branch);
348 self.put(route, Some(protection_data)).await
349 }
350
351 async fn update_branch_protection(
352 &self,
353 owner: &str,
354 repo: &str,
355 branch: &str,
356 protection_data: &CreateBranchProtectionRequest,
357 ) -> Result<GitHubBranchProtection> {
358 let route = format!("/repos/{}/{}/branches/{}/protection", owner, repo, branch);
359 self.put(route, Some(protection_data)).await
360 }
361
362 async fn delete_branch_protection(&self, owner: &str, repo: &str, branch: &str) -> Result<()> {
363 let route = format!("/repos/{}/{}/branches/{}/protection", owner, repo, branch);
364 self.delete(route, None::<&()>).await
365 }
366}
367
368#[async_trait]
369pub trait CollaboratorOpsExt {
370 async fn add_collaborator(
371 &self,
372 owner: &str,
373 repo: &str,
374 username: &str,
375 permission_data: &AddCollaboratorRequest,
376 ) -> Result<()>;
377 async fn update_collaborator_permission(
378 &self,
379 owner: &str,
380 repo: &str,
381 username: &str,
382 permission_data: &AddCollaboratorRequest,
383 ) -> Result<()>;
384 async fn remove_collaborator(&self, owner: &str, repo: &str, username: &str) -> Result<()>;
385 async fn add_team_to_repository(
386 &self,
387 owner: &str,
388 repo: &str,
389 team_slug: &str,
390 permission_data: &AddTeamCollaboratorRequest,
391 ) -> Result<()>;
392 async fn update_team_permission(
393 &self,
394 owner: &str,
395 repo: &str,
396 team_slug: &str,
397 permission_data: &AddTeamCollaboratorRequest,
398 ) -> Result<()>;
399 async fn remove_team_from_repository(&self, owner: &str, repo: &str, team_slug: &str) -> Result<()>;
400}
401
402#[async_trait]
403impl CollaboratorOpsExt for Octocrab {
404 async fn add_collaborator(
405 &self,
406 owner: &str,
407 repo: &str,
408 username: &str,
409 permission_data: &AddCollaboratorRequest,
410 ) -> Result<()> {
411 let route = format!("/repos/{}/{}/collaborators/{}", owner, repo, username);
412 self.put(route, Some(permission_data)).await
413 }
414
415 async fn update_collaborator_permission(
416 &self,
417 owner: &str,
418 repo: &str,
419 username: &str,
420 permission_data: &AddCollaboratorRequest,
421 ) -> Result<()> {
422 let route = format!("/repos/{}/{}/collaborators/{}", owner, repo, username);
423 self.put(route, Some(permission_data)).await
424 }
425
426 async fn remove_collaborator(&self, owner: &str, repo: &str, username: &str) -> Result<()> {
427 let route = format!("/repos/{}/{}/collaborators/{}", owner, repo, username);
428 self.delete(route, None::<&()>).await
429 }
430
431 async fn add_team_to_repository(
432 &self,
433 owner: &str,
434 repo: &str,
435 team_slug: &str,
436 permission_data: &AddTeamCollaboratorRequest,
437 ) -> Result<()> {
438 let route = format!("/orgs/{}/teams/{}/repos/{}/{}", owner, team_slug, owner, repo);
439 self.put(route, Some(permission_data)).await
440 }
441
442 async fn update_team_permission(
443 &self,
444 owner: &str,
445 repo: &str,
446 team_slug: &str,
447 permission_data: &AddTeamCollaboratorRequest,
448 ) -> Result<()> {
449 let route = format!("/orgs/{}/teams/{}/repos/{}/{}", owner, team_slug, owner, repo);
450 self.put(route, Some(permission_data)).await
451 }
452
453 async fn remove_team_from_repository(&self, owner: &str, repo: &str, team_slug: &str) -> Result<()> {
454 let route = format!("/orgs/{}/teams/{}/repos/{}/{}", owner, team_slug, owner, repo);
455 self.delete(route, None::<&()>).await
456 }
457}