sofie_identity/membership/
adding.rs1use sofie::db::{Outcome, Plan, Stamp};
2use sofie::prelude::*;
3
4use super::{Membership, factory, held};
5use crate::account::{ALREADY_REGISTERED, Account, Distrusted, EMAIL_INDEX};
6
7const MEMBERSHIP_INDEX: &str = "membership_account_area";
8const MEMBER_ALREADY: &str = "is a member already";
9
10pub struct MemberAdded {
13 membership: Membership,
14 account: Adopted,
15}
16
17enum Adopted {
18 Created(Account),
19 Distrusted(Account, Distrusted),
20 Kept,
21}
22
23impl MemberAdded {
24 #[must_use]
25 pub fn account(&self) -> crate::AccountId {
26 self.membership.account
27 }
28
29 #[cfg(feature = "permissions")]
30 pub async fn with_role(
31 mut self,
32 read: &Read,
33 clock: &Clock,
34 role: crate::RoleId,
35 ) -> Result<Self> {
36 if matches!(self.account, Adopted::Kept)
37 && super::roles::gives(&self.membership, Some(role))
38 && let Some((account, distrusted)) =
39 super::roles::unproven(read, self.membership.account, clock).await?
40 {
41 self.account = Adopted::Distrusted(account, distrusted);
42 }
43 self.membership.role = Some(role);
44 Ok(self)
45 }
46}
47
48pub async fn add_member(
54 read: &Read,
55 clock: &Clock,
56 email: Email,
57 area: &str,
58) -> Result<MemberAdded> {
59 if area.trim().is_empty() {
60 return Err(Error::invalid("area", "is required"));
61 }
62 let Some(mut existing) = Account::of_email(read, email.clone()).await? else {
63 let account = crate::account::factory::without_password(email)?;
64 let membership = factory::grant(account.id(), area)?;
65 return Ok(MemberAdded {
66 membership,
67 account: Adopted::Created(account),
68 });
69 };
70 if let Some(membership) = held(read, existing.id(), area).await? {
71 return Ok(MemberAdded {
72 membership,
73 account: Adopted::Kept,
74 });
75 }
76 let membership = factory::grant(existing.id(), area)?;
77 if existing.is_confirmed() {
78 return Ok(MemberAdded {
79 membership,
80 account: Adopted::Kept,
81 });
82 }
83 let distrusted = existing.distrust(read, clock.now()).await?;
84 Ok(MemberAdded {
85 membership,
86 account: Adopted::Distrusted(existing, distrusted),
87 })
88}
89
90#[derive(Input)]
91pub struct AddingMember {
92 email: Email,
93 #[max(64)]
94 area: String,
95 #[max(200)]
96 role: Option<String>,
97}
98
99pub async fn add_member_task(
102 adding: AddingMember,
103 read: Read,
104 clock: Clock,
105) -> Result<MemberAdded> {
106 let added = add_member(&read, &clock, adding.email, &adding.area).await?;
107 let Some(role) = adding.role else {
108 return Ok(added);
109 };
110 #[cfg(feature = "permissions")]
111 return added
112 .with_role(&read, &clock, crate::role_named(&read, &role).await?)
113 .await;
114 #[cfg(not(feature = "permissions"))]
115 {
116 let _ = (added, role);
117 Err(Error::invalid(
118 "role",
119 "needs sofie-identity's `permissions`",
120 ))
121 }
122}
123
124impl Outcome for MemberAdded {
125 fn plan(&self, plan: &mut Plan<'_>) -> Result<()> {
126 match &self.account {
127 Adopted::Created(account) => created(plan, account)?,
128 Adopted::Distrusted(account, distrusted) => {
129 created(plan, account)?;
130 distrusted.plan(plan)?;
131 }
132 Adopted::Kept => {}
133 }
134 plan.unique(MEMBERSHIP_INDEX, "area", MEMBER_ALREADY);
135 self.membership.plan(plan)
136 }
137
138 fn committed(&mut self, stamp: Stamp) {
139 match &mut self.account {
140 Adopted::Created(account) => account.committed(stamp),
141 Adopted::Distrusted(account, distrusted) => {
142 account.committed(stamp);
143 distrusted.committed(stamp);
144 }
145 Adopted::Kept => {}
146 }
147 self.membership.committed(stamp);
148 }
149}
150
151fn created(plan: &mut Plan<'_>, account: &Account) -> Result<()> {
152 plan.unique(EMAIL_INDEX, "email", ALREADY_REGISTERED);
153 account.plan(plan)
154}