Skip to main content

ward/github/
teams.rs

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    /// List all teams in the organization, handling pagination.
33    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    /// List teams that have access to a repository.
68    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    /// Add or update a team's access to a repository.
85    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    /// Remove a team's access from a repository.
112    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}