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}