sofie_tenancy/
account_data.rs1use std::collections::HashMap;
2
3use sofie::core::{Cast, SqlValue};
4use sofie::db::{Aggregate, BoxFuture, Db, Outcome, Plan, Step, Uuid};
5use sofie::prelude::*;
6use sofie::serde_json::{Value, json};
7use sofie_identity::AccountId;
8use sofie_identity::account_data::{Closable, PersonalData};
9
10use crate::organization::{
11 Held, Kept, Organization, OrganizationId, OrganizationMember, OrganizationRole,
12};
13
14pub struct OrganizationMemberships(Db);
17
18impl OrganizationMemberships {
19 #[must_use]
20 pub fn new(db: &Db) -> Self {
21 Self(db.clone())
22 }
23}
24
25impl PersonalData for OrganizationMemberships {
26 fn name(&self) -> &'static str {
27 "sofie_tenancy"
28 }
29
30 fn export<'a>(&'a self, _read: &'a Read, account: Uuid) -> BoxFuture<'a, Result<Value>> {
31 Box::pin(async move {
32 let held = of_account(&self.0.read(), account).await?;
33 Ok(json!(
34 held.iter()
35 .map(|member| json!({
36 "organization": member.organization().uuid(),
37 "role": member.role().name(),
38 }))
39 .collect::<Vec<_>>()
40 ))
41 })
42 }
43
44 fn closing<'a>(
45 &'a self,
46 _read: &'a Read,
47 account: Uuid,
48 ) -> BoxFuture<'a, Result<Vec<Box<dyn Closable>>>> {
49 Box::pin(async move {
50 let read = self.0.read();
51 let held = of_account(&read, account).await?;
52 let alone = owned_alone(&read, &held).await?;
53 if !alone.is_empty() {
54 return Err(last_owner_of(&read, alone).await?);
55 }
56 let mut held = held;
58 held.sort_by_key(|member| member.organization().uuid());
59 let mut closing = held
60 .into_iter()
61 .map(|mut member| {
62 member.leave()?;
63 let organization = member.organization();
64 let left: Box<dyn Closable> =
65 Box::new(Held::new(organization, Kept::AnOwner, Left(member)));
66 Ok(left)
67 })
68 .collect::<Result<Vec<_>>>()?;
69 closing.push(Box::new(Withdrawn(AccountId::from_uuid(account))));
70 Ok(closing)
71 })
72 }
73}
74
75async fn of_account(read: &Read, account: Uuid) -> Result<Vec<OrganizationMember>> {
76 OrganizationMember::query()
77 .where_(OrganizationMember::ACCOUNT.eq(AccountId::from_uuid(account)))
78 .all(read)
79 .await
80}
81
82async fn owned_alone(read: &Read, held: &[OrganizationMember]) -> Result<Vec<OrganizationId>> {
83 let owned: Vec<OrganizationId> = held
84 .iter()
85 .filter(|member| member.role() == OrganizationRole::Owner)
86 .map(OrganizationMember::organization)
87 .collect();
88 if owned.is_empty() {
89 return Ok(owned);
90 }
91 let owners = OrganizationMember::query()
92 .where_(OrganizationMember::ORGANIZATION.in_(owned.iter().copied()))
93 .where_(OrganizationMember::ROLE.eq(OrganizationRole::Owner))
94 .all(read)
95 .await?;
96 let mut counted: HashMap<OrganizationId, usize> = HashMap::new();
97 for owner in &owners {
98 *counted.entry(owner.organization()).or_default() += 1;
99 }
100 Ok(owned
101 .into_iter()
102 .filter(|organization| counted.get(organization).copied().unwrap_or(0) <= 1)
103 .collect())
104}
105
106async fn last_owner_of(read: &Read, organizations: Vec<OrganizationId>) -> Result<Error> {
107 let organizations = Organization::query()
108 .where_(Organization::ID.in_(organizations))
109 .all(read)
110 .await?;
111 let mut names: Vec<&str> = organizations.iter().map(Organization::name).collect();
112 names.sort_unstable();
113 Ok(Error::Rule(format!(
114 "This account is the last owner of {}; hand each over to another member or delete it \
115 first.",
116 names.join(", ")
117 )))
118}
119
120struct Left(OrganizationMember);
123
124impl Outcome for Left {
125 fn plan(&self, plan: &mut Plan<'_>) -> Result<()> {
126 let dialect = plan.dialect();
127 let [deleted_at, updated_at, version] =
128 ["deleted_at", "updated_at", "version"].map(|column| dialect.quote(column));
129 let sql = format!(
130 "UPDATE {} SET {deleted_at} = {now}, {updated_at} = {now}, {version} = {version} + 1 \
131 WHERE {} = {} AND {deleted_at} IS NULL",
132 dialect.qualified(OrganizationMember::MODULE, OrganizationMember::NAME),
133 dialect.quote("id"),
134 dialect.placeholder(1),
135 now = dialect.placeholder(2),
136 );
137 let mut params = vec![SqlValue::Uuid(self.0.id().uuid())];
138 plan.stamp().now.encode(&mut params);
139 plan.push(Step::statement(sql, params, Some(1)));
140 plan.raise(self.0.meta().events());
141 Ok(())
142 }
143}
144
145struct Withdrawn(AccountId);
148
149impl Outcome for Withdrawn {
150 fn plan(&self, plan: &mut Plan<'_>) -> Result<()> {
151 let dialect = plan.dialect();
152 let [expires_at, updated_at, version] =
153 ["expires_at", "updated_at", "version"].map(|column| dialect.quote(column));
154 let sql = format!(
155 "UPDATE {} SET {expires_at} = {now}, {updated_at} = {now}, {version} = {version} + 1 \
156 WHERE {} = {} AND {} IS NULL AND {expires_at} > {now}",
157 dialect.qualified(crate::Invitation::MODULE, crate::Invitation::NAME),
158 dialect.quote("invited_by"),
159 dialect.placeholder(1),
160 dialect.quote("accepted_at"),
161 now = dialect.placeholder(2),
162 );
163 let mut params = vec![SqlValue::Uuid(self.0.uuid())];
164 plan.stamp().now.encode(&mut params);
165 plan.push(Step::statement(sql, params, None));
166 Ok(())
167 }
168}