1use std::collections::HashMap;
2use std::sync::Mutex;
3
4use crate::Plugin;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum OrgRole {
9 Owner,
10 Admin,
11 Member,
12}
13
14impl OrgRole {
15 pub fn from_str(s: &str) -> Option<Self> {
16 match s {
17 "owner" => Some(Self::Owner),
18 "admin" => Some(Self::Admin),
19 "member" => Some(Self::Member),
20 _ => None,
21 }
22 }
23
24 pub fn as_str(&self) -> &str {
25 match self {
26 Self::Owner => "owner",
27 Self::Admin => "admin",
28 Self::Member => "member",
29 }
30 }
31
32 pub fn can_manage_members(&self) -> bool {
33 matches!(self, Self::Owner | Self::Admin)
34 }
35
36 pub fn can_delete_org(&self) -> bool {
37 matches!(self, Self::Owner)
38 }
39}
40
41#[derive(Debug, Clone)]
43pub struct Organization {
44 pub id: String,
45 pub name: String,
46 pub created_by: String,
47 pub created_at: String,
48}
49
50#[derive(Debug, Clone)]
52pub struct Membership {
53 pub org_id: String,
54 pub user_id: String,
55 pub role: OrgRole,
56 pub joined_at: String,
57}
58
59pub struct OrganizationsPlugin {
61 orgs: Mutex<HashMap<String, Organization>>,
62 members: Mutex<Vec<Membership>>,
63 next_id: Mutex<u64>,
64}
65
66impl OrganizationsPlugin {
67 pub fn new() -> Self {
68 Self {
69 orgs: Mutex::new(HashMap::new()),
70 members: Mutex::new(Vec::new()),
71 next_id: Mutex::new(0),
72 }
73 }
74
75 pub fn create_org(&self, name: &str, creator_id: &str) -> Organization {
77 let mut id_counter = self.next_id.lock().unwrap();
78 *id_counter += 1;
79 let id = format!("org_{}", *id_counter);
80
81 let org = Organization {
82 id: id.clone(),
83 name: name.to_string(),
84 created_by: creator_id.to_string(),
85 created_at: now(),
86 };
87
88 self.orgs.lock().unwrap().insert(id.clone(), org.clone());
89
90 self.members.lock().unwrap().push(Membership {
92 org_id: id,
93 user_id: creator_id.to_string(),
94 role: OrgRole::Owner,
95 joined_at: now(),
96 });
97
98 org
99 }
100
101 pub fn add_member(&self, org_id: &str, user_id: &str, role: OrgRole) -> Result<(), String> {
103 if !self.orgs.lock().unwrap().contains_key(org_id) {
104 return Err(format!("Organization {} not found", org_id));
105 }
106
107 let mut members = self.members.lock().unwrap();
108 if members
109 .iter()
110 .any(|m| m.org_id == org_id && m.user_id == user_id)
111 {
112 return Err("User is already a member".into());
113 }
114
115 members.push(Membership {
116 org_id: org_id.to_string(),
117 user_id: user_id.to_string(),
118 role,
119 joined_at: now(),
120 });
121 Ok(())
122 }
123
124 pub fn remove_member(&self, org_id: &str, user_id: &str) -> bool {
126 let mut members = self.members.lock().unwrap();
127 let before = members.len();
128 members.retain(|m| !(m.org_id == org_id && m.user_id == user_id));
129 members.len() < before
130 }
131
132 pub fn get_role(&self, org_id: &str, user_id: &str) -> Option<OrgRole> {
134 self.members
135 .lock()
136 .unwrap()
137 .iter()
138 .find(|m| m.org_id == org_id && m.user_id == user_id)
139 .map(|m| m.role.clone())
140 }
141
142 pub fn list_members(&self, org_id: &str) -> Vec<Membership> {
144 self.members
145 .lock()
146 .unwrap()
147 .iter()
148 .filter(|m| m.org_id == org_id)
149 .cloned()
150 .collect()
151 }
152
153 pub fn list_user_orgs(&self, user_id: &str) -> Vec<(Organization, OrgRole)> {
155 let members = self.members.lock().unwrap();
156 let orgs = self.orgs.lock().unwrap();
157
158 members
159 .iter()
160 .filter(|m| m.user_id == user_id)
161 .filter_map(|m| orgs.get(&m.org_id).map(|o| (o.clone(), m.role.clone())))
162 .collect()
163 }
164
165 pub fn is_member(&self, org_id: &str, user_id: &str) -> bool {
167 self.get_role(org_id, user_id).is_some()
168 }
169
170 pub fn delete_org(&self, org_id: &str) -> bool {
172 let removed = self.orgs.lock().unwrap().remove(org_id).is_some();
173 if removed {
174 self.members.lock().unwrap().retain(|m| m.org_id != org_id);
175 }
176 removed
177 }
178}
179
180impl Plugin for OrganizationsPlugin {
181 fn name(&self) -> &str {
182 "organizations"
183 }
184}
185
186fn now() -> String {
187 let ts = SystemTime::now()
188 .duration_since(std::time::UNIX_EPOCH)
189 .unwrap_or_default()
190 .as_secs();
191 format!("{ts}Z")
192}
193
194use std::time::SystemTime;
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn create_org() {
202 let plugin = OrganizationsPlugin::new();
203 let org = plugin.create_org("My Team", "user-1");
204 assert!(!org.id.is_empty());
205 assert_eq!(org.name, "My Team");
206 assert_eq!(org.created_by, "user-1");
207 }
208
209 #[test]
210 fn creator_is_owner() {
211 let plugin = OrganizationsPlugin::new();
212 let org = plugin.create_org("Team", "user-1");
213 let role = plugin.get_role(&org.id, "user-1").unwrap();
214 assert_eq!(role, OrgRole::Owner);
215 }
216
217 #[test]
218 fn add_and_list_members() {
219 let plugin = OrganizationsPlugin::new();
220 let org = plugin.create_org("Team", "user-1");
221 plugin
222 .add_member(&org.id, "user-2", OrgRole::Admin)
223 .unwrap();
224 plugin
225 .add_member(&org.id, "user-3", OrgRole::Member)
226 .unwrap();
227
228 let members = plugin.list_members(&org.id);
229 assert_eq!(members.len(), 3);
230 }
231
232 #[test]
233 fn duplicate_member_rejected() {
234 let plugin = OrganizationsPlugin::new();
235 let org = plugin.create_org("Team", "user-1");
236 let result = plugin.add_member(&org.id, "user-1", OrgRole::Member);
237 assert!(result.is_err());
238 }
239
240 #[test]
241 fn remove_member() {
242 let plugin = OrganizationsPlugin::new();
243 let org = plugin.create_org("Team", "user-1");
244 plugin
245 .add_member(&org.id, "user-2", OrgRole::Member)
246 .unwrap();
247
248 assert!(plugin.remove_member(&org.id, "user-2"));
249 assert!(!plugin.is_member(&org.id, "user-2"));
250 }
251
252 #[test]
253 fn list_user_orgs() {
254 let plugin = OrganizationsPlugin::new();
255 let org1 = plugin.create_org("Team A", "user-1");
256 let org2 = plugin.create_org("Team B", "user-2");
257 plugin
258 .add_member(&org2.id, "user-1", OrgRole::Member)
259 .unwrap();
260
261 let orgs = plugin.list_user_orgs("user-1");
262 assert_eq!(orgs.len(), 2);
263 }
264
265 #[test]
266 fn role_permissions() {
267 assert!(OrgRole::Owner.can_manage_members());
268 assert!(OrgRole::Owner.can_delete_org());
269 assert!(OrgRole::Admin.can_manage_members());
270 assert!(!OrgRole::Admin.can_delete_org());
271 assert!(!OrgRole::Member.can_manage_members());
272 assert!(!OrgRole::Member.can_delete_org());
273 }
274
275 #[test]
276 fn delete_org() {
277 let plugin = OrganizationsPlugin::new();
278 let org = plugin.create_org("Team", "user-1");
279 plugin
280 .add_member(&org.id, "user-2", OrgRole::Member)
281 .unwrap();
282
283 assert!(plugin.delete_org(&org.id));
284 assert!(plugin.list_members(&org.id).is_empty());
285 assert!(!plugin.is_member(&org.id, "user-1"));
286 }
287
288 #[test]
289 fn add_to_nonexistent_org() {
290 let plugin = OrganizationsPlugin::new();
291 let result = plugin.add_member("org_999", "user-1", OrgRole::Member);
292 assert!(result.is_err());
293 }
294}