1use serde::{Deserialize, Serialize};
4use std::collections::HashSet;
5
6#[derive(
8 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
9)]
10#[serde(rename_all = "lowercase")]
11pub enum OrgRole {
12 #[default]
14 Member,
15 Admin,
17 Owner,
19}
20
21impl OrgRole {
22 pub fn has(&self, required: OrgRole) -> bool {
24 *self >= required
25 }
26
27 pub fn parse(s: &str) -> Option<Self> {
29 match s.to_lowercase().as_str() {
30 "member" => Some(OrgRole::Member),
31 "admin" => Some(OrgRole::Admin),
32 "owner" => Some(OrgRole::Owner),
33 _ => None,
34 }
35 }
36}
37
38impl std::fmt::Display for OrgRole {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 OrgRole::Member => write!(f, "member"),
42 OrgRole::Admin => write!(f, "admin"),
43 OrgRole::Owner => write!(f, "owner"),
44 }
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct OrgMember {
51 pub user: String,
53 pub role: OrgRole,
55 pub added_at: u64,
57 pub added_by: String,
59}
60
61impl OrgMember {
62 pub fn new(user: String, role: OrgRole, added_by: String) -> Self {
64 Self {
65 user,
66 role,
67 added_at: Self::now(),
68 added_by,
69 }
70 }
71
72 fn now() -> u64 {
73 std::time::SystemTime::now()
74 .duration_since(std::time::UNIX_EPOCH)
75 .unwrap_or_default()
76 .as_secs()
77 }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct Organization {
83 pub id: u64,
85 pub name: String,
87 pub display_name: String,
89 pub description: Option<String>,
91 pub created_by: String,
93 pub members: Vec<OrgMember>,
95 pub teams: HashSet<u64>,
97 pub repos: HashSet<String>,
99 pub created_at: u64,
101 pub updated_at: u64,
103}
104
105impl Organization {
106 pub fn new(id: u64, name: String, display_name: String, created_by: String) -> Self {
108 let now = Self::now();
109 let founder = OrgMember {
110 user: created_by.clone(),
111 role: OrgRole::Owner,
112 added_at: now,
113 added_by: created_by.clone(),
114 };
115
116 Self {
117 id,
118 name,
119 display_name,
120 description: None,
121 created_by,
122 members: vec![founder],
123 teams: HashSet::new(),
124 repos: HashSet::new(),
125 created_at: now,
126 updated_at: now,
127 }
128 }
129
130 pub fn with_description(mut self, description: impl Into<String>) -> Self {
132 self.description = Some(description.into());
133 self.updated_at = Self::now();
134 self
135 }
136
137 pub fn get_member(&self, user: &str) -> Option<&OrgMember> {
139 self.members.iter().find(|m| m.user == user)
140 }
141
142 pub fn is_member(&self, user: &str) -> bool {
144 self.get_member(user).is_some()
145 }
146
147 pub fn has_role(&self, user: &str, required: OrgRole) -> bool {
149 self.get_member(user)
150 .map(|m| m.role.has(required))
151 .unwrap_or(false)
152 }
153
154 pub fn is_owner(&self, user: &str) -> bool {
156 self.has_role(user, OrgRole::Owner)
157 }
158
159 pub fn is_admin(&self, user: &str) -> bool {
161 self.has_role(user, OrgRole::Admin)
162 }
163
164 pub fn add_member(&mut self, member: OrgMember) -> bool {
166 if self.is_member(&member.user) {
167 return false;
168 }
169 self.members.push(member);
170 self.updated_at = Self::now();
171 true
172 }
173
174 pub fn remove_member(&mut self, user: &str) -> Result<bool, &'static str> {
177 if let Some(member) = self.get_member(user) {
179 if member.role == OrgRole::Owner {
180 let owner_count = self
181 .members
182 .iter()
183 .filter(|m| m.role == OrgRole::Owner)
184 .count();
185 if owner_count <= 1 {
186 return Err("cannot remove last owner");
187 }
188 }
189 }
190
191 let before = self.members.len();
192 self.members.retain(|m| m.user != user);
193 let removed = self.members.len() < before;
194
195 if removed {
196 self.updated_at = Self::now();
197 }
198
199 Ok(removed)
200 }
201
202 pub fn update_member_role(
205 &mut self,
206 user: &str,
207 new_role: OrgRole,
208 ) -> Result<bool, &'static str> {
209 if let Some(member) = self.get_member(user) {
211 if member.role == OrgRole::Owner && new_role != OrgRole::Owner {
212 let owner_count = self
213 .members
214 .iter()
215 .filter(|m| m.role == OrgRole::Owner)
216 .count();
217 if owner_count <= 1 {
218 return Err("cannot demote last owner");
219 }
220 }
221 }
222
223 for member in &mut self.members {
224 if member.user == user {
225 member.role = new_role;
226 self.updated_at = Self::now();
227 return Ok(true);
228 }
229 }
230
231 Ok(false)
232 }
233
234 pub fn add_team(&mut self, team_id: u64) {
236 self.teams.insert(team_id);
237 self.updated_at = Self::now();
238 }
239
240 pub fn remove_team(&mut self, team_id: u64) -> bool {
242 let removed = self.teams.remove(&team_id);
243 if removed {
244 self.updated_at = Self::now();
245 }
246 removed
247 }
248
249 pub fn add_repo(&mut self, repo_key: String) {
251 self.repos.insert(repo_key);
252 self.updated_at = Self::now();
253 }
254
255 pub fn remove_repo(&mut self, repo_key: &str) -> bool {
257 let removed = self.repos.remove(repo_key);
258 if removed {
259 self.updated_at = Self::now();
260 }
261 removed
262 }
263
264 pub fn owner_count(&self) -> usize {
266 self.members
267 .iter()
268 .filter(|m| m.role == OrgRole::Owner)
269 .count()
270 }
271
272 fn now() -> u64 {
273 std::time::SystemTime::now()
274 .duration_since(std::time::UNIX_EPOCH)
275 .unwrap_or_default()
276 .as_secs()
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_org_role_ordering() {
286 assert!(OrgRole::Member < OrgRole::Admin);
287 assert!(OrgRole::Admin < OrgRole::Owner);
288 }
289
290 #[test]
291 fn test_org_creation() {
292 let org = Organization::new(1, "acme".into(), "Acme Corp".into(), "abc123".into());
293
294 assert_eq!(org.id, 1);
295 assert_eq!(org.name, "acme");
296 assert_eq!(org.display_name, "Acme Corp");
297 assert_eq!(org.members.len(), 1);
298 assert!(org.is_owner("abc123"));
299 }
300
301 #[test]
302 fn test_add_remove_member() {
303 let mut org = Organization::new(1, "acme".into(), "Acme Corp".into(), "owner".into());
304
305 let member = OrgMember::new("user1".into(), OrgRole::Member, "owner".into());
307 assert!(org.add_member(member));
308 assert!(org.is_member("user1"));
309 assert!(!org.is_admin("user1"));
310
311 let admin = OrgMember::new("admin1".into(), OrgRole::Admin, "owner".into());
313 assert!(org.add_member(admin));
314 assert!(org.is_admin("admin1"));
315 assert!(!org.is_owner("admin1"));
316
317 assert!(org.remove_member("user1").unwrap());
319 assert!(!org.is_member("user1"));
320
321 assert!(org.remove_member("owner").is_err());
323 }
324
325 #[test]
326 fn test_role_update() {
327 let mut org = Organization::new(1, "acme".into(), "Acme Corp".into(), "owner1".into());
328
329 let owner2 = OrgMember::new("owner2".into(), OrgRole::Owner, "owner1".into());
331 org.add_member(owner2);
332
333 assert!(org.update_member_role("owner1", OrgRole::Admin).unwrap());
335 assert!(org.is_admin("owner1"));
336 assert!(!org.is_owner("owner1"));
337
338 assert!(org.update_member_role("owner2", OrgRole::Member).is_err());
340 }
341}