omni_orchestrator/schemas/v1/db/queries/user.rs
1use crate::models::user::{UserMeta, UserPii, UserSession, User};
2use anyhow::Context;
3use sqlx::{MySql, Pool};
4
5/// Retrieves all users in the system, ordered by creation time.
6///
7/// This function fetches all user records from the database, with
8/// the most recently created users first. It provides a complete view
9/// of all users registered in the system.
10///
11/// # Arguments
12///
13/// * `pool` - Database connection pool for executing the query
14///
15/// # Returns
16///
17/// * `Ok(Vec<User>)` - Successfully retrieved list of users
18/// * `Err(anyhow::Error)` - Failed to fetch users
19///
20/// # Use Cases
21///
22/// Common use cases include:
23/// - Administrative dashboards showing all system users
24/// - User management interfaces
25/// - User activity reports and analytics
26pub async fn list_users(
27 pool: &Pool<MySql>,
28 page: i64,
29 per_page: i64,
30) -> anyhow::Result<Vec<User>> {
31 let offset = (page * per_page) as i64;
32 let limit = per_page as i64;
33
34 let users = sqlx::query_as::<_, User>(
35 "SELECT * FROM users WHERE deleted_at IS NULL ORDER BY created_at DESC LIMIT ? OFFSET ?"
36 )
37 .bind(limit)
38 .bind(offset)
39 .fetch_all(pool)
40 .await
41 .context("Failed to fetch users")?;
42
43 Ok(users)
44}
45
46/// Counts the total number of users in the system.
47pub async fn count_users(pool: &Pool<MySql>) -> anyhow::Result<i64> {
48 let count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM users WHERE deleted_at IS NULL")
49 .fetch_one(pool)
50 .await
51 .context("Failed to count users")?;
52
53 Ok(count)
54}
55
56/// Retrieves a specific user by their unique identifier.
57///
58/// This function fetches detailed information about a single user record.
59/// It's typically used when specific user details are needed, such as
60/// for displaying user profiles or performing user-specific operations.
61///
62/// # Arguments
63///
64/// * `pool` - Database connection pool for executing the query
65/// * `id` - Unique identifier of the user to retrieve
66///
67/// # Returns
68///
69/// * `Ok(User)` - Successfully retrieved user information
70/// * `Err(anyhow::Error)` - Failed to fetch user (including if not found)
71///
72/// # Error Handling
73///
74/// Returns an error if no user with the given ID exists or if a database
75/// error occurs during the query execution.
76pub async fn get_user_by_id(pool: &Pool<MySql>, id: i64) -> anyhow::Result<User> {
77 let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = ? AND deleted_at IS NULL")
78 .bind(id)
79 .fetch_one(pool)
80 .await
81 .context("Failed to fetch user")?;
82
83 Ok(user)
84}
85
86/// Retrieves a user by their email address.
87///
88/// This function fetches a user record based on their email address, which
89/// serves as a unique identifier in many authentication and user management
90/// scenarios. It's commonly used during login processes and for email verification.
91///
92/// # Arguments
93///
94/// * `pool` - Database connection pool for executing the query
95/// * `email` - Email address of the user to retrieve
96///
97/// # Returns
98///
99/// * `Ok(User)` - Successfully retrieved user information
100/// * `Err(anyhow::Error)` - Failed to fetch user (including if not found)
101///
102/// # Error Handling
103///
104/// Returns an error if no user with the given email exists or if a database
105/// error occurs during the query execution.
106pub async fn get_user_by_email(pool: &Pool<MySql>, email: &str) -> anyhow::Result<User> {
107 let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE email = ? AND deleted_at IS NULL")
108 .bind(email)
109 .fetch_one(pool)
110 .await
111 .context("Failed to fetch user by email")?;
112
113 Ok(user)
114}
115
116/// Creates a new user in the system.
117///
118/// This function inserts a new user record with the provided information.
119/// New users are created with the 'pending' status by default.
120///
121/// # Arguments
122///
123/// * `pool` - Database connection pool for executing the query
124/// * `email` - User's email address (must be unique)
125/// * `password` - User's password (should be pre-hashed for security)
126/// * `salt` - Cryptographic salt used in the password hashing process
127///
128/// # Returns
129///
130/// * `Ok(User)` - Successfully created user record
131/// * `Err(anyhow::Error)` - Failed to create user record
132///
133/// # Security Considerations
134///
135/// This function expects the password to be pre-hashed before being passed in.
136/// It does not perform any password hashing itself, as this is typically handled
137/// by a higher-level security service. Never pass plain text passwords to this function.
138///
139/// # Transaction Handling
140///
141/// This function uses a database transaction to ensure atomicity of the operation.
142/// If any part of the operation fails, the entire operation is rolled back.
143pub async fn create_user(
144 pool: &Pool<MySql>,
145 email: &str,
146 password: &str, // Should be pre-hashed
147 salt: &str,
148) -> anyhow::Result<User> {
149 let mut tx = pool.begin().await?;
150
151 // Insert into the users table first
152 let query = r#"INSERT INTO users (
153 email, password, salt, email_verified, active, status,
154 two_factor_enabled, two_factor_verified, login_attempts
155 ) VALUES (?, ?, ?, 0, 1, 'pending', 0, 0, 0)"#;
156
157 let user_id = sqlx::query(query)
158 .bind(email)
159 .bind(password)
160 .bind(salt)
161 .execute(&mut *tx)
162 .await
163 .context("Failed to create user")?
164 .last_insert_id();
165
166 println!("User created with ID: {}", user_id);
167
168 // Get the newly created user
169 let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = ?")
170 .bind(user_id)
171 .fetch_one(&mut *tx)
172 .await
173 .context("Failed to fetch created user")?;
174
175 // Create default user_meta record
176 sqlx::query(r#"INSERT INTO user_meta (user_id, timezone, language, theme, onboarding_completed)
177 VALUES (?, 'UTC', 'en', 'light', 0)"#)
178 .bind(user_id)
179 .execute(&mut *tx)
180 .await
181 .context("Failed to create user metadata")?;
182
183 // Create empty user_pii record
184 sqlx::query(r#"INSERT INTO user_pii (user_id, identity_verified)
185 VALUES (?, 0)"#)
186 .bind(user_id)
187 .execute(&mut *tx)
188 .await
189 .context("Failed to create user PII record")?;
190
191 tx.commit().await?;
192 Ok(user)
193}
194
195/// Updates an existing user's information.
196///
197/// This function modifies a user record with the provided information.
198/// It supports partial updates, allowing you to update only the fields that need changing.
199///
200/// # Arguments
201///
202/// * `pool` - Database connection pool for executing the query
203/// * `id` - Unique identifier of the user to update
204/// * `email` - Optional new email address for the user
205/// * `active` - Optional new active status for the user
206/// * `status` - Optional new status (active, deactivated, suspended, pending)
207///
208/// # Returns
209///
210/// * `Ok(User)` - Successfully updated user record
211/// * `Err(anyhow::Error)` - Failed to update user
212///
213/// # Dynamic Query Building
214///
215/// This function dynamically builds an SQL query based on which parameters are provided.
216/// Only the fields specified with Some values will be updated, while None values are ignored.
217///
218/// # Transaction Handling
219///
220/// This function uses a database transaction to ensure atomicity of the operation.
221/// If any part of the operation fails, the entire operation is rolled back.
222///
223/// # Note
224///
225/// This function does not support updating passwords. Password updates should be
226/// handled by a dedicated function with appropriate security measures.
227pub async fn update_user(
228 pool: &Pool<MySql>,
229 id: i64,
230 email: Option<&str>,
231 active: Option<bool>,
232 status: Option<&str>,
233) -> anyhow::Result<User> {
234 let mut tx = pool.begin().await?;
235
236 let mut query = String::from("UPDATE users SET updated_at = CURRENT_TIMESTAMP");
237
238 if let Some(email) = email {
239 query.push_str(", email = ?");
240 }
241 if let Some(active) = active {
242 query.push_str(", active = ?");
243 }
244 if let Some(status) = status {
245 query.push_str(", status = ?");
246 }
247
248 query.push_str(" WHERE id = ?");
249
250 let mut db_query = sqlx::query(&query);
251
252 if let Some(email) = email {
253 db_query = db_query.bind(email);
254 }
255 if let Some(active) = active {
256 db_query = db_query.bind(active);
257 }
258 if let Some(status) = status {
259 db_query = db_query.bind(status);
260 }
261
262 db_query = db_query.bind(id);
263
264 db_query
265 .execute(&mut *tx)
266 .await
267 .context("Failed to update user")?;
268
269 let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = ?")
270 .bind(id)
271 .fetch_one(&mut *tx)
272 .await
273 .context("Failed to fetch updated user")?;
274
275 tx.commit().await?;
276 Ok(user)
277}
278
279/// Updates a user's personal information.
280///
281/// This function updates the user's personal information in the user_pii table.
282///
283/// # Arguments
284///
285/// * `pool` - Database connection pool for executing the query
286/// * `user_id` - Unique identifier of the user
287/// * `first_name` - Optional new first name
288/// * `last_name` - Optional new last name
289/// * `full_name` - Optional new full name (can be generated if first and last are provided)
290///
291/// # Returns
292///
293/// * `Ok(())` - Successfully updated user PII
294/// * `Err(anyhow::Error)` - Failed to update user PII
295pub async fn update_user_pii(
296 pool: &Pool<MySql>,
297 user_id: i64,
298 first_name: Option<&str>,
299 last_name: Option<&str>,
300 full_name: Option<&str>,
301) -> anyhow::Result<()> {
302 let mut tx = pool.begin().await?;
303
304 let mut query = String::from("UPDATE user_pii SET updated_at = CURRENT_TIMESTAMP");
305
306 if let Some(first_name) = first_name {
307 query.push_str(", first_name = ?");
308 }
309 if let Some(last_name) = last_name {
310 query.push_str(", last_name = ?");
311 }
312 if let Some(full_name) = full_name {
313 query.push_str(", full_name = ?");
314 } else if first_name.is_some() && last_name.is_some() {
315 query.push_str(", full_name = CONCAT(first_name, ' ', last_name)");
316 }
317
318 query.push_str(" WHERE user_id = ?");
319
320 let mut db_query = sqlx::query(&query);
321
322 if let Some(first_name) = first_name {
323 db_query = db_query.bind(first_name);
324 }
325 if let Some(last_name) = last_name {
326 db_query = db_query.bind(last_name);
327 }
328 if let Some(full_name) = full_name {
329 db_query = db_query.bind(full_name);
330 }
331
332 db_query = db_query.bind(user_id);
333
334 db_query
335 .execute(&mut *tx)
336 .await
337 .context("Failed to update user PII")?;
338
339 tx.commit().await?;
340 Ok(())
341}
342
343/// Updates a user's preferences and metadata.
344///
345/// This function updates the user's preferences in the user_meta table.
346///
347/// # Arguments
348///
349/// * `pool` - Database connection pool for executing the query
350/// * `user_id` - Unique identifier of the user
351/// * `timezone` - Optional new timezone preference
352/// * `language` - Optional new language preference
353/// * `theme` - Optional new theme preference
354/// * `onboarding_completed` - Optional flag indicating if onboarding is completed
355///
356/// # Returns
357///
358/// * `Ok(())` - Successfully updated user metadata
359/// * `Err(anyhow::Error)` - Failed to update user metadata
360pub async fn update_user_meta(
361 pool: &Pool<MySql>,
362 user_id: i64,
363 timezone: Option<&str>,
364 language: Option<&str>,
365 theme: Option<&str>,
366 onboarding_completed: Option<bool>,
367) -> anyhow::Result<()> {
368 let mut tx = pool.begin().await?;
369
370 let mut query = String::from("UPDATE user_meta SET updated_at = CURRENT_TIMESTAMP");
371
372 if let Some(timezone) = timezone {
373 query.push_str(", timezone = ?");
374 }
375 if let Some(language) = language {
376 query.push_str(", language = ?");
377 }
378 if let Some(theme) = theme {
379 query.push_str(", theme = ?");
380 }
381 if let Some(onboarding_completed) = onboarding_completed {
382 query.push_str(", onboarding_completed = ?");
383 }
384
385 query.push_str(" WHERE user_id = ?");
386
387 let mut db_query = sqlx::query(&query);
388
389 if let Some(timezone) = timezone {
390 db_query = db_query.bind(timezone);
391 }
392 if let Some(language) = language {
393 db_query = db_query.bind(language);
394 }
395 if let Some(theme) = theme {
396 db_query = db_query.bind(theme);
397 }
398 if let Some(onboarding_completed) = onboarding_completed {
399 db_query = db_query.bind(onboarding_completed);
400 }
401
402 db_query = db_query.bind(user_id);
403
404 db_query
405 .execute(&mut *tx)
406 .await
407 .context("Failed to update user metadata")?;
408
409 tx.commit().await?;
410 Ok(())
411}
412
413/// Updates a user's security settings.
414///
415/// This function updates security-related fields in the users table.
416///
417/// # Arguments
418///
419/// * `pool` - Database connection pool for executing the query
420/// * `id` - Unique identifier of the user
421/// * `password` - Optional new password (should be pre-hashed)
422/// * `salt` - Optional new salt (should be provided if password is)
423/// * `two_factor_enabled` - Optional flag to enable/disable two-factor authentication
424/// * `two_factor_verified` - Optional flag indicating 2FA verification status
425///
426/// # Returns
427///
428/// * `Ok(User)` - Successfully updated user security settings
429/// * `Err(anyhow::Error)` - Failed to update user security settings
430pub async fn update_user_security(
431 pool: &Pool<MySql>,
432 id: i64,
433 password: Option<&str>,
434 salt: Option<&str>,
435 two_factor_enabled: Option<bool>,
436 two_factor_verified: Option<bool>,
437) -> anyhow::Result<User> {
438 let mut tx = pool.begin().await?;
439
440 let mut query = String::from("UPDATE users SET updated_at = CURRENT_TIMESTAMP");
441
442 if let Some(password) = password {
443 query.push_str(", password = ?, password_changed_at = CURRENT_TIMESTAMP");
444 }
445 if let Some(salt) = salt {
446 query.push_str(", salt = ?");
447 }
448 if let Some(two_factor_enabled) = two_factor_enabled {
449 query.push_str(", two_factor_enabled = ?");
450 }
451 if let Some(two_factor_verified) = two_factor_verified {
452 query.push_str(", two_factor_verified = ?");
453 }
454
455 query.push_str(" WHERE id = ?");
456
457 let mut db_query = sqlx::query(&query);
458
459 if let Some(password) = password {
460 db_query = db_query.bind(password);
461 }
462 if let Some(salt) = salt {
463 db_query = db_query.bind(salt);
464 }
465 if let Some(two_factor_enabled) = two_factor_enabled {
466 db_query = db_query.bind(two_factor_enabled);
467 }
468 if let Some(two_factor_verified) = two_factor_verified {
469 db_query = db_query.bind(two_factor_verified);
470 }
471
472 db_query = db_query.bind(id);
473
474 db_query
475 .execute(&mut *tx)
476 .await
477 .context("Failed to update user security settings")?;
478
479 let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = ?")
480 .bind(id)
481 .fetch_one(&mut *tx)
482 .await
483 .context("Failed to fetch updated user")?;
484
485 tx.commit().await?;
486 Ok(user)
487}
488
489/// Soft deletes a user from the system.
490///
491/// This function marks a user as deleted by setting the deleted_at timestamp,
492/// but does not actually remove the record from the database.
493///
494/// # Arguments
495///
496/// * `pool` - Database connection pool for executing the query
497/// * `id` - Unique identifier of the user to delete
498///
499/// # Returns
500///
501/// * `Ok(())` - Successfully marked the user as deleted
502/// * `Err(anyhow::Error)` - Failed to mark the user as deleted
503///
504/// # Notes
505///
506/// This operation is reversible by clearing the deleted_at field. The function
507/// preserves user data while making it inactive in the system.
508pub async fn soft_delete_user(pool: &Pool<MySql>, id: i64) -> anyhow::Result<()> {
509 let mut tx = pool.begin().await?;
510
511 sqlx::query("UPDATE users SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?")
512 .bind(id)
513 .execute(&mut *tx)
514 .await
515 .context("Failed to soft-delete user")?;
516
517 tx.commit().await?;
518 Ok(())
519}
520
521/// Hard deletes a user from the system.
522///
523/// This function permanently removes a user record from the database.
524/// It should be used with caution, as it typically has significant implications
525/// for data integrity and user experience.
526///
527/// # Arguments
528///
529/// * `pool` - Database connection pool for executing the query
530/// * `id` - Unique identifier of the user to delete
531///
532/// # Returns
533///
534/// * `Ok(())` - Successfully deleted the user
535/// * `Err(anyhow::Error)` - Failed to delete the user
536///
537/// # Warning
538///
539/// This operation is irreversible. Consider the implications before deleting
540/// users, especially those with existing data or relationships in the system.
541/// For many applications, it may be preferable to deactivate users by setting
542/// their 'active' flag to false or using soft_delete_user rather than deleting them completely.
543///
544/// # Note
545///
546/// Due to CASCADE DELETE constraints in the database, this will automatically delete
547/// all related records in user_meta, user_pii, user_sessions, and other tables with
548/// foreign key relationships to the user.
549pub async fn delete_user(pool: &Pool<MySql>, id: i64) -> anyhow::Result<()> {
550 let mut tx = pool.begin().await?;
551
552 sqlx::query("DELETE FROM users WHERE id = ?")
553 .bind(id)
554 .execute(&mut *tx)
555 .await
556 .context("Failed to delete user")?;
557
558 tx.commit().await?;
559 Ok(())
560}
561
562/// Records a user login attempt.
563///
564/// This function updates login-related fields like last_login_at and login_attempts.
565/// It's used during authentication to track login activity and manage security features
566/// like account lockouts after too many failed attempts.
567///
568/// # Arguments
569///
570/// * `pool` - Database connection pool for executing the query
571/// * `id` - User ID
572/// * `successful` - Whether the login attempt was successful
573///
574/// # Returns
575///
576/// * `Ok(User)` - Updated user record
577/// * `Err(anyhow::Error)` - Failed to update login information
578pub async fn record_login_attempt(
579 pool: &Pool<MySql>,
580 id: i64,
581 successful: bool
582) -> anyhow::Result<User> {
583 let mut tx = pool.begin().await?;
584
585 if successful {
586 // Reset login attempts and update last login time
587 sqlx::query(
588 "UPDATE users SET last_login_at = CURRENT_TIMESTAMP, login_attempts = 0,
589 locked_until = NULL WHERE id = ?"
590 )
591 .bind(id)
592 .execute(&mut *tx)
593 .await
594 .context("Failed to record successful login")?;
595 } else {
596 // Increment login attempts and possibly lock the account
597 sqlx::query(
598 "UPDATE users SET login_attempts = login_attempts + 1,
599 locked_until = CASE
600 WHEN login_attempts >= 5 THEN DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 30 MINUTE)
601 ELSE locked_until
602 END
603 WHERE id = ?"
604 )
605 .bind(id)
606 .execute(&mut *tx)
607 .await
608 .context("Failed to record failed login")?;
609 }
610
611 let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = ?")
612 .bind(id)
613 .fetch_one(&mut *tx)
614 .await
615 .context("Failed to fetch updated user")?;
616
617 tx.commit().await?;
618 Ok(user)
619}
620
621/// Authenticates a user using email and password.
622///
623/// This function attempts to retrieve a user record with the provided email
624/// and hashed password combination. It's used during login processes to verify
625/// user credentials.
626///
627/// # Arguments
628///
629/// * `pool` - Database connection pool for executing the query
630/// * `email` - Email address entered by the user
631/// * `password` - Password entered by the user (should be pre-hashed)
632///
633/// # Returns
634///
635/// * `Ok(User)` - Successfully authenticated user
636/// * `Err(anyhow::Error)` - Authentication failed or user doesn't exist
637///
638/// # Security Considerations
639///
640/// This function expects the password to be pre-hashed before being passed in.
641/// It does not perform any password hashing itself, as this is typically handled
642/// by a higher-level security service that:
643///
644/// 1. Retrieves the user and their salt using `get_user_by_email`
645/// 2. Uses the salt to hash the provided password
646/// 3. Calls this function with the properly hashed password
647///
648/// # Error Handling
649///
650/// For security reasons, this function provides a generic error message
651/// regardless of whether the email wasn't found or the password was incorrect.
652/// This prevents information leakage about existing email addresses.
653///
654/// # Account Lockout
655///
656/// This function checks if the account is locked before attempting authentication.
657pub async fn login_user(
658 pool: &Pool<MySql>,
659 email: &str,
660 password: &str, // Should be pre-hashed
661) -> anyhow::Result<User> {
662 let user = sqlx::query_as::<_, User>(
663 "SELECT * FROM users
664 WHERE email = ?
665 AND password = ?
666 AND deleted_at IS NULL
667 AND active = 1
668 AND (locked_until IS NULL OR locked_until < CURRENT_TIMESTAMP)"
669 )
670 .bind(email)
671 .bind(password)
672 .fetch_one(pool)
673 .await
674 .context("Failed to login user")?;
675
676 Ok(user)
677}
678
679/// Creates a new user session.
680///
681/// This function creates a new session for a user after successful authentication.
682///
683/// # Arguments
684///
685/// * `pool` - Database connection pool for executing the query
686/// * `user_id` - ID of the authenticated user
687/// * `session_token` - Generated session token
688/// * `refresh_token` - Optional refresh token
689/// * `ip_address` - Client IP address
690/// * `user_agent` - Client user agent string
691/// * `expires_at` - Session expiration time
692///
693/// # Returns
694///
695/// * `Ok(i64)` - ID of the created session
696/// * `Err(anyhow::Error)` - Failed to create session
697pub async fn create_session(
698 pool: &Pool<MySql>,
699 user_id: i64,
700 session_token: &str,
701 refresh_token: Option<&str>,
702 ip_address: &str,
703 user_agent: &str,
704 expires_at: chrono::DateTime<chrono::Utc>,
705) -> anyhow::Result<i64> {
706 let result = sqlx::query(
707 "INSERT INTO user_sessions (
708 user_id, session_token, refresh_token, ip_address, user_agent,
709 is_active, last_activity, expires_at
710 ) VALUES (?, ?, ?, ?, ?, 1, CURRENT_TIMESTAMP, ?)"
711 )
712 .bind(user_id)
713 .bind(session_token)
714 .bind(refresh_token)
715 .bind(ip_address)
716 .bind(user_agent)
717 .bind(expires_at.naive_utc())
718 .execute(pool)
719 .await
720 .context("Failed to create session")?;
721
722 Ok(result.last_insert_id() as i64)
723}
724
725/// Invalidates a user session.
726///
727/// This function marks a session as inactive, effectively logging the user out.
728///
729/// # Arguments
730///
731/// * `pool` - Database connection pool for executing the query
732/// * `session_token` - The session token to invalidate
733///
734/// # Returns
735///
736/// * `Ok(())` - Successfully invalidated the session
737/// * `Err(anyhow::Error)` - Failed to invalidate the session
738pub async fn invalidate_session(
739 pool: &Pool<MySql>,
740 session_token: &str,
741) -> anyhow::Result<()> {
742 sqlx::query("UPDATE user_sessions SET is_active = 0 WHERE session_token = ?")
743 .bind(session_token)
744 .execute(pool)
745 .await
746 .context("Failed to invalidate session")?;
747
748 Ok(())
749}
750
751/// Invalidates all sessions for a user.
752///
753/// This function marks all of a user's sessions as inactive, effectively logging them out
754/// of all devices.
755///
756/// # Arguments
757///
758/// * `pool` - Database connection pool for executing the query
759/// * `user_id` - The ID of the user whose sessions should be invalidated
760///
761/// # Returns
762///
763/// * `Ok(())` - Successfully invalidated all sessions
764/// * `Err(anyhow::Error)` - Failed to invalidate sessions
765pub async fn invalidate_all_user_sessions(
766 pool: &Pool<MySql>,
767 user_id: i64,
768) -> anyhow::Result<()> {
769 sqlx::query("UPDATE user_sessions SET is_active = 0 WHERE user_id = ?")
770 .bind(user_id)
771 .execute(pool)
772 .await
773 .context("Failed to invalidate user sessions")?;
774
775 Ok(())
776}
777
778/// Retrieves a list of all active sessions for a user.
779pub async fn get_user_sessions(
780 pool: &Pool<MySql>,
781 user_id: i64,
782) -> anyhow::Result<Vec<UserSession>> {
783 let sessions = sqlx::query_as::<_, UserSession>(
784 "SELECT * FROM user_sessions WHERE user_id = ? AND is_active = 1"
785 )
786 .bind(user_id)
787 .fetch_all(pool)
788 .await
789 .context("Failed to fetch user sessions")?;
790
791 Ok(sessions)
792}
793
794/// Is session valid?
795pub async fn is_session_valid(
796 pool: &Pool<MySql>,
797 session_token: &str,
798) -> anyhow::Result<bool> {
799 let session = sqlx::query_as::<_, UserSession>(
800 "SELECT * FROM user_sessions WHERE session_token = ? AND is_active = 1"
801 )
802 .bind(session_token)
803 .fetch_optional(pool)
804 .await
805 .context("Failed to check session validity")?;
806
807 Ok(session.is_some())
808}
809
810/// Get user meta information
811pub async fn get_user_meta(pool: &Pool<MySql>, user_id: i64) -> Result<UserMeta, anyhow::Error> {
812 // First try to find existing meta
813 let meta = sqlx::query_as::<_, UserMeta>(
814 r#"
815 SELECT
816 id, user_id, timezone, language, theme,
817 notification_preferences, profile_image, dashboard_layout,
818 onboarding_completed, created_at, updated_at
819 FROM user_meta
820 WHERE user_id = ?
821 "#
822 )
823 .bind(user_id)
824 .fetch_optional(pool)
825 .await?;
826
827 // If meta exists, return it
828 if let Some(meta) = meta {
829 return Ok(meta);
830 }
831
832 // Otherwise create default meta for the user
833 let default_meta = sqlx::query_as::<_, UserMeta>(
834 r#"
835 INSERT INTO user_meta (
836 user_id, timezone, language, theme,
837 onboarding_completed
838 ) VALUES (?, 'UTC', 'en', 'light', 0)
839 RETURNING id, user_id, timezone, language, theme,
840 notification_preferences, profile_image, dashboard_layout,
841 onboarding_completed, created_at, updated_at
842 "#
843 )
844 .bind(user_id)
845 .fetch_one(pool)
846 .await?;
847
848 Ok(default_meta)
849}
850
851/// Get user PII (Personally Identifiable Information)
852pub async fn get_user_pii(pool: &Pool<MySql>, user_id: i64) -> Result<UserPii, anyhow::Error> {
853 // Try to find existing PII
854 let pii = sqlx::query_as::<_, UserPii>(
855 r#"
856 SELECT
857 id, user_id, first_name, last_name, full_name,
858 identity_verified, identity_verification_date,
859 identity_verification_method, created_at, updated_at
860 FROM user_pii
861 WHERE user_id = ?
862 "#
863 )
864 .bind(user_id)
865 .fetch_optional(pool)
866 .await?;
867
868 // If PII exists, return it
869 if let Some(pii) = pii {
870 return Ok(pii);
871 }
872
873 // Otherwise create empty PII record for the user
874 let default_pii = sqlx::query_as::<_, UserPii>(
875 r#"
876 INSERT INTO user_pii (
877 user_id, identity_verified
878 ) VALUES (?, 0)
879 RETURNING id, user_id, first_name, last_name, full_name,
880 identity_verified, identity_verification_date,
881 identity_verification_method, created_at, updated_at
882 "#
883 )
884 .bind(user_id)
885 .fetch_one(pool)
886 .await?;
887
888 Ok(default_pii)
889}