sofie_tenancy/
invitation.rs1use std::time::Duration;
2
3use sofie::core::Instant;
4use sofie::db::{Query, Source};
5use sofie::prelude::*;
6
7use sofie_identity::AccountId;
8
9use crate::organization::{OrganizationId, OrganizationRole};
10
11pub(crate) const VALID_FOR: Duration = Duration::from_hours(7 * 24);
13pub(crate) const NOT_OPEN: &str = "This invitation was accepted already or has expired.";
14
15#[derive(Aggregate)]
17pub struct Invitation {
18 meta: Meta,
19 #[owner]
20 organization: OrganizationId,
21 email: Email,
22 role: OrganizationRole,
23 invited_by: AccountId,
24 expires_at: Instant,
25 accepted_at: Option<Instant>,
26}
27
28impl Invitation {
29 pub(crate) fn new(
30 organization: OrganizationId,
31 email: Email,
32 role: OrganizationRole,
33 invited_by: AccountId,
34 now: Instant,
35 ) -> Result<Self> {
36 Ok(Self {
37 meta: Meta::new()?,
38 organization,
39 email,
40 role,
41 invited_by,
42 expires_at: now.checked_add(VALID_FOR)?,
43 accepted_at: None,
44 })
45 }
46
47 #[must_use]
48 pub fn organization(&self) -> OrganizationId {
49 self.organization
50 }
51
52 #[must_use]
53 pub fn email(&self) -> &Email {
54 &self.email
55 }
56
57 #[must_use]
58 pub fn role(&self) -> OrganizationRole {
59 self.role
60 }
61
62 pub(crate) fn invited_by(&self) -> AccountId {
63 self.invited_by
64 }
65
66 #[must_use]
67 pub fn expires_at(&self) -> Instant {
68 self.expires_at
69 }
70
71 #[must_use]
72 pub fn is_open(&self, now: Instant) -> bool {
73 self.accepted_at.is_none() && self.expires_at > now
74 }
75
76 pub(crate) fn withdraw(&mut self, now: Instant) -> Result<()> {
78 if !self.is_open(now) {
79 return Err(Error::Rule(NOT_OPEN.to_owned()));
80 }
81 self.expires_at = now;
82 Ok(())
83 }
84
85 pub(crate) fn accept(&mut self, now: Instant) -> Result<()> {
86 if !self.is_open(now) {
87 return Err(Error::Rule(NOT_OPEN.to_owned()));
88 }
89 self.accepted_at = Some(now);
90 Ok(())
91 }
92}
93
94pub(crate) fn pending<T: Source<Base = Invitation>>(
96 query: Query<T>,
97 organization: OrganizationId,
98 now: Instant,
99) -> Query<T> {
100 query
101 .where_(Invitation::ORGANIZATION.eq(organization))
102 .where_(Invitation::ACCEPTED_AT.is_null())
103 .where_(Invitation::EXPIRES_AT.gt(now))
104}