omni_orchestrator/schemas/v1/db/queries/
user.rs

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