shared/domain/
user.rs

1//! Types for users.
2
3use chrono::{DateTime, NaiveDate, Utc};
4use macros::make_path_parts;
5use serde::{Deserialize, Serialize, Serializer};
6use std::fmt;
7use strum_macros::Display;
8
9use crate::domain::billing::{
10    AccountId, AmountInCents, PlanTier, PlanType, SchoolId, SubscriptionStatus, UserAccountSummary,
11};
12use crate::{
13    api::endpoints::PathPart,
14    domain::{
15        circle::CircleId,
16        image::ImageId,
17        meta::{AffiliationId, AgeRangeId, SubjectId},
18    },
19};
20
21pub mod public_user;
22
23wrap_uuid! {
24    /// Wrapper type around [`Uuid`], represents the ID of a User.
25    pub struct UserId
26}
27
28/// Represents a user's permissions.
29///
30/// Note: 5 was `ManageModule`, and has been deleted, but cannot be replaced(?)
31#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone, Copy)]
32#[non_exhaustive]
33#[repr(i16)]
34pub enum UserScope {
35    /// The user has access to everything, implies all other scopes.
36    Admin = 1,
37
38    /// The user can create/delete/modify categories
39    ManageCategory = 2,
40
41    /// The user can create/delete/modify images.
42    ManageImage = 3,
43
44    /// The user can delete/modify *any* asset.
45    AdminAsset = 4,
46
47    /// The user can create/delete/modify animations.
48    ManageAnimation = 6,
49
50    /// The user can create/delete/modify locale entries.
51    ManageEntry = 7,
52
53    /// The user can create/modify/delete assets of their own.
54    ManageSelfAsset = 8,
55
56    /// The User can create/delete/modify audio files of their own.
57    ManageAudio = 9,
58
59    /// The User can create resource focused jigs.
60    Resources = 10,
61}
62
63impl TryFrom<i16> for UserScope {
64    type Error = anyhow::Error;
65
66    fn try_from(i: i16) -> Result<Self, Self::Error> {
67        match i {
68            1 => Ok(Self::Admin),
69            2 => Ok(Self::ManageCategory),
70            3 => Ok(Self::ManageImage),
71            4 => Ok(Self::AdminAsset),
72            6 => Ok(Self::ManageAnimation),
73            7 => Ok(Self::ManageEntry),
74            8 => Ok(Self::ManageSelfAsset),
75            9 => Ok(Self::ManageAudio),
76            10 => Ok(Self::Resources),
77            _ => anyhow::bail!("Scope {} is invalid", i),
78        }
79    }
80}
81
82make_path_parts!(UserLookupPath => "/v1/user/lookup");
83
84/// Query to lookup a user by unique data
85/// no filters will return that the user does not exist.
86/// multiple filters will act as a logical `OR` of them (multiple choices will return an arbitrary user).
87#[derive(Debug, Serialize, Deserialize, Clone, Default)]
88pub struct UserLookupQuery {
89    /// The user ID we're filtering by.
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub id: Option<UserId>,
92
93    /// The name we're filtering by.
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub name: Option<String>,
96}
97
98/// Publicly accessible information about a user.
99#[derive(Debug, Serialize, Deserialize, Clone)]
100pub struct OtherUser {
101    /// The user's id.
102    pub id: UserId,
103}
104
105make_path_parts!(ResetEmailPath => "/v1/user/me/reset-email");
106
107/// Update user email request
108#[derive(Debug, Serialize, Deserialize, Clone, Default)]
109pub struct ResetEmailRequest {
110    /// user's email
111    pub email: String,
112}
113
114/// Update user email response (returns the paseto token for the user)
115#[derive(Debug, Serialize, Deserialize, Clone, Default)]
116pub struct ResetEmailResponse {
117    /// paseto token with user's email
118    pub paseto_token: String,
119}
120
121/// user badge
122#[derive(Debug, Display, Serialize, Deserialize, Clone, Copy)]
123#[cfg_attr(feature = "backend", derive(sqlx::Type))]
124#[serde(rename_all = "camelCase")]
125#[strum(serialize_all = "camelCase")]
126#[repr(i16)]
127pub enum UserBadge {
128    /// Master teacher
129    MasterTeacher = 0,
130    /// JI team member
131    JiTeam = 1,
132    ///  No Badge
133    NoBadge = 10,
134}
135
136impl UserBadge {
137    /// get str
138    pub fn as_str(&self) -> &'static str {
139        match self {
140            UserBadge::MasterTeacher => "master-teacher",
141            UserBadge::JiTeam => "ji-team",
142            UserBadge::NoBadge => "",
143        }
144    }
145
146    /// get display name
147    pub fn display_name(&self) -> &'static str {
148        match self {
149            UserBadge::MasterTeacher => "Master Teacher",
150            UserBadge::JiTeam => "JI Team",
151            UserBadge::NoBadge => "",
152        }
153    }
154}
155
156/// A user's profile.
157#[derive(Debug, Serialize, Deserialize, Clone)]
158pub struct UserProfile {
159    /// The user's id.
160    pub id: UserId,
161
162    /// The user's username.
163    pub username: String,
164
165    /// The user's email address.
166    pub email: String,
167
168    /// Indicator for Oauth email
169    pub is_oauth: bool,
170
171    /// The user's given name (first name)
172    pub given_name: String,
173
174    /// The user's family name (last name)
175    pub family_name: String,
176
177    /// ID to the user's profile image in the user image library.
178    pub profile_image: Option<ImageId>,
179
180    /// The user's preferred application language.
181    pub language_app: String,
182
183    /// The user's preferred email language.
184    pub language_emails: String,
185
186    /// The user's preferred language.
187    pub languages_spoken: Vec<String>,
188
189    /// Does the user want educational resources sent to them?
190    pub opt_into_edu_resources: bool,
191
192    /// Is the user over 18 years old?
193    pub over_18: bool,
194
195    /// The user's timezone.
196    pub timezone: chrono_tz::Tz,
197
198    /// Bio for User
199    pub bio: String,
200
201    /// Badge of User
202    pub badge: Option<UserBadge>,
203
204    /// Allow location to be public
205    #[serde(default)]
206    pub location_public: bool,
207
208    /// Allow organization to be public
209    #[serde(default)]
210    pub organization_public: bool, // default to false
211
212    /// Allow persona to be public
213    #[serde(default)]
214    pub persona_public: bool, // default to false
215
216    /// Allow languages_spoken to be public
217    #[serde(default)]
218    pub languages_spoken_public: bool, // default to false
219
220    /// Allow bio to be public
221    #[serde(default)]
222    pub bio_public: bool, // default to false
223
224    /// User associated Circles
225    #[serde(default)]
226    #[serde(skip_serializing_if = "Vec::is_empty")]
227    pub circles: Vec<CircleId>,
228
229    /// The scopes associated with the user.
230    #[serde(default)]
231    #[serde(skip_serializing_if = "Vec::is_empty")]
232    pub scopes: Vec<UserScope>,
233
234    /// When the user was created.
235    pub created_at: DateTime<Utc>,
236
237    /// When the user was last updated.
238    #[serde(default)]
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub updated_at: Option<DateTime<Utc>>,
241
242    /// The organization that the user belongs to.
243    #[serde(default)]
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub organization: Option<String>,
246
247    /// The persona of the user
248    #[serde(default)]
249    #[serde(skip_serializing_if = "Vec::is_empty")]
250    pub persona: Vec<String>,
251
252    /// The user's taught subjects.
253    #[serde(default)]
254    #[serde(skip_serializing_if = "Vec::is_empty")]
255    pub subjects: Vec<SubjectId>,
256
257    /// The user's age-ranges.
258    #[serde(default)]
259    #[serde(skip_serializing_if = "Vec::is_empty")]
260    pub age_ranges: Vec<AgeRangeId>,
261
262    /// The user's affiliations.
263    #[serde(default)]
264    #[serde(skip_serializing_if = "Vec::is_empty")]
265    pub affiliations: Vec<AffiliationId>,
266
267    /// The user's location
268    #[serde(default)]
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub location: Option<serde_json::Value>,
271
272    /// Number of Jigs
273    #[serde(default)]
274    pub jig_count: u64,
275
276    /// Number of Resources
277    #[serde(default)]
278    pub resource_count: u64,
279
280    /// Number of Courses
281    #[serde(default)]
282    pub course_count: u64,
283
284    /// Number of playlists
285    #[serde(default)]
286    pub playlist_count: u64,
287
288    /// Total number of assets
289    #[serde(default)]
290    pub total_asset_count: u64,
291
292    /// The user's account summary, if available.
293    ///
294    /// Note: This is not set when fetching a user profile. It must be explicitly set using a
295    /// function such as [`db::account::get_user_account_summary()`]
296    #[serde(default)]
297    #[serde(skip_serializing_if = "Option::is_none")]
298    pub account_summary: Option<UserAccountSummary>,
299}
300
301impl UserProfile {
302    /// Returns the school or organization associated with this user if any.
303    pub fn school_or_organization(&self) -> Option<&str> {
304        self.account_summary
305            .as_ref()
306            .and_then(|summary| summary.school_name.as_deref())
307            .or(self.organization.as_deref())
308    }
309}
310
311/// Login types
312#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
313#[cfg_attr(feature = "backend", derive(sqlx::Type))]
314pub enum UserLoginType {
315    /// Google
316    Google,
317    /// Email
318    Email,
319}
320
321impl fmt::Display for UserLoginType {
322    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323        match self {
324            UserLoginType::Google => write!(f, "Google"),
325            UserLoginType::Email => write!(f, "Email"),
326        }
327    }
328}
329
330/// User Response (used for Admin).
331#[derive(Debug, Serialize, Deserialize, Clone)]
332#[serde(rename_all = "camelCase")]
333pub struct UserResponse {
334    /// The user's id.
335    pub id: UserId,
336
337    /// The user's username.
338    pub username: String,
339
340    /// The user's given name (first name)
341    pub given_name: String,
342
343    /// The user's family name (last name)
344    pub family_name: String,
345
346    /// The user's email address.
347    pub email: String,
348
349    /// The user's country.
350    #[serde(default)]
351    pub country: Option<String>,
352
353    /// The user's state.
354    #[serde(default)]
355    pub state: Option<String>,
356
357    /// The user's city.
358    #[serde(default)]
359    pub city: Option<String>,
360
361    /// The user's associated organization/school.
362    #[serde(default)]
363    pub organization: Option<String>,
364
365    /// The date the user signed up on .
366    pub created_at: NaiveDate,
367
368    /// The user's preferred email language for newsletters.
369    pub language: String,
370
371    /// The user's city.
372    #[serde(default)]
373    pub badge: Option<UserBadge>,
374
375    /// The users subscription plan type
376    #[serde(default)]
377    pub plan_type: Option<PlanType>,
378
379    /// The users subscription status
380    #[serde(default)]
381    pub subscription_status: Option<SubscriptionStatus>,
382
383    /// Whether the user's subscription is in a trial period
384    #[serde(default)]
385    pub is_trial: Option<bool>,
386
387    /// The users subscription expiry/renewal date
388    #[serde(default)]
389    pub current_period_end: Option<DateTime<Utc>>,
390
391    /// The amount due on the users subscription
392    #[serde(default)]
393    pub amount_due_in_cents: Option<AmountInCents>,
394
395    /// Whether the user is an admin of the account
396    #[serde(default)]
397    pub is_admin: Option<bool>,
398
399    /// The school name associated with the users account
400    #[serde(default)]
401    pub school_id: Option<SchoolId>,
402
403    /// The school name associated with the users account
404    #[serde(default)]
405    pub school_name: Option<String>,
406
407    /// The account id for the user
408    #[serde(default)]
409    pub account_id: Option<AccountId>,
410
411    /// Plan tier override
412    #[serde(default)]
413    pub tier_override: Option<PlanTier>,
414
415    /// Login type
416    pub login_type: UserLoginType,
417}
418
419/// A user's profile export representation.
420#[derive(Debug, Serialize, Deserialize, Clone)]
421pub struct UserProfileExport {
422    /// The user's id.
423    pub id: UserId,
424    /// The user's username.
425    pub username: String,
426    /// The user's email address.
427    pub email: String,
428    /// The user's given name (first name)
429    pub given_name: String,
430    /// The user's family name (last name)
431    pub family_name: String,
432    /// ID to the user's profile image in the user image library.
433    pub profile_image: Option<ImageId>,
434    /// Last time user logged in.
435    pub last_login: Option<DateTime<Utc>>,
436    /// Last time user did an action.
437    pub last_action: Option<DateTime<Utc>>,
438    /// The user's preferred application language.
439    pub language_app: String,
440    /// The user's preferred email language.
441    pub language_emails: String,
442    /// The user's preferred language.
443    #[serde(default)]
444    #[serde(serialize_with = "serialize_list")]
445    pub languages_spoken: Vec<String>,
446    /// When the user was created.
447    pub created_at: DateTime<Utc>,
448    /// When the user was last updated.
449    #[serde(default)]
450    pub updated_at: Option<DateTime<Utc>>,
451    /// The organization that the user belongs to.
452    #[serde(default)]
453    pub organization: Option<String>,
454    /// The persona of the user
455    #[serde(default)]
456    #[serde(serialize_with = "serialize_list")]
457    pub persona: Vec<String>,
458    /// The user's taught subjects.
459    #[serde(default)]
460    #[serde(serialize_with = "serialize_list")]
461    pub subjects: Vec<String>,
462    /// The user's age-ranges.
463    #[serde(default)]
464    #[serde(serialize_with = "serialize_list")]
465    pub age_ranges: Vec<String>,
466    /// The user's affiliations.
467    #[serde(default)]
468    #[serde(serialize_with = "serialize_list")]
469    pub affiliations: Vec<String>,
470    /// The user's city
471    #[serde(default)]
472    pub city: Option<String>,
473    /// The user's state/province
474    #[serde(default)]
475    pub state: Option<String>,
476    /// The user's country
477    #[serde(default)]
478    pub country: Option<String>,
479    /// Whether this user has opted in to receive educational resources
480    pub opt_into_edu_resources: bool,
481    /// Number of liked jigs
482    pub liked_jig_count: i64,
483    /// Number of published jigs
484    pub published_jigs_count: i64,
485    // /// Users plan type
486    // pub plan_type: Option<String>
487}
488
489fn serialize_list<S, T>(list: &Vec<T>, serializer: S) -> Result<S::Ok, S::Error>
490where
491    S: Serializer,
492    T: Serialize,
493{
494    list.iter()
495        .map(|v| serde_json::to_string(v).unwrap())
496        .collect::<Vec<String>>()
497        .join(", ")
498        .serialize(serializer)
499}
500
501impl UserProfile {
502    /// Returns the display name for UI purposes
503    pub fn display_name(&self) -> String {
504        format!("{} {}", self.given_name, self.family_name)
505            .trim()
506            .to_string()
507    }
508}
509
510make_path_parts!(VerifyEmailPath => "/v1/user/verify-email");
511
512/// Request for [`VerifyEmail`](crate::api::endpoints::user::VerifyEmail)
513#[derive(Debug, Serialize, Deserialize, Clone)]
514#[serde(rename_all = "camelCase")]
515pub enum VerifyEmailRequest {
516    /// Attempt to verify the email
517    Verify {
518        /// The token to verify.
519        token: String,
520    },
521
522    /// Resend a confirmation link if a verification is in progress
523    Resend {
524        /// The email to send a verification link to.
525        email: String,
526    },
527}
528
529make_path_parts!(VerifyResetEmailPath => "/v1/user/verify-reset-email");
530
531/// Request for [`VerifyUpdateEmail`](crate::api::endpoints::user::VerifyEmail)
532#[derive(Debug, Serialize, Deserialize, Clone)]
533#[serde(untagged)]
534pub enum VerifyResetEmailRequest {
535    /// Attempt to verify the email
536    #[serde(rename_all = "camelCase")]
537    Verify {
538        /// paseto token
539        paseto_token: String,
540
541        /// Forcibly logout of all sessions.
542        force_logout: bool,
543    },
544
545    /// Resend a confirmation link if a verification is in progress
546    #[serde(rename_all = "camelCase")]
547    Resend {
548        /// paseto token
549        paseto_token: String,
550    },
551}
552
553make_path_parts!(CreateProfilePath => "/v1/user/me/profile");
554
555/// Request for [`user::profile::Create`](crate::api::endpoints::user::CreateProfile)
556#[derive(Debug, Serialize, Deserialize)]
557pub struct CreateProfileRequest {
558    /// The user's username.
559    ///
560    /// This must be unique.
561    pub username: String,
562
563    /// Is the user >= 18 yeas old?
564    pub over_18: bool,
565
566    /// The user's given name / "first name".
567    pub given_name: String,
568
569    /// The user's family name / "last name".
570    pub family_name: String,
571
572    /// URL to the user's profile image. The API server uploads and processes the image so that the
573    /// profile image is stored in Cloud Storage in the user image library.
574    #[serde(skip_serializing_if = "Option::is_none")]
575    pub profile_image_url: Option<String>,
576
577    /// The user's preferred application language.
578    pub language_app: String,
579
580    /// The user's preferred email language.
581    pub language_emails: String,
582
583    /// The user's preferred language.
584    pub languages_spoken: Vec<String>,
585
586    /// the timezone that the user uses.
587    pub timezone: chrono_tz::Tz,
588
589    // todo: does this have something to do with emails?
590    /// Does the user want educational resources sent to them?
591    pub opt_into_edu_resources: bool,
592
593    /// The organization that the user belongs to.
594    #[serde(default)]
595    #[serde(skip_serializing_if = "Option::is_none")]
596    pub organization: Option<String>,
597
598    /// The persona of the user
599    #[serde(default)]
600    #[serde(skip_serializing_if = "Vec::is_empty")]
601    pub persona: Vec<String>,
602
603    /// The user's taught subjects.
604    #[serde(default)]
605    #[serde(skip_serializing_if = "Vec::is_empty")]
606    pub subjects: Vec<SubjectId>,
607
608    /// The user's age-ranges.
609    #[serde(default)]
610    #[serde(skip_serializing_if = "Vec::is_empty")]
611    pub age_ranges: Vec<AgeRangeId>,
612
613    /// The user's affiliations.
614    #[serde(default)]
615    #[serde(skip_serializing_if = "Vec::is_empty")]
616    pub affiliations: Vec<AffiliationId>,
617
618    /// The user's location
619    #[serde(default)]
620    #[serde(skip_serializing_if = "Option::is_none")]
621    pub location: Option<serde_json::Value>,
622}
623
624make_path_parts!(GetProfilePath => "/v1/user/me/profile");
625
626make_path_parts!(PatchProfilePath => "/v1/user/me/profile");
627
628/// Request for [`PatchProfile`](crate::api::endpoints::user::PatchProfile)
629#[derive(Debug, Default, Serialize, Deserialize)]
630pub struct PatchProfileRequest {
631    /// The user's username.
632    ///
633    /// This must be unique.
634    #[serde(default)]
635    #[serde(skip_serializing_if = "Option::is_none")]
636    pub username: Option<String>,
637
638    /// The user's given name / "first name".
639    #[serde(default)]
640    #[serde(skip_serializing_if = "Option::is_none")]
641    pub given_name: Option<String>,
642
643    /// The user's family name / "last name".
644    #[serde(default)]
645    #[serde(skip_serializing_if = "Option::is_none")]
646    pub family_name: Option<String>,
647
648    /// ID to the user's profile image in the user image library.
649    #[serde(default)]
650    #[serde(deserialize_with = "super::deserialize_optional_field")]
651    #[serde(skip_serializing_if = "Option::is_none")]
652    pub profile_image: Option<Option<ImageId>>,
653
654    /// The user's bio
655    #[serde(default)]
656    #[serde(skip_serializing_if = "Option::is_none")]
657    pub bio: Option<String>,
658
659    /// the language the user prefers the application to be in.
660    #[serde(skip_serializing_if = "Option::is_none")]
661    pub language_app: Option<String>,
662
663    /// the language the user prefers emails to be in.
664    #[serde(skip_serializing_if = "Option::is_none")]
665    pub language_emails: Option<String>,
666
667    /// the languages the user prefers.
668    #[serde(default)]
669    #[serde(skip_serializing_if = "Option::is_none")]
670    pub languages_spoken: Option<Vec<String>>,
671
672    /// the timezone that the user uses.
673    #[serde(default)]
674    #[serde(skip_serializing_if = "Option::is_none")]
675    pub timezone: Option<chrono_tz::Tz>,
676
677    /// Does the user want educational resources sent to them?
678    #[serde(default)]
679    #[serde(skip_serializing_if = "Option::is_none")]
680    pub opt_into_edu_resources: Option<bool>,
681
682    /// Publicize Users organization
683    #[serde(default)]
684    #[serde(skip_serializing_if = "Option::is_none")]
685    pub organization_public: Option<bool>,
686
687    /// Publicize user persona
688    #[serde(default)]
689    #[serde(skip_serializing_if = "Option::is_none")]
690    pub persona_public: Option<bool>,
691
692    /// Publicize user lanuage
693    #[serde(default)]
694    #[serde(skip_serializing_if = "Option::is_none")]
695    pub languages_spoken_public: Option<bool>,
696
697    /// Publicize user location
698    #[serde(default)]
699    #[serde(skip_serializing_if = "Option::is_none")]
700    pub location_public: Option<bool>,
701
702    /// Publicize user bio
703    #[serde(default)]
704    #[serde(skip_serializing_if = "Option::is_none")]
705    pub bio_public: Option<bool>,
706
707    /// The organization that the user belongs to.
708    ///
709    /// Field is updated if `Some(_)` with the inner contents.
710    #[serde(default)]
711    #[serde(deserialize_with = "super::deserialize_optional_field")]
712    #[serde(skip_serializing_if = "Option::is_none")]
713    pub organization: Option<Option<String>>,
714
715    /// The persona of the user.
716    ///
717    /// Field is updated if `Some(_)` with the inner contents.
718    #[serde(default)]
719    #[serde(skip_serializing_if = "Option::is_none")]
720    pub persona: Option<Vec<String>>,
721
722    /// The user's taught subjects.
723    ///
724    /// If `Some`, replace the existing `SubjectId`s with this.
725    #[serde(default)]
726    #[serde(skip_serializing_if = "Option::is_none")]
727    pub subjects: Option<Vec<SubjectId>>,
728
729    /// The user's age-ranges.
730    ///
731    /// If `Some`, replace the existing `AgeRangeId`s with this.
732    #[serde(default)]
733    #[serde(skip_serializing_if = "Option::is_none")]
734    pub age_ranges: Option<Vec<AgeRangeId>>,
735
736    /// The user's affiliations.
737    ///
738    /// If `Some`, replace the existing `AffiliationId`s with this.
739    #[serde(default)]
740    #[serde(skip_serializing_if = "Option::is_none")]
741    pub affiliations: Option<Vec<AffiliationId>>,
742
743    /// The user's location.
744    /// * If the outer `Option` is `None`, then no update is done,
745    /// * If `Some(None)`, sets the location to `None`,
746    /// * If `Some(Some(_))`, updates the user location to `Some(_)`.
747    #[serde(default)]
748    #[serde(deserialize_with = "super::deserialize_optional_field")]
749    #[serde(skip_serializing_if = "Option::is_none")]
750    pub location: Option<Option<serde_json::Value>>,
751}
752
753make_path_parts!(PatchProfileAdminDataPath => "/v1/user/me/profile/{}/admin-data" => UserId);
754
755/// Request for [`PatchProfileAdminData`](crate::api::endpoints::user::PatchProfileAdminData)
756#[derive(Debug, Default, Serialize, Deserialize)]
757pub struct PatchProfileAdminDataRequest {
758    /// Users badge
759    #[serde(default)]
760    #[serde(deserialize_with = "super::deserialize_optional_field")]
761    #[serde(skip_serializing_if = "Option::is_none")]
762    pub badge: Option<Option<UserBadge>>,
763}
764
765make_path_parts!(CreateUserPath => "/v1/user");
766
767/// Request for [`Create`](crate::api::endpoints::user::Create)
768#[derive(Debug, Serialize, Deserialize)]
769pub struct CreateUserRequest {
770    /// The new user's email
771    pub email: String,
772
773    /// The new user's password
774    pub password: String,
775}
776
777make_path_parts!(ResetPasswordPath => "/v1/user/password-reset");
778
779/// Request for [`ResetPassword`](crate::api::endpoints::user::ResetPassword)
780#[derive(Debug, Serialize, Deserialize)]
781pub struct ResetPasswordRequest {
782    /// The email to request a password reset for
783    pub email: String,
784}
785
786make_path_parts!(ChangePasswordPath => "/v1/user/me/password");
787
788/// Request for [`ChangePassword`](crate::api::endpoints::user::ChangePassword)
789#[derive(Debug, Serialize, Deserialize)]
790#[serde(rename_all = "camelCase")]
791pub enum ChangePasswordRequest {
792    /// Change the email
793    Change {
794        /// The token to verify with
795        token: String,
796
797        /// The new password
798        password: String,
799
800        /// Forcibly logout of all sessions.
801        force_logout: bool,
802    },
803}
804
805make_path_parts!(UserDeletePath => "/v1/user/me");
806
807// Colors
808
809make_path_parts!(UserColorCreatePath => "/v1/user/me/color");
810
811// i32 is color index
812make_path_parts!(UserColorUpdatePath => "/v1/user/me/color/{}" => i32);
813
814/// Request for [`CreateColor`](crate::api::endpoints::user::CreateColor), [`UpdateColor`](crate::api::endpoints::user::UpdateColor)
815#[derive(Debug, Serialize, Deserialize)]
816pub struct UserColorValueRequest {
817    /// the color to add/change to.
818    pub color: rgb::RGBA8,
819}
820
821make_path_parts!(UserColorGetPath => "/v1/user/me/color");
822
823/// Response for [`GetColors`](crate::api::endpoints::user::GetColors)
824#[derive(Debug, Serialize, Deserialize)]
825pub struct UserColorResponse {
826    /// The user's colors.
827    pub colors: Vec<rgb::RGBA8>,
828}
829
830// i32 is color index
831make_path_parts!(UserColorDeletePath => "/v1/user/me/color/{}" => i32);
832
833// Fonts
834
835make_path_parts!(UserFontCreatePath => "/v1/user/me/font");
836
837// i32 is font index
838make_path_parts!(UserFontUpdatePath => "/v1/user/me/font/{}" => i32);
839
840/// Request for [`CreateFont`](crate::api::endpoints::user::CreateFont), [`UpdateFont`](crate::api::endpoints::user::UpdateFont)
841#[derive(Debug, Serialize, Deserialize)]
842pub struct UserFontNameRequest {
843    /// Name of the font to add/change.
844    pub name: String,
845}
846
847make_path_parts!(UserFontGetPath => "/v1/user/me/font");
848
849/// Response for [`GetFonts`](crate::api::endpoints::user::GetFonts)
850#[derive(Debug, Serialize, Deserialize)]
851pub struct UserFontResponse {
852    /// Names of the user's fonts.
853    pub names: Vec<String>,
854}
855
856// i32 is font index
857make_path_parts!(UserFontDeletePath => "/v1/user/me/font/{}" => i32);
858
859//
860// Browse users
861//
862// Authorization:
863//  - Admin
864
865/// Query for [`Browse`](crate::api::endpoints::user::Browse).
866#[derive(Debug, Serialize, Deserialize, Clone, Default)]
867#[serde(rename_all = "camelCase")]
868pub struct UserBrowseQuery {
869    /// filter User by Id.
870    #[serde(default)]
871    #[serde(skip_serializing_if = "Option::is_none")]
872    pub user_id: Option<UserId>,
873
874    /// The page number of the Users to get.
875    #[serde(default)]
876    #[serde(skip_serializing_if = "Option::is_none")]
877    pub page: Option<u32>,
878
879    /// The hits per page to be returned
880    #[serde(default)]
881    #[serde(skip_serializing_if = "Option::is_none")]
882    pub page_limit: Option<u32>,
883
884    /// Optional filter for user badges
885    #[serde(default)]
886    #[serde(deserialize_with = "super::from_csv")]
887    #[serde(skip_serializing_if = "Vec::is_empty")]
888    pub badge: Vec<UserBadge>,
889}
890
891/// Response for [`Browse`](crate::api::endpoints::user::Browse).
892#[derive(Serialize, Deserialize, Clone, Debug)]
893#[serde(rename_all = "camelCase")]
894pub struct UserBrowseResponse {
895    /// the users returned.
896    pub users: Vec<UserResponse>,
897
898    /// The number of pages found.
899    pub pages: u32,
900
901    /// The total number of users found
902    pub total_user_count: u64,
903}
904
905make_path_parts!(UserBrowsePath => "/v1/user/browse");
906
907//
908// Search users
909//
910// Authorization:
911//  - Admin
912
913/// Query for [`Search`](crate::api::endpoints::user::Search).
914#[derive(Debug, Serialize, Deserialize, Clone, Default)]
915#[serde(rename_all = "camelCase")]
916pub struct UserSearchQuery {
917    /// The query string.
918    #[serde(default)]
919    #[serde(skip_serializing_if = "String::is_empty")]
920    pub q: String,
921
922    /// The query string.
923    #[serde(default)]
924    #[serde(skip_serializing_if = "Option::is_none")]
925    pub user_id: Option<UserId>,
926
927    /// The page number of the Users to get.
928    #[serde(default)]
929    #[serde(skip_serializing_if = "Option::is_none")]
930    pub page: Option<u32>,
931
932    /// The hits per page to be returned
933    #[serde(default)]
934    #[serde(skip_serializing_if = "Option::is_none")]
935    pub page_limit: Option<u32>,
936}
937
938/// Response for [`Search`](crate::api::endpoints::user::Search).
939#[derive(Serialize, Deserialize, Clone, Debug)]
940#[serde(rename_all = "camelCase")]
941pub struct UserSearchResponse {
942    /// the users returned.
943    pub users: Vec<UserResponse>,
944
945    /// The number of pages found.
946    pub pages: u32,
947
948    /// The total number of users found
949    pub total_user_count: u64,
950}
951
952make_path_parts!(UserSearchPath => "/v1/user");