revelation_user/entity/
user.rs

1// SPDX-FileCopyrightText: 2025 Revelation Team
2// SPDX-License-Identifier: MIT
3
4//! User entity - the core aggregate of the user domain.
5//!
6//! This module defines [`RUser`], the primary user representation in the
7//! Revelation ecosystem. Uses `entity-derive` for DTO and repository
8//! generation.
9//!
10//! # Generated Types
11//!
12//! The `Entity` derive macro generates:
13//! - [`CreateRUserRequest`] - DTO for user creation
14//! - [`UpdateRUserRequest`] - DTO for profile updates
15//! - [`RUserResponse`] - DTO for API responses
16//! - [`RUserRow`] - Database row mapping
17//! - [`InsertableRUser`] - For INSERT operations
18//! - [`RUserRepository`] - Async CRUD trait
19//!
20//! # Examples
21//!
22//! ```rust
23//! use revelation_user::RUser;
24//!
25//! // From Telegram authentication
26//! let user = RUser::from_telegram(123456789);
27//!
28//! // From email authentication
29//! let user = RUser::from_email("user@example.com");
30//!
31//! // Empty user (for OAuth flows)
32//! let user = RUser::empty();
33//! ```
34
35use chrono::{DateTime, NaiveDate, Utc};
36use entity_derive::Entity;
37use serde::{Deserialize, Serialize};
38use uuid::Uuid;
39
40use crate::Gender;
41
42/// Core user entity for the Revelation ecosystem.
43///
44/// `RUser` represents a registered user with support for multiple
45/// authentication methods and optional profile data.
46///
47/// # Fields
48///
49/// | Field | Type | Create | Update | Response |
50/// |-------|------|--------|--------|----------|
51/// | `id` | `Uuid` | — | — | Yes |
52/// | `name` | `Option<String>` | — | Yes | Yes |
53/// | `gender` | `Option<Gender>` | — | Yes | Yes |
54/// | `birth_date` | `Option<NaiveDate>` | — | Yes | Yes |
55/// | `confession_id` | `Option<Uuid>` | — | Yes | Yes |
56/// | `email` | `Option<String>` | Yes | — | Yes |
57/// | `phone` | `Option<String>` | Yes | — | Yes |
58/// | `telegram_id` | `Option<i64>` | Yes | — | Yes |
59/// | `created_at` | `DateTime<Utc>` | — | — | Yes |
60/// | `updated_at` | `DateTime<Utc>` | — | — | Yes |
61#[derive(Debug, Clone, Serialize, Deserialize, Entity)]
62#[entity(table = "users", schema = "public", sql = "none")]
63pub struct RUser {
64    /// Unique user identifier (UUIDv7).
65    #[id]
66    pub id: Uuid,
67
68    /// Display name (2-100 chars).
69    #[field(update, response)]
70    pub name: Option<String>,
71
72    /// User's gender.
73    #[field(update, response)]
74    pub gender: Option<Gender>,
75
76    /// Date of birth.
77    #[field(update, response)]
78    pub birth_date: Option<NaiveDate>,
79
80    /// Reference to confession/denomination.
81    #[field(update, response)]
82    pub confession_id: Option<Uuid>,
83
84    /// Verified email address.
85    #[field(create, response)]
86    pub email: Option<String>,
87
88    /// Phone number in E.164 format.
89    #[field(create, response)]
90    pub phone: Option<String>,
91
92    /// Telegram user ID.
93    #[field(create, response)]
94    pub telegram_id: Option<i64>,
95
96    /// Creation timestamp.
97    #[field(response)]
98    #[auto]
99    pub created_at: DateTime<Utc>,
100
101    /// Last update timestamp.
102    #[field(response)]
103    #[auto]
104    pub updated_at: DateTime<Utc>
105}
106
107impl RUser {
108    /// Create user from Telegram authentication.
109    ///
110    /// # Examples
111    ///
112    /// ```rust
113    /// use revelation_user::RUser;
114    ///
115    /// let user = RUser::from_telegram(123456789);
116    /// assert_eq!(user.telegram_id, Some(123456789));
117    /// ```
118    #[must_use]
119    pub fn from_telegram(telegram_id: i64) -> Self {
120        Self {
121            id:            Uuid::now_v7(),
122            name:          None,
123            gender:        None,
124            birth_date:    None,
125            confession_id: None,
126            email:         None,
127            phone:         None,
128            telegram_id:   Some(telegram_id),
129            created_at:    Utc::now(),
130            updated_at:    Utc::now()
131        }
132    }
133
134    /// Create user from email authentication.
135    ///
136    /// # Examples
137    ///
138    /// ```rust
139    /// use revelation_user::RUser;
140    ///
141    /// let user = RUser::from_email("user@example.com");
142    /// assert_eq!(user.email.as_deref(), Some("user@example.com"));
143    /// ```
144    #[must_use]
145    pub fn from_email(email: impl Into<String>) -> Self {
146        Self {
147            id:            Uuid::now_v7(),
148            name:          None,
149            gender:        None,
150            birth_date:    None,
151            confession_id: None,
152            email:         Some(email.into()),
153            phone:         None,
154            telegram_id:   None,
155            created_at:    Utc::now(),
156            updated_at:    Utc::now()
157        }
158    }
159
160    /// Create user from phone authentication.
161    ///
162    /// # Examples
163    ///
164    /// ```rust
165    /// use revelation_user::RUser;
166    ///
167    /// let user = RUser::from_phone("+79991234567");
168    /// assert!(user.phone.is_some());
169    /// ```
170    #[must_use]
171    pub fn from_phone(phone: impl Into<String>) -> Self {
172        Self {
173            id:            Uuid::now_v7(),
174            name:          None,
175            gender:        None,
176            birth_date:    None,
177            confession_id: None,
178            email:         None,
179            phone:         Some(phone.into()),
180            telegram_id:   None,
181            created_at:    Utc::now(),
182            updated_at:    Utc::now()
183        }
184    }
185
186    /// Create empty user with only ID.
187    ///
188    /// # Examples
189    ///
190    /// ```rust
191    /// use revelation_user::RUser;
192    ///
193    /// let user = RUser::empty();
194    /// assert!(user.email.is_none());
195    /// ```
196    #[must_use]
197    pub fn empty() -> Self {
198        Self {
199            id:            Uuid::now_v7(),
200            name:          None,
201            gender:        None,
202            birth_date:    None,
203            confession_id: None,
204            email:         None,
205            phone:         None,
206            telegram_id:   None,
207            created_at:    Utc::now(),
208            updated_at:    Utc::now()
209        }
210    }
211
212    /// Create user with a specific ID.
213    ///
214    /// # Examples
215    ///
216    /// ```rust
217    /// use revelation_user::RUser;
218    /// use uuid::Uuid;
219    ///
220    /// let id = Uuid::now_v7();
221    /// let user = RUser::with_id(id);
222    /// assert_eq!(user.id, id);
223    /// ```
224    #[must_use]
225    pub fn with_id(id: Uuid) -> Self {
226        Self {
227            id,
228            name: None,
229            gender: None,
230            birth_date: None,
231            confession_id: None,
232            email: None,
233            phone: None,
234            telegram_id: None,
235            created_at: Utc::now(),
236            updated_at: Utc::now()
237        }
238    }
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn from_telegram_sets_telegram_id() {
247        let user = RUser::from_telegram(123);
248        assert_eq!(user.telegram_id, Some(123));
249        assert!(user.email.is_none());
250    }
251
252    #[test]
253    fn from_email_sets_email() {
254        let user = RUser::from_email("test@example.com");
255        assert_eq!(user.email.as_deref(), Some("test@example.com"));
256        assert!(user.telegram_id.is_none());
257    }
258
259    #[test]
260    fn from_phone_sets_phone() {
261        let user = RUser::from_phone("+14155551234");
262        assert_eq!(user.phone.as_deref(), Some("+14155551234"));
263    }
264
265    #[test]
266    fn empty_has_only_id() {
267        let user = RUser::empty();
268        assert!(user.name.is_none());
269        assert!(user.email.is_none());
270        assert!(user.telegram_id.is_none());
271    }
272
273    #[test]
274    fn with_id_sets_specific_id() {
275        let id = Uuid::nil();
276        let user = RUser::with_id(id);
277        assert_eq!(user.id, id);
278    }
279
280    #[test]
281    fn serializes_to_json() {
282        let user = RUser::from_telegram(123);
283        let json = serde_json::to_string(&user).unwrap();
284        assert!(json.contains("\"telegram_id\":123"));
285    }
286}