openauth_plugins/organization/
models.rs1use 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}