use sqlx::{FromRow, Row}; use crate::redis_cache::{RedisCache, Cache}; // Import RedisCache and Cache Trait use serde::{Serialize, Deserialize}; use std::sync::Arc; use tokio::sync::Mutex; use tracing::{debug}; #[derive(Debug, FromRow, Serialize, Deserialize)] pub struct User { pub id: i32, pub username: String, pub email: String, pub hashed_password: String, pub roles: Option>, pub created_at: chrono::NaiveDateTime, pub updated_at: chrono::NaiveDateTime, } pub struct UserRepository { pool: sqlx::PgPool, cache: Arc>, // Thread-safe RedisCache } impl UserRepository { pub fn new(pool: sqlx::PgPool, cache: Arc>) -> Self { Self { pool, cache } } pub async fn get_user_by_id(&self, user_id: i32) -> Result { let cache_key = format!("user:{}", user_id); if let Some(user) = self.cache.lock().await.get::(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)? { return Ok(user); } let user = sqlx::query_as::<_, User>( "SELECT id, username, email, hashed_password, roles, created_at, updated_at FROM users WHERE id = $1", ) .bind(user_id) .fetch_one(&self.pool) .await?; self.cache.lock().await.set(&cache_key, &user, 300).await.map_err(|_| sqlx::Error::RowNotFound)?; Ok(user) } pub async fn get_user_by_username(&self, username: &str) -> Result { let cache_key = format!("user:username:{}", username); if let Some(user) = self.cache.lock().await.get::(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)? { return Ok(user); } let user = sqlx::query_as::<_, User>( "SELECT id, username, email, hashed_password, roles, created_at, updated_at FROM users WHERE username = $1", ) .bind(username) .fetch_one(&self.pool) .await?; self.cache.lock().await.set(&cache_key, &user, 300).await.map_err(|_| sqlx::Error::RowNotFound)?; Ok(user) } pub async fn get_user_by_email(&self, email: &str) -> Result { let cache_key = format!("user:email:{}", email); if let Some(user) = self.cache.lock().await.get::(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)? { return Ok(user); } let user = sqlx::query_as::<_, User>( "SELECT id, username, email, hashed_password, roles, created_at, updated_at FROM users WHERE email = $1", ) .bind(email) .fetch_one(&self.pool) .await?; self.cache.lock().await.set(&cache_key, &user, 300).await.map_err(|_| sqlx::Error::RowNotFound)?; Ok(user) } pub async fn create_user(&self, username: &str, email: &str, hashed_password: &str) -> Result { let row = sqlx::query( r#" INSERT INTO users (username, email, hashed_password) VALUES ($1, $2, $3) RETURNING id "#, ) .bind(username) .bind(email) .bind(hashed_password) .fetch_one(&self.pool) .await?; Ok(row.get(0)) } pub async fn update_user_email(&self, user_id: i32, new_email: &str) -> Result<(), sqlx::Error> { sqlx::query( "UPDATE users SET email = $1, updated_at = NOW() WHERE id = $2", ) .bind(new_email) .bind(user_id) .execute(&self.pool) .await?; let cache_key = format!("user:{}", user_id); self.cache.lock().await.delete(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)?; Ok(()) } pub async fn delete_user(&self, user_id: i32) -> Result<(), sqlx::Error> { sqlx::query("DELETE FROM users WHERE id = $1") .bind(user_id) .execute(&self.pool) .await?; let cache_key = format!("user:{}", user_id); self.cache.lock().await.delete(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)?; Ok(()) } }