Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar-ui

crates/gunnar-ui-web/src/members.rs

//! **Members** on a repository's Settings tab (owner 2026-10-07, the members
//! model): the account's admin gives a member Read or Write on one of the
//! account's repositories, and takes it away.
//!
//! The server answers who the members are and what gunnar says they hold
//! (`GET /api/repos/{ns}/{name}/members`, asked as the person — gunnar answers
//! the account's admin and refuses anybody else, and then this panel draws
//! nothing); the buttons are `PUT` / `DELETE …/members/{id}`, signed as the
//! person, and gunnar decides. The page decides nothing.
//!
//! The words are here, on the host side of the wasm boundary (same argument
//! as [`crate::room`]); the panel is `pages::repo::MembersPanel`.

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Member {
    pub id: String,
    pub name: String,
    pub repo_level: Option<String>,
    pub account_level: Option<String>,
}

#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct Members {
    pub account: String,
    pub repo: String,
    #[serde(default)]
    pub members: Vec<Member>,
}

/// What the page sends: `read` or `write`.
#[derive(Serialize)]
pub struct Give<'a> {
    pub level: &'a str,
}

/// What a member holds here, in words: the grant on this repository, else the
/// one on the whole account, else nothing.
pub fn holds(m: &Member, account: &str) -> String {
    let cap = |w: &str| {
        let mut c = w.chars();
        c.next().map(|f| f.to_uppercase().collect::<String>() + c.as_str()).unwrap_or_default()
    };
    match (m.repo_level.as_deref(), m.account_level.as_deref()) {
        (Some(repo), _) => format!("{} on this repository", cap(repo)),
        (None, Some(all)) => format!("{} on all of {account}", cap(all)),
        (None, None) => "Nothing".to_owned(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn member(repo: Option<&str>, all: Option<&str>) -> Member {
        Member { id: "ben".into(), name: "Ben".into(), repo_level: repo.map(Into::into), account_level: all.map(Into::into) }
    }

    /// What a member holds is said as gunnar's grant: this repository's
    /// first, then the account's.
    #[test]
    fn a_member_holds_what_gunnar_says_here_first() {
        assert_eq!(holds(&member(Some("write"), Some("read")), "acme"), "Write on this repository");
        assert_eq!(holds(&member(None, Some("read")), "acme"), "Read on all of acme");
        assert_eq!(holds(&member(None, None), "acme"), "Nothing");
    }
}