1use anyhow::{Context, Result};
2use serde::Deserialize;
3
4use super::Client;
5
6#[derive(Debug, Deserialize)]
7pub struct Team {
8 pub id: u64,
9 pub name: String,
10 pub slug: String,
11 #[serde(default)]
12 pub description: Option<String>,
13 #[serde(default)]
14 pub permission: String,
15 #[serde(default)]
16 pub privacy: String,
17}
18
19#[derive(Debug, Deserialize)]
20pub struct TeamMember {
21 pub login: String,
22 pub role: String,
23}
24
25#[derive(Debug, Deserialize)]
26pub struct TeamRepoPermission {
27 pub team_slug: String,
28 pub permission: String,
29}
30
31impl Client {
32 pub async fn list_org_teams(&self) -> Result<Vec<Team>> {
34 let mut all_teams = Vec::new();
35 let mut page = 1u32;
36
37 loop {
38 let resp = self
39 .get(&format!(
40 "/orgs/{}/teams?per_page=100&page={page}",
41 self.org
42 ))
43 .await?;
44
45 let status = resp.status();
46 if !status.is_success() {
47 let body = resp.text().await.unwrap_or_default();
48 anyhow::bail!("Failed to list org teams (HTTP {status}): {body}");
49 }
50
51 let teams: Vec<Team> = resp
52 .json()
53 .await
54 .context("Failed to parse org teams response")?;
55
56 if teams.is_empty() {
57 break;
58 }
59
60 all_teams.extend(teams);
61 page += 1;
62 }
63
64 Ok(all_teams)
65 }
66
67 pub async fn list_repo_teams(&self, repo: &str) -> Result<Vec<Team>> {
69 let resp = self
70 .get(&format!("/repos/{}/{repo}/teams", self.org))
71 .await?;
72
73 let status = resp.status();
74 if !status.is_success() {
75 let body = resp.text().await.unwrap_or_default();
76 anyhow::bail!("Failed to list teams for {repo} (HTTP {status}): {body}");
77 }
78
79 resp.json()
80 .await
81 .context("Failed to parse repo teams response")
82 }
83
84 pub async fn add_team_to_repo(
86 &self,
87 repo: &str,
88 team_slug: &str,
89 permission: &str,
90 ) -> Result<()> {
91 let body = serde_json::json!({ "permission": permission });
92 let resp = self
93 .put_json(
94 &format!(
95 "/orgs/{}/teams/{team_slug}/repos/{}/{repo}",
96 self.org, self.org
97 ),
98 &body,
99 )
100 .await?;
101
102 let status = resp.status();
103 if !status.is_success() && status.as_u16() != 204 {
104 let body = resp.text().await.unwrap_or_default();
105 anyhow::bail!("Failed to add team {team_slug} to {repo} (HTTP {status}): {body}");
106 }
107
108 Ok(())
109 }
110
111 pub async fn remove_team_from_repo(&self, repo: &str, team_slug: &str) -> Result<()> {
113 let resp = self
114 .delete(&format!(
115 "/orgs/{}/teams/{team_slug}/repos/{}/{repo}",
116 self.org, self.org
117 ))
118 .await?;
119
120 let status = resp.status();
121 if !status.is_success() && status.as_u16() != 204 {
122 let body = resp.text().await.unwrap_or_default();
123 anyhow::bail!("Failed to remove team {team_slug} from {repo} (HTTP {status}): {body}");
124 }
125
126 Ok(())
127 }
128}