Skip to main content

subcog/storage/group/
traits.rs

1//! Group storage trait definitions.
2//!
3//! Defines the interface for group storage backends, supporting CRUD operations
4//! for groups, members, and invites.
5
6use crate::Result;
7use crate::models::group::{Group, GroupId, GroupInvite, GroupMember, GroupMembership, GroupRole};
8
9/// Trait for group storage backends.
10///
11/// Provides storage operations for groups, members, and invites within an organization.
12/// Implementations must be thread-safe (`Send + Sync`).
13pub trait GroupBackend: Send + Sync {
14    // =========================================================================
15    // Group Operations
16    // =========================================================================
17
18    /// Creates a new group.
19    ///
20    /// # Arguments
21    ///
22    /// * `org_id` - Organization identifier
23    /// * `name` - Group name (must be unique within org)
24    /// * `description` - Optional description
25    /// * `created_by` - Email of the creator
26    ///
27    /// # Errors
28    ///
29    /// Returns an error if:
30    /// - A group with the same name already exists in the org
31    /// - Storage cannot be accessed
32    fn create_group(
33        &self,
34        org_id: &str,
35        name: &str,
36        description: &str,
37        created_by: &str,
38    ) -> Result<Group>;
39
40    /// Gets a group by ID.
41    ///
42    /// # Arguments
43    ///
44    /// * `group_id` - The group identifier
45    ///
46    /// # Returns
47    ///
48    /// The group if found, None otherwise.
49    ///
50    /// # Errors
51    ///
52    /// Returns an error if storage cannot be accessed.
53    fn get_group(&self, group_id: &GroupId) -> Result<Option<Group>>;
54
55    /// Gets a group by name within an organization.
56    ///
57    /// # Arguments
58    ///
59    /// * `org_id` - Organization identifier
60    /// * `name` - Group name
61    ///
62    /// # Returns
63    ///
64    /// The group if found, None otherwise.
65    ///
66    /// # Errors
67    ///
68    /// Returns an error if storage cannot be accessed.
69    fn get_group_by_name(&self, org_id: &str, name: &str) -> Result<Option<Group>>;
70
71    /// Lists all groups in an organization.
72    ///
73    /// # Arguments
74    ///
75    /// * `org_id` - Organization identifier
76    ///
77    /// # Returns
78    ///
79    /// List of groups in the organization.
80    ///
81    /// # Errors
82    ///
83    /// Returns an error if storage cannot be accessed.
84    fn list_groups(&self, org_id: &str) -> Result<Vec<Group>>;
85
86    /// Deletes a group and all its members and invites.
87    ///
88    /// # Arguments
89    ///
90    /// * `group_id` - The group to delete
91    ///
92    /// # Returns
93    ///
94    /// True if the group was deleted, false if it didn't exist.
95    ///
96    /// # Errors
97    ///
98    /// Returns an error if storage cannot be accessed.
99    fn delete_group(&self, group_id: &GroupId) -> Result<bool>;
100
101    // =========================================================================
102    // Member Operations
103    // =========================================================================
104
105    /// Adds a member to a group.
106    ///
107    /// If the member already exists, updates their role.
108    ///
109    /// # Arguments
110    ///
111    /// * `group_id` - The group to add to
112    /// * `email` - Email of the new member
113    /// * `role` - Role to assign
114    /// * `added_by` - Email of the user adding this member
115    ///
116    /// # Errors
117    ///
118    /// Returns an error if:
119    /// - The group doesn't exist
120    /// - Storage cannot be accessed
121    fn add_member(
122        &self,
123        group_id: &GroupId,
124        email: &str,
125        role: GroupRole,
126        added_by: &str,
127    ) -> Result<GroupMember>;
128
129    /// Gets a member's record in a group.
130    ///
131    /// # Arguments
132    ///
133    /// * `group_id` - The group
134    /// * `email` - The member's email
135    ///
136    /// # Returns
137    ///
138    /// The member record if found, None otherwise.
139    ///
140    /// # Errors
141    ///
142    /// Returns an error if storage cannot be accessed.
143    fn get_member(&self, group_id: &GroupId, email: &str) -> Result<Option<GroupMember>>;
144
145    /// Updates a member's role in a group.
146    ///
147    /// # Arguments
148    ///
149    /// * `group_id` - The group
150    /// * `email` - The member's email
151    /// * `new_role` - The new role to assign
152    ///
153    /// # Returns
154    ///
155    /// True if the member was updated, false if not found.
156    ///
157    /// # Errors
158    ///
159    /// Returns an error if storage cannot be accessed.
160    fn update_member_role(
161        &self,
162        group_id: &GroupId,
163        email: &str,
164        new_role: GroupRole,
165    ) -> Result<bool>;
166
167    /// Removes a member from a group.
168    ///
169    /// # Arguments
170    ///
171    /// * `group_id` - The group
172    /// * `email` - The member's email
173    ///
174    /// # Returns
175    ///
176    /// True if the member was removed, false if not found.
177    ///
178    /// # Errors
179    ///
180    /// Returns an error if storage cannot be accessed.
181    fn remove_member(&self, group_id: &GroupId, email: &str) -> Result<bool>;
182
183    /// Lists all members of a group.
184    ///
185    /// # Arguments
186    ///
187    /// * `group_id` - The group
188    ///
189    /// # Returns
190    ///
191    /// List of members with their roles.
192    ///
193    /// # Errors
194    ///
195    /// Returns an error if storage cannot be accessed.
196    fn list_members(&self, group_id: &GroupId) -> Result<Vec<GroupMember>>;
197
198    /// Gets all groups a user is a member of.
199    ///
200    /// # Arguments
201    ///
202    /// * `org_id` - Organization to search within
203    /// * `email` - The user's email
204    ///
205    /// # Returns
206    ///
207    /// List of group memberships for the user.
208    ///
209    /// # Errors
210    ///
211    /// Returns an error if storage cannot be accessed.
212    fn get_user_groups(&self, org_id: &str, email: &str) -> Result<Vec<GroupMembership>>;
213
214    /// Counts the number of admins in a group.
215    ///
216    /// Used to prevent removing the last admin.
217    ///
218    /// # Arguments
219    ///
220    /// * `group_id` - The group
221    ///
222    /// # Returns
223    ///
224    /// Number of members with admin role.
225    ///
226    /// # Errors
227    ///
228    /// Returns an error if storage cannot be accessed.
229    fn count_admins(&self, group_id: &GroupId) -> Result<u32>;
230
231    // =========================================================================
232    // Invite Operations
233    // =========================================================================
234
235    /// Creates an invite for a group.
236    ///
237    /// # Arguments
238    ///
239    /// * `group_id` - The group to invite to
240    /// * `role` - Role to assign when joined
241    /// * `created_by` - Email of the admin creating the invite
242    /// * `expires_in_secs` - How long until expiration
243    /// * `max_uses` - Maximum number of uses
244    ///
245    /// # Returns
246    ///
247    /// A tuple of (invite, `plaintext_token`). The token should be shared
248    /// with invitees and never stored.
249    ///
250    /// # Errors
251    ///
252    /// Returns an error if:
253    /// - The group doesn't exist
254    /// - Storage cannot be accessed
255    fn create_invite(
256        &self,
257        group_id: &GroupId,
258        role: GroupRole,
259        created_by: &str,
260        expires_in_secs: Option<u64>,
261        max_uses: Option<u32>,
262    ) -> Result<(GroupInvite, String)>;
263
264    /// Gets an invite by its token hash.
265    ///
266    /// # Arguments
267    ///
268    /// * `token_hash` - SHA256 hash of the token
269    ///
270    /// # Returns
271    ///
272    /// The invite if found and valid, None otherwise.
273    ///
274    /// # Errors
275    ///
276    /// Returns an error if storage cannot be accessed.
277    fn get_invite_by_token_hash(&self, token_hash: &str) -> Result<Option<GroupInvite>>;
278
279    /// Gets an invite by ID.
280    ///
281    /// # Arguments
282    ///
283    /// * `invite_id` - The invite identifier
284    ///
285    /// # Returns
286    ///
287    /// The invite if found, None otherwise.
288    ///
289    /// # Errors
290    ///
291    /// Returns an error if storage cannot be accessed.
292    fn get_invite(&self, invite_id: &str) -> Result<Option<GroupInvite>>;
293
294    /// Lists all invites for a group.
295    ///
296    /// # Arguments
297    ///
298    /// * `group_id` - The group
299    /// * `include_expired` - Whether to include expired/revoked invites
300    ///
301    /// # Returns
302    ///
303    /// List of invites.
304    ///
305    /// # Errors
306    ///
307    /// Returns an error if storage cannot be accessed.
308    fn list_invites(&self, group_id: &GroupId, include_expired: bool) -> Result<Vec<GroupInvite>>;
309
310    /// Increments the use count of an invite.
311    ///
312    /// Called when a user successfully joins using the invite.
313    ///
314    /// # Arguments
315    ///
316    /// * `invite_id` - The invite to update
317    ///
318    /// # Errors
319    ///
320    /// Returns an error if storage cannot be accessed.
321    fn increment_invite_uses(&self, invite_id: &str) -> Result<()>;
322
323    /// Revokes an invite.
324    ///
325    /// # Arguments
326    ///
327    /// * `invite_id` - The invite to revoke
328    ///
329    /// # Returns
330    ///
331    /// True if the invite was revoked, false if not found.
332    ///
333    /// # Errors
334    ///
335    /// Returns an error if storage cannot be accessed.
336    fn revoke_invite(&self, invite_id: &str) -> Result<bool>;
337
338    /// Deletes expired invites.
339    ///
340    /// # Returns
341    ///
342    /// Number of invites deleted.
343    ///
344    /// # Errors
345    ///
346    /// Returns an error if storage cannot be accessed.
347    fn cleanup_expired_invites(&self) -> Result<u64>;
348}