Skip to main content

sova_scope/
membership.rs

1//! Scope membership queries.
2
3use 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}