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}