1use crate::AuthUser;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize, Serializer};
4use std::borrow::Cow;
5#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
7#[serde(try_from = "UserViewInput")]
8pub struct UserView {
9 pub id: String,
10 pub name: Option<String>,
11 pub email: Option<String>,
12 #[serde(rename = "emailVerified")]
13 pub email_verified: bool,
14 pub image: Option<String>,
15 #[serde(rename = "createdAt")]
16 pub created_at: DateTime<Utc>,
17 #[serde(rename = "updatedAt")]
18 pub updated_at: DateTime<Utc>,
19 #[serde(default)]
20 pub username: Option<String>,
21 #[serde(rename = "displayUsername", default)]
22 pub display_username: Option<String>,
23 #[serde(rename = "twoFactorEnabled", default)]
24 pub two_factor_enabled: Option<bool>,
25 #[serde(default)]
26 pub role: Option<String>,
27 #[serde(default)]
28 pub banned: Option<bool>,
29 #[serde(rename = "banReason", default)]
30 pub ban_reason: Option<String>,
31 #[serde(rename = "banExpires", default)]
32 pub ban_expires: Option<DateTime<Utc>>,
33 #[serde(rename = "isAnonymous", default)]
34 pub is_anonymous: Option<bool>,
35 #[serde(rename = "phoneNumber", default)]
36 pub phone_number: Option<String>,
37 #[serde(rename = "phoneNumberVerified", default)]
38 pub phone_number_verified: Option<bool>,
39 #[serde(rename = "lastLoginMethod", default)]
40 pub last_login_method: Option<String>,
41 #[serde(
43 flatten,
44 default,
45 deserialize_with = "crate::utils::json::deserialize_btree_map"
46 )]
47 pub extension_fields: std::collections::BTreeMap<String, serde_json::Value>,
48 #[serde(skip)]
49 pub metadata: serde_json::Value,
50 #[serde(skip)]
52 pub omitted_fields: std::collections::BTreeSet<String>,
53}
54
55impl Serialize for UserView {
56 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
57 use serde::ser::SerializeMap;
58 let mut map = serializer.serialize_map(None)?;
59 macro_rules! entry {
60 ($name:literal, $value:expr) => {
61 if !self.omitted_fields.contains($name) {
62 map.serialize_entry($name, $value)?;
63 }
64 };
65 }
66 entry!("id", &self.id);
67 entry!("name", &self.name);
68 entry!("email", &self.email);
69 if let Some(value) = self.extension_fields.get("emailVerified") {
70 entry!("emailVerified", value);
71 } else {
72 entry!("emailVerified", &self.email_verified);
73 }
74 entry!("image", &self.image);
75 entry!(
76 "createdAt",
77 &crate::utils::datetime::json_date_millis(self.created_at.timestamp_millis())
78 );
79 entry!(
80 "updatedAt",
81 &crate::utils::datetime::json_date_millis(self.updated_at.timestamp_millis())
82 );
83 if let Some(value) = &self.username {
84 entry!("username", value);
85 }
86 if let Some(value) = &self.display_username {
87 entry!("displayUsername", value);
88 }
89 if let Some(value) = &self.two_factor_enabled {
90 entry!("twoFactorEnabled", value);
91 }
92 if let Some(value) = &self.role {
93 entry!("role", value);
94 }
95 if let Some(value) = &self.banned {
96 entry!("banned", value);
97 }
98 if let Some(value) = &self.ban_reason {
99 entry!("banReason", value);
100 }
101 if let Some(value) = &self.ban_expires {
102 entry!(
103 "banExpires",
104 &crate::utils::datetime::json_date_millis(value.timestamp_millis())
105 );
106 }
107 if let Some(value) = &self.is_anonymous {
108 entry!("isAnonymous", value);
109 }
110 if let Some(value) = &self.phone_number {
111 entry!("phoneNumber", value);
112 }
113 if let Some(value) = &self.phone_number_verified {
114 entry!("phoneNumberVerified", value);
115 }
116 if let Some(value) = &self.last_login_method {
117 entry!("lastLoginMethod", value);
118 }
119 for (name, value) in &self.extension_fields {
120 if name != "emailVerified" && !self.omitted_fields.contains(name) {
121 map.serialize_entry(name, value)?;
122 }
123 }
124 map.end()
125 }
126}
127
128#[derive(Deserialize)]
129pub(in crate::wire) struct UserViewInput {
130 pub(in crate::wire) id: String,
131 pub(in crate::wire) name: Option<String>,
132 pub(in crate::wire) email: Option<String>,
133 #[serde(rename = "emailVerified")]
134 pub(in crate::wire) email_verified: serde_json::Value,
135 pub(in crate::wire) image: Option<String>,
136 #[serde(rename = "createdAt")]
137 pub(in crate::wire) created_at: DateTime<Utc>,
138 #[serde(rename = "updatedAt")]
139 pub(in crate::wire) updated_at: DateTime<Utc>,
140 #[serde(default)]
141 pub(in crate::wire) username: Option<String>,
142 #[serde(rename = "displayUsername", default)]
143 pub(in crate::wire) display_username: Option<String>,
144 #[serde(rename = "twoFactorEnabled", default)]
145 pub(in crate::wire) two_factor_enabled: Option<bool>,
146 #[serde(default)]
147 pub(in crate::wire) role: Option<String>,
148 #[serde(default)]
149 pub(in crate::wire) banned: Option<bool>,
150 #[serde(rename = "banReason", default)]
151 pub(in crate::wire) ban_reason: Option<String>,
152 #[serde(rename = "banExpires", default)]
153 pub(in crate::wire) ban_expires: Option<DateTime<Utc>>,
154 #[serde(rename = "isAnonymous", default)]
155 pub(in crate::wire) is_anonymous: Option<bool>,
156 #[serde(rename = "phoneNumber", default)]
157 pub(in crate::wire) phone_number: Option<String>,
158 #[serde(rename = "phoneNumberVerified", default)]
159 pub(in crate::wire) phone_number_verified: Option<bool>,
160 #[serde(rename = "lastLoginMethod", default)]
161 pub(in crate::wire) last_login_method: Option<String>,
162 #[serde(
164 flatten,
165 default,
166 deserialize_with = "crate::utils::json::deserialize_btree_map"
167 )]
168 pub(in crate::wire) extension_fields: std::collections::BTreeMap<String, serde_json::Value>,
169 #[serde(skip)]
170 pub(in crate::wire) metadata: serde_json::Value,
171}
172
173impl TryFrom<UserViewInput> for UserView {
174 type Error = &'static str;
175 fn try_from(mut input: UserViewInput) -> Result<Self, Self::Error> {
176 let email_verified = match &input.email_verified {
177 serde_json::Value::Bool(value) => *value,
178 serde_json::Value::String(value) => !value.is_empty(),
179 _ => {
180 return Err("User verification output must be a boolean or retained SQLite string");
181 }
182 };
183 if !input.email_verified.is_boolean() {
184 _ = input
185 .extension_fields
186 .insert("emailVerified".into(), input.email_verified);
187 }
188 Ok(Self {
189 email_verified,
190 extension_fields: input.extension_fields,
191 id: input.id,
192 name: input.name,
193 email: input.email,
194 image: input.image,
195 created_at: input.created_at,
196 updated_at: input.updated_at,
197 username: input.username,
198 display_username: input.display_username,
199 two_factor_enabled: input.two_factor_enabled,
200 role: input.role,
201 banned: input.banned,
202 ban_reason: input.ban_reason,
203 ban_expires: input.ban_expires,
204 is_anonymous: input.is_anonymous,
205 phone_number: input.phone_number,
206 phone_number_verified: input.phone_number_verified,
207 last_login_method: input.last_login_method,
208 metadata: input.metadata,
209 omitted_fields: std::collections::BTreeSet::default(),
210 })
211 }
212}
213
214impl<T: AuthUser> From<&T> for UserView {
215 fn from(user: &T) -> Self {
216 let mut view = Self {
217 id: user.id().into_owned(),
218 name: user.name().map(str::to_owned),
219 email: user.email().map(str::to_owned),
220 email_verified: user.email_verified(),
221 image: user.image().map(str::to_owned),
222 created_at: user.created_at(),
223 updated_at: user.updated_at(),
224 username: user.username().map(str::to_owned),
225 display_username: user.display_username().map(str::to_owned),
226 two_factor_enabled: user.two_factor_enabled_value(),
227 role: user.role().map(str::to_owned),
228 banned: user.banned_value(),
229 ban_reason: user.ban_reason().map(str::to_owned),
230 ban_expires: user.ban_expires(),
231 is_anonymous: user.is_anonymous(),
232 phone_number: user.phone_number().map(str::to_owned),
233 phone_number_verified: user.phone_number_verified(),
234 last_login_method: user.last_login_method().map(str::to_owned),
235 extension_fields: std::collections::BTreeMap::default(),
236 metadata: user.metadata().clone(),
237 omitted_fields: std::collections::BTreeSet::default(),
238 };
239 if let Some(value) = user
240 .adapter_snapshot()
241 .and_then(|output| output.values().get("emailVerified"))
242 && !value.is_boolean()
243 {
244 _ = view
245 .extension_fields
246 .insert("emailVerified".into(), value.clone());
247 }
248 view
249 }
250}
251
252impl AuthUser for UserView {
253 fn retained_user_view(&self) -> Option<&UserView> {
254 Some(self)
255 }
256 fn additional_fields(&self) -> crate::field_policy::FieldOutput {
257 self.extension_fields.clone().into_iter().collect()
258 }
259 fn id(&self) -> Cow<'_, str> {
260 Cow::Borrowed(&self.id)
261 }
262 fn email(&self) -> Option<&str> {
263 self.email.as_deref()
264 }
265 fn name(&self) -> Option<&str> {
266 self.name.as_deref()
267 }
268 fn email_verified(&self) -> bool {
269 self.email_verified
270 }
271 fn image(&self) -> Option<&str> {
272 self.image.as_deref()
273 }
274 fn created_at(&self) -> DateTime<Utc> {
275 self.created_at
276 }
277 fn updated_at(&self) -> DateTime<Utc> {
278 self.updated_at
279 }
280 fn username(&self) -> Option<&str> {
281 self.username.as_deref()
282 }
283 fn display_username(&self) -> Option<&str> {
284 self.display_username.as_deref()
285 }
286 fn two_factor_enabled(&self) -> bool {
287 self.two_factor_enabled.unwrap_or(false)
288 }
289
290 fn two_factor_enabled_value(&self) -> Option<bool> {
291 self.two_factor_enabled
292 }
293 fn role(&self) -> Option<&str> {
294 self.role.as_deref()
295 }
296 fn banned(&self) -> bool {
297 self.banned.unwrap_or(false)
298 }
299
300 fn banned_value(&self) -> Option<bool> {
301 self.banned
302 }
303 fn ban_reason(&self) -> Option<&str> {
304 self.ban_reason.as_deref()
305 }
306 fn ban_expires(&self) -> Option<DateTime<Utc>> {
307 self.ban_expires
308 }
309 fn is_anonymous(&self) -> Option<bool> {
310 self.is_anonymous
311 }
312 fn phone_number(&self) -> Option<&str> {
313 self.phone_number.as_deref()
314 }
315 fn phone_number_verified(&self) -> Option<bool> {
316 self.phone_number_verified
317 }
318 fn last_login_method(&self) -> Option<&str> {
319 self.last_login_method.as_deref()
320 }
321 fn metadata(&self) -> &serde_json::Value {
322 &self.metadata
323 }
324}