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