1use super::*;
2#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
4pub struct OrganizationView {
5 #[serde(default, flatten)]
6 pub additional_fields: std::collections::BTreeMap<String, serde_json::Value>,
7 pub id: String,
8 pub name: String,
9 pub slug: String,
10 pub logo: Option<String>,
11 #[serde(
12 serialize_with = "serialize_json_option_as_string",
13 skip_serializing_if = "Option::is_none"
14 )]
15 #[serde(
16 default,
17 deserialize_with = "crate::utils::json::deserialize_optional_value"
18 )]
19 pub metadata: Option<serde_json::Value>,
20 #[serde(rename = "createdAt")]
21 #[serde(serialize_with = "crate::utils::datetime::serialize")]
22 pub created_at: DateTime<Utc>,
23 #[serde(rename = "updatedAt")]
24 #[serde(serialize_with = "crate::utils::datetime::serialize")]
25 pub updated_at: DateTime<Utc>,
26}
27
28impl<T: AuthOrganization> From<&T> for OrganizationView {
29 fn from(org: &T) -> Self {
30 Self {
31 additional_fields: org.additional_fields(),
32 id: org.id().into_owned(),
33 name: org.name().to_owned(),
34 slug: org.slug().to_owned(),
35 logo: org.logo().map(str::to_owned),
36 metadata: org.metadata().cloned(),
37 created_at: org.created_at(),
38 updated_at: org.updated_at(),
39 }
40 }
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
45pub struct InvitationView {
46 pub id: String,
47 #[serde(rename = "organizationId")]
48 pub organization_id: String,
49 pub email: String,
50 pub role: Option<String>,
51 pub status: InvitationStatus,
52 #[serde(rename = "inviterId")]
53 pub inviter_id: String,
54 #[serde(rename = "expiresAt")]
55 #[serde(serialize_with = "crate::utils::datetime::serialize")]
56 pub expires_at: DateTime<Utc>,
57 #[serde(rename = "createdAt")]
58 #[serde(serialize_with = "crate::utils::datetime::serialize")]
59 pub created_at: DateTime<Utc>,
60 #[serde(rename = "teamId", default, skip_serializing_if = "Option::is_none")]
61 pub team_id: Option<String>,
62 #[serde(
63 flatten,
64 default,
65 deserialize_with = "crate::utils::json::deserialize_btree_map"
66 )]
67 pub extension_fields: std::collections::BTreeMap<String, serde_json::Value>,
68}
69
70impl<T: AuthInvitation> From<&T> for InvitationView {
71 fn from(inv: &T) -> Self {
72 Self {
73 id: inv.id().into_owned(),
74 organization_id: inv.organization_id().into_owned(),
75 email: inv.email().to_owned(),
76 role: inv.optional_role().map(str::to_owned),
77 status: inv.status().clone(),
78 inviter_id: inv.inviter_id().into_owned(),
79 expires_at: inv.expires_at(),
80 created_at: inv.created_at(),
81 team_id: inv.team_id().map(str::to_owned),
82 extension_fields: std::collections::BTreeMap::default(),
83 }
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
89pub struct PasskeyView {
90 pub id: String,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub name: Option<String>,
93 #[serde(rename = "credentialID")]
94 pub credential_id: String,
95 #[serde(rename = "userId")]
96 pub user_id: String,
97 #[serde(rename = "publicKey")]
98 pub public_key: String,
99 pub counter: u64,
100 #[serde(rename = "deviceType")]
101 pub device_type: String,
102 #[serde(rename = "backedUp")]
103 pub backed_up: bool,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub transports: Option<String>,
106 #[serde(rename = "createdAt")]
107 pub created_at: String,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub aaguid: Option<String>,
110}
111
112impl<T: AuthPasskey> From<&T> for PasskeyView {
113 fn from(pk: &T) -> Self {
114 Self {
115 id: pk.id().into_owned(),
116 name: pk.name().map(str::to_owned),
117 credential_id: pk.credential_id().to_owned(),
118 user_id: pk.user_id().into_owned(),
119 public_key: pk.public_key().to_owned(),
120 counter: pk.counter(),
121 device_type: pk.device_type().to_owned(),
122 backed_up: pk.backed_up(),
123 transports: pk.transports().map(str::to_owned),
124 created_at: pk
125 .created_at()
126 .to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
127 aaguid: pk.aaguid().map(str::to_owned),
128 }
129 }
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
137pub struct ApiKeyView {
138 pub id: String,
139 pub name: Option<String>,
140 pub start: Option<String>,
141 pub prefix: Option<String>,
142 #[serde(rename = "referenceId")]
143 pub reference_id: String,
144 #[serde(rename = "configId")]
145 pub config_id: String,
146 #[serde(rename = "refillInterval")]
147 #[serde(serialize_with = "serialize_api_key_number")]
148 pub refill_interval: Option<f64>,
149 #[serde(rename = "refillAmount")]
150 #[serde(serialize_with = "serialize_api_key_number")]
151 pub refill_amount: Option<f64>,
152 #[serde(rename = "lastRefillAt")]
153 pub last_refill_at: Option<String>,
154 pub enabled: bool,
155 #[serde(rename = "rateLimitEnabled")]
156 pub rate_limit_enabled: bool,
157 #[serde(rename = "rateLimitTimeWindow")]
158 #[serde(serialize_with = "serialize_api_key_number")]
159 pub rate_limit_time_window: Option<f64>,
160 #[serde(rename = "rateLimitMax")]
161 #[serde(serialize_with = "serialize_api_key_number")]
162 pub rate_limit_max: Option<f64>,
163 #[serde(rename = "requestCount")]
164 #[serde(serialize_with = "serialize_api_key_number")]
165 pub request_count: Option<f64>,
166 #[serde(serialize_with = "serialize_api_key_number")]
167 pub remaining: Option<f64>,
168 #[serde(rename = "lastRequest")]
169 pub last_request: Option<String>,
170 #[serde(rename = "expiresAt")]
171 pub expires_at: Option<String>,
172 #[serde(rename = "createdAt")]
173 pub created_at: String,
174 #[serde(rename = "updatedAt")]
175 pub updated_at: String,
176 #[serde(
177 default,
178 deserialize_with = "crate::utils::json::deserialize_optional_value"
179 )]
180 pub permissions: Option<serde_json::Value>,
181 #[serde(
182 default,
183 deserialize_with = "crate::utils::json::deserialize_optional_value"
184 )]
185 pub metadata: Option<serde_json::Value>,
186}
187
188impl<T: AuthApiKey> From<&T> for ApiKeyView {
189 fn from(ak: &T) -> Self {
190 Self {
191 id: ak.id().into_owned(),
192 name: ak.name().map(str::to_owned),
193 start: ak.start().map(str::to_owned),
194 prefix: ak.prefix().map(str::to_owned),
195 reference_id: ak.reference_id().into_owned(),
196 config_id: ak.config_id().into_owned(),
197 refill_interval: ak.refill_interval(),
198 refill_amount: ak.refill_amount(),
199 last_refill_at: ak.last_refill_at().map(api_key_wire_date),
200 enabled: ak.enabled(),
201 rate_limit_enabled: ak.rate_limit_enabled(),
202 rate_limit_time_window: ak.rate_limit_time_window(),
203 rate_limit_max: ak.rate_limit_max(),
204 request_count: ak.request_count(),
205 remaining: ak.remaining(),
206 last_request: ak.last_request().map(api_key_wire_date),
207 expires_at: ak.expires_at().map(api_key_wire_date),
208 created_at: api_key_wire_date(ak.created_at()),
209 updated_at: api_key_wire_date(ak.updated_at()),
210 permissions: ak
211 .permissions()
212 .and_then(|s| crate::utils::json::from_slice(s.as_bytes()).ok())
213 .map(|mut value| {
214 normalize_api_key_permission_dates(&mut value);
215 value
216 }),
217 metadata: ak.metadata().and_then(|s| {
218 let value = crate::utils::json::parse_value(s).ok()?;
219 if let crate::utils::json::JsValue::String(encoded) = value {
220 let mut value = crate::utils::json::parse_value(&encoded)
221 .ok()?
222 .to_json_value()
223 .ok()?;
224 normalize_api_key_permission_dates(&mut value);
225 Some(value)
226 } else {
227 value.to_json_value().ok()
228 }
229 }),
230 }
231 }
232}
233
234pub(in crate::wire) fn normalize_api_key_permission_dates(value: &mut serde_json::Value) {
235 match value {
236 serde_json::Value::String(text) => {
237 if let Some(date) = crate::utils::datetime::normalize_json_date(text) {
238 *text = date;
239 }
240 }
241 serde_json::Value::Array(values) => {
242 for value in values {
243 normalize_api_key_permission_dates(value);
244 }
245 }
246 serde_json::Value::Object(values) => {
247 for value in values.values_mut() {
248 normalize_api_key_permission_dates(value);
249 }
250 }
251 _ => {}
252 }
253}
254
255#[expect(
260 clippy::ref_option,
261 reason = "Serde field serializers receive a reference to the declared Option field type"
262)]
263pub(in crate::wire) fn serialize_json_option_as_string<S>(
264 value: &Option<serde_json::Value>,
265 serializer: S,
266) -> Result<S::Ok, S::Error>
267where
268 S: Serializer,
269{
270 match value {
271 Some(inner) => serializer
272 .serialize_some(&serde_json::to_string(inner).map_err(serde::ser::Error::custom)?),
273 None => serializer.serialize_none(),
274 }
275}
276
277#[expect(
279 clippy::as_conversions,
280 clippy::cast_possible_truncation,
281 reason = "JavaScript-compatible numbers deliberately retain IEEE754 rounding and guarded integer coercion at the wire or adapter boundary"
282)]
283#[expect(
284 clippy::ref_option,
285 reason = "Serde field serializers receive a reference to the declared Option field type"
286)]
287pub(in crate::wire) fn serialize_api_key_number<S: Serializer>(
288 value: &Option<f64>,
289 serializer: S,
290) -> Result<S::Ok, S::Error> {
291 match value {
292 Some(value) if value.fract() == 0.0 && value.abs() <= 9_007_199_254_740_991.0 => {
293 serializer.serialize_i64(*value as i64)
294 }
295 value => value.serialize(serializer),
296 }
297}
298
299pub(in crate::wire) fn api_key_wire_date(value: &str) -> String {
302 DateTime::parse_from_rfc3339(value).map_or_else(
303 |_| value.to_owned(),
304 |date| {
305 date.with_timezone(&Utc)
306 .to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
307 },
308 )
309}