Skip to main content

openauth_plugins/organization/
models.rs

1use std::collections::BTreeMap;
2
3use openauth_core::db::{DbRecord, DbValue};
4use openauth_core::error::OpenAuthError;
5use serde::{Deserialize, Serialize};
6use time::OffsetDateTime;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct Organization {
11    pub id: String,
12    pub name: String,
13    pub slug: String,
14    pub logo: Option<String>,
15    pub metadata: Option<serde_json::Value>,
16    pub created_at: OffsetDateTime,
17    pub updated_at: Option<OffsetDateTime>,
18    #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
19    pub additional_fields: BTreeMap<String, serde_json::Value>,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "camelCase")]
24pub struct Member {
25    pub id: String,
26    pub organization_id: String,
27    pub user_id: String,
28    pub role: String,
29    pub created_at: OffsetDateTime,
30    #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
31    pub additional_fields: BTreeMap<String, serde_json::Value>,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(rename_all = "lowercase")]
36pub enum InvitationStatus {
37    Pending,
38    Accepted,
39    Rejected,
40    Canceled,
41}
42
43impl InvitationStatus {
44    pub fn as_str(self) -> &'static str {
45        match self {
46            Self::Pending => "pending",
47            Self::Accepted => "accepted",
48            Self::Rejected => "rejected",
49            Self::Canceled => "canceled",
50        }
51    }
52}
53
54impl TryFrom<&str> for InvitationStatus {
55    type Error = OpenAuthError;
56
57    fn try_from(value: &str) -> Result<Self, Self::Error> {
58        match value {
59            "pending" => Ok(Self::Pending),
60            "accepted" => Ok(Self::Accepted),
61            "rejected" => Ok(Self::Rejected),
62            "canceled" => Ok(Self::Canceled),
63            _ => Err(OpenAuthError::Adapter(format!(
64                "invalid invitation status `{value}`"
65            ))),
66        }
67    }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71#[serde(rename_all = "camelCase")]
72pub struct Invitation {
73    pub id: String,
74    pub organization_id: String,
75    pub email: String,
76    pub role: String,
77    pub status: InvitationStatus,
78    pub team_id: Option<String>,
79    pub expires_at: OffsetDateTime,
80    pub created_at: OffsetDateTime,
81    pub inviter_id: String,
82    #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
83    pub additional_fields: BTreeMap<String, serde_json::Value>,
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub struct Team {
89    pub id: String,
90    pub name: String,
91    pub organization_id: String,
92    pub created_at: OffsetDateTime,
93    pub updated_at: Option<OffsetDateTime>,
94    #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
95    pub additional_fields: BTreeMap<String, serde_json::Value>,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "camelCase")]
100pub struct TeamMember {
101    pub id: String,
102    pub team_id: String,
103    pub user_id: String,
104    pub created_at: OffsetDateTime,
105    #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
106    pub additional_fields: BTreeMap<String, serde_json::Value>,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110#[serde(rename_all = "camelCase")]
111pub struct OrganizationRoleRecord {
112    pub id: String,
113    pub organization_id: String,
114    pub role: String,
115    pub permission: serde_json::Value,
116    pub created_at: OffsetDateTime,
117    pub updated_at: Option<OffsetDateTime>,
118    #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
119    pub additional_fields: BTreeMap<String, serde_json::Value>,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub(crate) struct FullOrganization {
125    #[serde(flatten)]
126    pub organization: Organization,
127    pub members: Vec<Member>,
128    pub invitations: Vec<Invitation>,
129    #[serde(skip_serializing_if = "Vec::is_empty")]
130    pub teams: Vec<Team>,
131}
132
133pub(crate) fn required_string(record: &DbRecord, field: &str) -> Result<String, OpenAuthError> {
134    match record.get(field) {
135        Some(DbValue::String(value)) => Ok(value.clone()),
136        Some(_) => Err(invalid_field(field, "string")),
137        None => Err(missing_field(field)),
138    }
139}
140
141pub(crate) fn optional_string(
142    record: &DbRecord,
143    field: &str,
144) -> Result<Option<String>, OpenAuthError> {
145    match record.get(field) {
146        Some(DbValue::String(value)) => Ok(Some(value.clone())),
147        Some(DbValue::Null) | None => Ok(None),
148        Some(_) => Err(invalid_field(field, "string or null")),
149    }
150}
151
152pub(crate) fn required_timestamp(
153    record: &DbRecord,
154    field: &str,
155) -> Result<OffsetDateTime, OpenAuthError> {
156    match record.get(field) {
157        Some(DbValue::Timestamp(value)) => Ok(*value),
158        Some(_) => Err(invalid_field(field, "timestamp")),
159        None => Err(missing_field(field)),
160    }
161}
162
163pub(crate) fn optional_timestamp(
164    record: &DbRecord,
165    field: &str,
166) -> Result<Option<OffsetDateTime>, OpenAuthError> {
167    match record.get(field) {
168        Some(DbValue::Timestamp(value)) => Ok(Some(*value)),
169        Some(DbValue::Null) | None => Ok(None),
170        Some(_) => Err(invalid_field(field, "timestamp or null")),
171    }
172}
173
174pub(crate) fn optional_json(
175    record: &DbRecord,
176    field: &str,
177) -> Result<Option<serde_json::Value>, OpenAuthError> {
178    match record.get(field) {
179        Some(DbValue::Json(value)) => Ok(Some(value.clone())),
180        Some(DbValue::String(value)) => serde_json::from_str(value)
181            .map(Some)
182            .map_err(|error| OpenAuthError::Adapter(error.to_string())),
183        Some(DbValue::Null) | None => Ok(None),
184        Some(_) => Err(invalid_field(field, "json, string, or null")),
185    }
186}
187
188fn missing_field(field: &str) -> OpenAuthError {
189    OpenAuthError::Adapter(format!("record is missing `{field}`"))
190}
191
192fn invalid_field(field: &str, expected: &str) -> OpenAuthError {
193    OpenAuthError::Adapter(format!("record field `{field}` must be {expected}"))
194}