1use crate::audit::{AuditStore, AUDIT_ADDED, AUDIT_REMOVED, AUDIT_ROLE_CHANGED};
4use crate::cache::MutateOpts;
5use crate::entity::{scope_membership, ScopeMembership};
6use crate::types::{Membership, ScopeRef};
7use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
8use sova_core::Result;
9use sova_db::{ActiveModelTrait, DbError, DbHandle, Set};
10
11pub struct MembershipStore;
12
13impl MembershipStore {
14 pub async fn find(
15 db: &DbHandle,
16 scope: ScopeRef,
17 user_id: i64,
18 ) -> Result<Option<Membership>> {
19 Ok(ScopeMembership::find()
20 .filter(scope_membership::Column::ScopeKind.eq(scope.kind))
21 .filter(scope_membership::Column::ScopeId.eq(scope.id))
22 .filter(scope_membership::Column::UserId.eq(user_id))
23 .one(db)
24 .await
25 .map_err(DbError::from)?
26 .map(model_to_membership))
27 }
28
29 pub async fn require(
30 db: &DbHandle,
31 scope: ScopeRef,
32 user_id: i64,
33 ) -> Result<Membership> {
34 Self::find(db, scope, user_id)
35 .await?
36 .ok_or(sova_core::Error::NotFound)
37 }
38
39 pub async fn list_for_scope(
40 db: &DbHandle,
41 scope: ScopeRef,
42 ) -> Result<Vec<Membership>> {
43 Ok(ScopeMembership::find()
44 .filter(scope_membership::Column::ScopeKind.eq(scope.kind))
45 .filter(scope_membership::Column::ScopeId.eq(scope.id))
46 .all(db)
47 .await
48 .map_err(DbError::from)?
49 .into_iter()
50 .map(model_to_membership)
51 .collect())
52 }
53
54 pub async fn list_for_user(
55 db: &DbHandle,
56 scope_kind: &str,
57 user_id: i64,
58 ) -> Result<Vec<Membership>> {
59 Ok(ScopeMembership::find()
60 .filter(scope_membership::Column::ScopeKind.eq(scope_kind))
61 .filter(scope_membership::Column::UserId.eq(user_id))
62 .all(db)
63 .await
64 .map_err(DbError::from)?
65 .into_iter()
66 .map(model_to_membership)
67 .collect())
68 }
69
70 pub async fn add(
71 db: &DbHandle,
72 scope: ScopeRef,
73 user_id: i64,
74 role: &str,
75 ) -> Result<Membership> {
76 Self::add_with(db, scope, user_id, role, MutateOpts::default()).await
77 }
78
79 pub async fn add_with(
80 db: &DbHandle,
81 scope: ScopeRef,
82 user_id: i64,
83 role: &str,
84 opts: MutateOpts<'_>,
85 ) -> Result<Membership> {
86 let m = model_to_membership(
87 scope_membership::ActiveModel {
88 scope_kind: Set(scope.kind.clone()),
89 scope_id: Set(scope.id),
90 user_id: Set(user_id),
91 role: Set(role.into()),
92 ..Default::default()
93 }
94 .insert(db)
95 .await
96 .map_err(DbError::from)?,
97 );
98 let _ = AuditStore::record(
99 db,
100 &scope,
101 user_id,
102 opts.actor_id,
103 AUDIT_ADDED,
104 None,
105 Some(role),
106 )
107 .await;
108 if let Some(cache) = opts.cache {
109 cache.invalidate(&scope, user_id);
110 cache.put(m.clone());
111 }
112 Ok(m)
113 }
114
115 pub async fn update_role(
116 db: &DbHandle,
117 membership_id: i64,
118 role: &str,
119 ) -> Result<Membership> {
120 Self::update_role_with(db, membership_id, role, MutateOpts::default()).await
121 }
122
123 pub async fn update_role_with(
124 db: &DbHandle,
125 membership_id: i64,
126 role: &str,
127 opts: MutateOpts<'_>,
128 ) -> Result<Membership> {
129 let row = ScopeMembership::find_by_id(membership_id)
130 .one(db)
131 .await
132 .map_err(DbError::from)?
133 .ok_or(sova_core::Error::NotFound)?;
134 let old_role = row.role.clone();
135 let scope = ScopeRef::new(row.scope_kind.clone(), row.scope_id);
136 let user_id = row.user_id;
137 let mut am: scope_membership::ActiveModel = row.into();
138 am.role = Set(role.into());
139 let m = model_to_membership(am.update(db).await.map_err(DbError::from)?);
140 let _ = AuditStore::record(
141 db,
142 &scope,
143 user_id,
144 opts.actor_id,
145 AUDIT_ROLE_CHANGED,
146 Some(&old_role),
147 Some(role),
148 )
149 .await;
150 if let Some(cache) = opts.cache {
151 cache.invalidate(&scope, user_id);
152 cache.put(m.clone());
153 }
154 Ok(m)
155 }
156
157 pub async fn remove(db: &DbHandle, membership_id: i64) -> Result<()> {
158 Self::remove_with(db, membership_id, MutateOpts::default()).await
159 }
160
161 pub async fn remove_with(
162 db: &DbHandle,
163 membership_id: i64,
164 opts: MutateOpts<'_>,
165 ) -> Result<()> {
166 let row = ScopeMembership::find_by_id(membership_id)
167 .one(db)
168 .await
169 .map_err(DbError::from)?
170 .ok_or(sova_core::Error::NotFound)?;
171 let scope = ScopeRef::new(row.scope_kind.clone(), row.scope_id);
172 let user_id = row.user_id;
173 let old_role = row.role.clone();
174 ScopeMembership::delete_by_id(membership_id)
175 .exec(db)
176 .await
177 .map_err(DbError::from)?;
178 let _ = AuditStore::record(
179 db,
180 &scope,
181 user_id,
182 opts.actor_id,
183 AUDIT_REMOVED,
184 Some(&old_role),
185 None,
186 )
187 .await;
188 if let Some(cache) = opts.cache {
189 cache.invalidate(&scope, user_id);
190 }
191 Ok(())
192 }
193
194 pub async fn count_role(
195 db: &DbHandle,
196 scope: ScopeRef,
197 role: &str,
198 ) -> Result<usize> {
199 Ok(ScopeMembership::find()
200 .filter(scope_membership::Column::ScopeKind.eq(scope.kind))
201 .filter(scope_membership::Column::ScopeId.eq(scope.id))
202 .filter(scope_membership::Column::Role.eq(role))
203 .all(db)
204 .await
205 .map_err(DbError::from)?
206 .len())
207 }
208}
209
210fn model_to_membership(m: scope_membership::Model) -> Membership {
211 Membership {
212 id: m.id,
213 scope_kind: m.scope_kind,
214 scope_id: m.scope_id,
215 user_id: m.user_id,
216 role: m.role,
217 }
218}