revelation_user/dto/
create.rs

1// SPDX-FileCopyrightText: 2025 Revelation Team
2// SPDX-License-Identifier: MIT
3
4//! User creation DTOs.
5//!
6//! This module provides request types for creating new users.
7//!
8//! # Overview
9//!
10//! [`CreateUserRequest`] is used when creating a user from
11//! an authentication event (Telegram login, email signup, etc.).
12//!
13//! # Examples
14//!
15//! ```rust
16//! use revelation_user::CreateUserRequest;
17//!
18//! // From Telegram authentication
19//! let req = CreateUserRequest::telegram(123456789);
20//! assert!(req.telegram_id.is_some());
21//!
22//! // From email authentication
23//! let req = CreateUserRequest::email("user@example.com");
24//! assert!(req.email.is_some());
25//! ```
26
27use serde::{Deserialize, Serialize};
28use uuid::Uuid;
29use validator::Validate;
30
31/// Request to create a new user.
32///
33/// Contains the minimal data required to create a user record.
34/// Additional profile data can be added later via [`UpdateProfileRequest`].
35///
36/// # Fields
37///
38/// | Field | Type | Description |
39/// |-------|------|-------------|
40/// | `id` | `Uuid` | Pre-generated user ID (auto-generated if not provided) |
41/// | `telegram_id` | `Option<i64>` | Telegram user ID |
42/// | `email` | `Option<String>` | Email address |
43/// | `phone` | `Option<String>` | Phone number |
44///
45/// # Validation
46///
47/// - `telegram_id`: Must be positive (≥ 1)
48/// - `email`: Must be valid email format
49///
50/// # Examples
51///
52/// ```rust
53/// use revelation_user::CreateUserRequest;
54/// use validator::Validate;
55///
56/// // Valid request
57/// let req = CreateUserRequest::telegram(123456789);
58/// assert!(req.validate().is_ok());
59///
60/// // Manual construction
61/// let req = CreateUserRequest {
62///     id:          uuid::Uuid::now_v7(),
63///     telegram_id: Some(123),
64///     email:       None,
65///     phone:       None
66/// };
67/// ```
68///
69/// [`UpdateProfileRequest`]: crate::UpdateProfileRequest
70#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
71#[cfg_attr(feature = "api", derive(utoipa::ToSchema))]
72pub struct CreateUserRequest {
73    /// Pre-generated user ID.
74    ///
75    /// Defaults to a new UUIDv7 if not provided during deserialization.
76    #[serde(default = "Uuid::now_v7")]
77    pub id: Uuid,
78
79    /// Telegram user ID from bot callback.
80    ///
81    /// Must be a positive integer.
82    #[validate(range(min = 1))]
83    pub telegram_id: Option<i64>,
84
85    /// Email address from email authentication.
86    ///
87    /// Must be a valid email format.
88    #[validate(email)]
89    pub email: Option<String>,
90
91    /// Phone number from phone authentication.
92    ///
93    /// Should be in E.164 format (e.g., `+14155551234`).
94    pub phone: Option<String>
95}
96
97impl CreateUserRequest {
98    /// Create request for Telegram authentication.
99    ///
100    /// # Arguments
101    ///
102    /// * `telegram_id` - The Telegram user ID from login callback
103    ///
104    /// # Examples
105    ///
106    /// ```rust
107    /// use revelation_user::CreateUserRequest;
108    ///
109    /// let req = CreateUserRequest::telegram(123456789);
110    ///
111    /// assert_eq!(req.telegram_id, Some(123456789));
112    /// assert!(req.email.is_none());
113    /// assert!(req.phone.is_none());
114    /// ```
115    #[must_use]
116    pub fn telegram(telegram_id: i64) -> Self {
117        Self {
118            id:          Uuid::now_v7(),
119            telegram_id: Some(telegram_id),
120            email:       None,
121            phone:       None
122        }
123    }
124
125    /// Create request for email authentication.
126    ///
127    /// # Arguments
128    ///
129    /// * `email` - The verified email address
130    ///
131    /// # Examples
132    ///
133    /// ```rust
134    /// use revelation_user::CreateUserRequest;
135    ///
136    /// let req = CreateUserRequest::email("user@example.com");
137    ///
138    /// assert_eq!(req.email.as_deref(), Some("user@example.com"));
139    /// assert!(req.telegram_id.is_none());
140    /// ```
141    #[must_use]
142    pub fn email(email: impl Into<String>) -> Self {
143        Self {
144            id:          Uuid::now_v7(),
145            telegram_id: None,
146            email:       Some(email.into()),
147            phone:       None
148        }
149    }
150
151    /// Create request for phone authentication.
152    ///
153    /// # Arguments
154    ///
155    /// * `phone` - The phone number in E.164 format
156    ///
157    /// # Examples
158    ///
159    /// ```rust
160    /// use revelation_user::CreateUserRequest;
161    ///
162    /// let req = CreateUserRequest::phone("+14155551234");
163    ///
164    /// assert_eq!(req.phone.as_deref(), Some("+14155551234"));
165    /// ```
166    #[must_use]
167    pub fn phone(phone: impl Into<String>) -> Self {
168        Self {
169            id:          Uuid::now_v7(),
170            telegram_id: None,
171            email:       None,
172            phone:       Some(phone.into())
173        }
174    }
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn telegram_constructor() {
183        let req = CreateUserRequest::telegram(123);
184        assert_eq!(req.telegram_id, Some(123));
185        assert!(req.email.is_none());
186    }
187
188    #[test]
189    fn email_constructor() {
190        let req = CreateUserRequest::email("test@test.com");
191        assert_eq!(req.email.as_deref(), Some("test@test.com"));
192    }
193
194    #[test]
195    fn validates_telegram_id_positive() {
196        let req = CreateUserRequest {
197            id:          Uuid::nil(),
198            telegram_id: Some(0),
199            email:       None,
200            phone:       None
201        };
202        assert!(req.validate().is_err());
203
204        let req = CreateUserRequest::telegram(1);
205        assert!(req.validate().is_ok());
206    }
207
208    #[test]
209    fn validates_email_format() {
210        let req = CreateUserRequest {
211            id:          Uuid::nil(),
212            telegram_id: None,
213            email:       Some("invalid".into()),
214            phone:       None
215        };
216        assert!(req.validate().is_err());
217
218        let req = CreateUserRequest::email("valid@example.com");
219        assert!(req.validate().is_ok());
220    }
221
222    #[test]
223    fn phone_constructor() {
224        let req = CreateUserRequest::phone("+14155551234");
225        assert_eq!(req.phone.as_deref(), Some("+14155551234"));
226        assert!(req.telegram_id.is_none());
227        assert!(req.email.is_none());
228    }
229}