- update: major refactor of the database-service to make it easy to add newer api services

- add: character database api
This commit is contained in:
2024-12-17 01:58:18 -05:00
parent 267422adb4
commit 52455d6ffc
18 changed files with 378 additions and 180 deletions

View File

@@ -1,168 +1,121 @@
use crate::redis_cache::{Cache, RedisCache};
use serde::{Deserialize, Serialize};
use sqlx::Error;
use sqlx::PgPool;
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;
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct User {
pub id: i32,
pub username: String,
pub email: String,
pub hashed_password: String,
pub roles: Vec<String>,
pub created_at: chrono::NaiveDateTime,
pub updated_at: chrono::NaiveDateTime,
}
pub struct UsersService {
pub pool: PgPool,
pub cache: Arc<RedisCache>, // Shared Redis cache
pub struct UserRepository {
pool: sqlx::PgPool,
cache: Arc<Mutex<RedisCache>>, // Thread-safe RedisCache
}
impl UsersService {
pub async fn create_user(
&self,
username: &str,
email: &str,
hashed_password: &str,
) -> Result<i32, Error> {
let result = sqlx::query!(
r#"
INSERT INTO users (username, email, hashed_password)
VALUES ($1, $2, $3)
RETURNING id
"#,
username,
email,
hashed_password
)
.fetch_one(&self.pool)
.await?;
Ok(result.id)
impl UserRepository {
pub fn new(pool: sqlx::PgPool, cache: Arc<Mutex<RedisCache>>) -> Self {
Self { pool, cache }
}
pub async fn get_user_by_id(&self, user_id: i32) -> Result<User, sqlx::Error> {
// Check Redis cache first
if let Ok(Some(cached_user)) = self.cache.get::<User>(&format!("user:{}", user_id)).await {
return Ok(cached_user);
let cache_key = format!("user:{}", user_id);
if let Some(user) = self.cache.lock().await.get::<User>(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)? {
return Ok(user);
}
// Fetch from PostgreSQL
let row = sqlx::query!(
"SELECT id, username, email, hashed_password, roles FROM users WHERE id = $1",
user_id
)
.fetch_one(&self.pool)
.await?;
let user = User {
id: row.id,
username: row.username,
email: row.email,
hashed_password: row.hashed_password,
roles: row.roles.unwrap_or_default(),
};
// Store result in Redis
self.cache
.set(&format!("user:{}", user_id), &user, 3600)
.await
.unwrap_or_else(|err| eprintln!("Failed to cache user: {:?}", err));
Ok(user)
}
pub async fn get_user_by_username(&self, username: &str) -> Result<User, Error> {
// Check Redis cache first
if let Ok(Some(cached_user)) = self.cache.get::<User>(&format!("user_by_username:{}", username)).await {
return Ok(cached_user);
}
// Fetch from PostgreSQL
let row = sqlx::query!(
"SELECT id, username, email, hashed_password, roles FROM users WHERE username = $1",
username
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?;
let user = User {
id: row.id,
username: row.username,
email: row.email,
hashed_password: row.hashed_password,
roles: row.roles.unwrap_or_default(),
};
// Store result in Redis
self.cache
.set(&format!("user_by_username:{}", username), &user, 3600)
.await
.unwrap_or_else(|err| eprintln!("Failed to cache user: {:?}", err));
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<User, Error> {
// Check Redis cache first
if let Ok(Some(cached_user)) = self.cache.get::<User>(&format!("user_by_email:{}", email)).await {
return Ok(cached_user);
pub async fn get_user_by_username(&self, username: &str) -> Result<User, sqlx::Error> {
let cache_key = format!("user:username:{}", username);
if let Some(user) = self.cache.lock().await.get::<User>(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)? {
return Ok(user);
}
// Fetch from PostgreSQL
let row = sqlx::query!(
"SELECT id, username, email, hashed_password, roles FROM users WHERE email = $1",
email
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?;
let user = User {
id: row.id,
username: row.username,
email: row.email,
hashed_password: row.hashed_password,
roles: row.roles.unwrap_or_default(),
};
// Store result in Redis
self.cache
.set(&format!("user_by_email:{}", email), &user, 3600)
.await
.unwrap_or_else(|err| eprintln!("Failed to cache user: {:?}", err));
self.cache.lock().await.set(&cache_key, &user, 300).await.map_err(|_| sqlx::Error::RowNotFound)?;
Ok(user)
}
pub async fn update_user_email(&self, user_id: i32, new_email: &str) -> Result<(), Error> {
sqlx::query!(
r#"
UPDATE users
SET email = $1, updated_at = CURRENT_TIMESTAMP
WHERE id = $2
"#,
new_email,
user_id
pub async fn get_user_by_email(&self, email: &str) -> Result<User, sqlx::Error> {
let cache_key = format!("user:email:{}", email);
if let Some(user) = self.cache.lock().await.get::<User>(&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, roles: &[String]) -> Result<i32, sqlx::Error> {
let row = sqlx::query(
"INSERT INTO users (username, email, hashed_password, roles, created_at, updated_at) \
VALUES ($1, $2, $3, $4, NOW(), NOW()) RETURNING id",
)
.bind(username)
.bind(email)
.bind(hashed_password)
.bind(roles)
.fetch_one(&self.pool)
.await?;
Ok(row.get("id"))
}
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<(), Error> {
sqlx::query!(
r#"
DELETE FROM users
WHERE id = $1
"#,
user_id
)
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(())
}
}