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}