- chore: ran cargo fix on the codebase

This commit is contained in:
2025-03-07 21:03:15 -05:00
parent 3b789d0fd4
commit b6f2d3f456
59 changed files with 1324 additions and 523 deletions

View File

@@ -3,6 +3,12 @@ fn main() {
.build_server(true)
.compile_well_known_types(true)
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
.compile_protos(&["../proto/user_db_api.proto", "../proto/character_db_api.proto"], &["../proto"])
.compile_protos(
&[
"../proto/user_db_api.proto",
"../proto/character_db_api.proto",
],
&["../proto"],
)
.unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
}

View File

@@ -1,8 +1,8 @@
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Row};
use utils::redis_cache::{Cache, RedisCache}; // Import RedisCache
use serde::{Serialize, Deserialize};
use std::sync::Arc;
use tokio::sync::Mutex;
use utils::redis_cache::{Cache, RedisCache}; // Import RedisCache
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct Character {
@@ -34,7 +34,14 @@ impl CharacterRepository {
let cache_key = format!("character:{}", character_id);
// Try fetching from Redis cache
if let Some(character) = self.cache.lock().await.get::<Character>(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)? {
if let Some(character) = self
.cache
.lock()
.await
.get::<Character>(&cache_key)
.await
.map_err(|_| sqlx::Error::RowNotFound)?
{
return Ok(character);
}
@@ -49,11 +56,24 @@ impl CharacterRepository {
.await?;
// Cache result
self.cache.lock().await.set(&cache_key, &character, 300).await.map_err(|_| sqlx::Error::RowNotFound)?;
self.cache
.lock()
.await
.set(&cache_key, &character, 300)
.await
.map_err(|_| sqlx::Error::RowNotFound)?;
Ok(character)
}
pub async fn create_character(&self, user_id: i32, name: &str, inventory: serde_json::Value, stats: serde_json::Value, looks: serde_json::Value, position: serde_json::Value) -> Result<i32, sqlx::Error> {
pub async fn create_character(
&self,
user_id: i32,
name: &str,
inventory: serde_json::Value,
stats: serde_json::Value,
looks: serde_json::Value,
position: serde_json::Value,
) -> Result<i32, sqlx::Error> {
let result = sqlx::query(
"INSERT INTO characters (user_id, name, inventory, stats, looks, position, created_at, updated_at, is_active) \
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW(), true) RETURNING id",
@@ -69,35 +89,62 @@ impl CharacterRepository {
// Invalidate cache
let cache_key = format!("character:user:{}", user_id);
self.cache.lock().await.delete(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)?;
self.cache
.lock()
.await
.delete(&cache_key)
.await
.map_err(|_| sqlx::Error::RowNotFound)?;
Ok(result.get("id"))
}
pub async fn delete_character(&self, character_id: i32, delete_type: i32) -> Result<i64, sqlx::Error> {
let mut query = "UPDATE characters SET updated_at = NOW(), deleted_at = NOW() + '24 hours' WHERE id = $1 RETURNING user_id, extract(epoch from (deleted_at - now()))::BIGINT as deleted_at";
pub async fn delete_character(
&self,
character_id: i32,
delete_type: i32,
) -> Result<i64, sqlx::Error> {
let mut query = "UPDATE characters SET updated_at = NOW(), deleted_at = NOW() + '24 hours' WHERE id = $1 RETURNING user_id, extract(epoch from (deleted_at - now()))::BIGINT as deleted_at";
if 0 == delete_type {
query = "UPDATE characters SET updated_at = NOW(), deleted_at = null WHERE id = $1 RETURNING user_id, 0::BIGINT as deleted_at";
}
let result = sqlx::query(
query,
)
let result = sqlx::query(query)
.bind(character_id)
.fetch_one(&self.pool)
.await?;
// Invalidate cache
let cache_key = format!("character:user:{}", result.get::<i32, &str>("user_id"));
self.cache.lock().await.delete(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)?;
self.cache
.lock()
.await
.delete(&cache_key)
.await
.map_err(|_| sqlx::Error::RowNotFound)?;
let cache_key = format!("character:{}", character_id);
self.cache.lock().await.delete(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)?;
self.cache
.lock()
.await
.delete(&cache_key)
.await
.map_err(|_| sqlx::Error::RowNotFound)?;
Ok(result.get::<i64, &str>("deleted_at"))
}
pub async fn get_characters_by_user(&self, user_id: i32) -> Result<Vec<Character>, sqlx::Error> {
pub async fn get_characters_by_user(
&self,
user_id: i32,
) -> Result<Vec<Character>, sqlx::Error> {
let cache_key = format!("character:user:{}", user_id);
// Try fetching from Redis cache
if let Some(characters) = self.cache.lock().await.get::<Vec<Character>>(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)? {
if let Some(characters) = self
.cache
.lock()
.await
.get::<Vec<Character>>(&cache_key)
.await
.map_err(|_| sqlx::Error::RowNotFound)?
{
return Ok(characters);
}
@@ -110,7 +157,12 @@ impl CharacterRepository {
.await?;
// Cache result
self.cache.lock().await.set(&cache_key, &characters, 300).await.map_err(|_| sqlx::Error::RowNotFound)?;
self.cache
.lock()
.await
.set(&cache_key, &characters, 300)
.await
.map_err(|_| sqlx::Error::RowNotFound)?;
Ok(characters)
}
}

View File

@@ -1,9 +1,9 @@
use crate::users::UserRepository;
use crate::characters::CharacterRepository;
use utils::redis_cache::RedisCache;
use crate::users::UserRepository;
use sqlx::PgPool;
use std::sync::Arc;
use tokio::sync::Mutex;
use utils::redis_cache::RedisCache;
pub struct Database {
pub user_repo: Arc<UserRepository>,

View File

@@ -1,7 +1,10 @@
use serde_json::Value::Null;
use crate::grpc::{Character, CharacterRequest, CharacterListRequest, CharacterListResponse, CreateCharacterRequest, CreateCharacterResponse, DeleteCharacterRequest, DeleteCharacterResponse};
use crate::grpc::character_db_service_server::CharacterDbService;
use crate::grpc::database_service::MyDatabaseService;
use crate::grpc::{
Character, CharacterListRequest, CharacterListResponse, CharacterRequest,
CreateCharacterRequest, CreateCharacterResponse, DeleteCharacterRequest,
DeleteCharacterResponse,
};
use tonic::{Request, Response, Status};
#[tonic::async_trait]
@@ -18,11 +21,11 @@ impl CharacterDbService for MyDatabaseService {
.await
.map_err(|_| Status::not_found("Character not found"))?;
let mut deleted_at= "".to_string();
let mut deleted_at = "".to_string();
if character.deleted_at.is_some() {
deleted_at = character.deleted_at.unwrap().to_string();
}
let response = Character {
id: character.id,
user_id: character.user_id,
@@ -52,12 +55,11 @@ impl CharacterDbService for MyDatabaseService {
.get_characters_by_user(req.user_id)
.await
.map_err(|_| Status::not_found("Character not found"))?;
let mut character_list: Vec<Character> = Vec::new();
for character in characters {
let mut deleted_at= "".to_string();
let mut deleted_at = "".to_string();
if character.deleted_at.is_some() {
deleted_at = character.deleted_at.unwrap_or_default().to_string();
}
@@ -91,7 +93,7 @@ impl CharacterDbService for MyDatabaseService {
) -> Result<Response<CreateCharacterResponse>, Status> {
let req = request.into_inner();
let repo = &self.db.character_repo;
//todo: we need to check if the character name exists already
let character_id = repo
@@ -126,7 +128,8 @@ impl CharacterDbService for MyDatabaseService {
let req = request.into_inner();
let repo = &self.db.character_repo;
let time_left_in_seconds = repo.delete_character(req.character_id, req.delete_type)
let time_left_in_seconds = repo
.delete_character(req.character_id, req.delete_type)
.await
.map_err(|_| Status::internal("Failed to delete character"))?;

View File

@@ -4,4 +4,4 @@ use std::sync::Arc;
#[derive(Clone)]
pub struct MyDatabaseService {
pub db: Arc<Database>, // Use the Database struct from users.rs
}
}

View File

@@ -1,6 +1,6 @@
mod character_service;
pub mod database_service;
pub mod user_service;
mod character_service;
tonic::include_proto!("user_db_api");
tonic::include_proto!("character_db_api");
tonic::include_proto!("character_db_api");

View File

@@ -1,6 +1,9 @@
use crate::grpc::{CreateUserRequest, CreateUserResponse, GetUserByEmailRequest, GetUserByUsernameRequest, GetUserRequest, GetUserResponse};
use crate::grpc::user_service_server::UserService;
use crate::grpc::database_service::MyDatabaseService;
use crate::grpc::user_service_server::UserService;
use crate::grpc::{
CreateUserRequest, CreateUserResponse, GetUserByEmailRequest, GetUserByUsernameRequest,
GetUserRequest, GetUserResponse,
};
use tonic::{Request, Response, Status};
#[tonic::async_trait]
@@ -11,7 +14,10 @@ impl UserService for MyDatabaseService {
) -> Result<Response<GetUserResponse>, Status> {
let req = request.into_inner();
let user = self.db.user_repo.get_user_by_id(req.user_id)
let user = self
.db
.user_repo
.get_user_by_id(req.user_id)
.await
.map_err(|_| Status::not_found("User not found"))?;
@@ -30,7 +36,10 @@ impl UserService for MyDatabaseService {
) -> Result<Response<CreateUserResponse>, Status> {
let req = request.into_inner();
let user_id = self.db.user_repo.create_user(&req.username, &req.email, &req.hashed_password)
let user_id = self
.db
.user_repo
.create_user(&req.username, &req.email, &req.hashed_password)
.await
.map_err(|_| Status::internal("Failed to create user"))?;
@@ -44,7 +53,10 @@ impl UserService for MyDatabaseService {
) -> Result<Response<GetUserResponse>, Status> {
let req = request.into_inner();
let user = self.db.user_repo.get_user_by_username(&req.username)
let user = self
.db
.user_repo
.get_user_by_username(&req.username)
.await
.map_err(|_| Status::not_found("User not found"))?;
@@ -63,7 +75,10 @@ impl UserService for MyDatabaseService {
) -> Result<Response<GetUserResponse>, Status> {
let req = request.into_inner();
let user = self.db.user_repo.get_user_by_email(&req.email)
let user = self
.db
.user_repo
.get_user_by_email(&req.email)
.await
.map_err(|_| Status::not_found("User not found"))?;
@@ -75,4 +90,4 @@ impl UserService for MyDatabaseService {
roles: user.roles.unwrap_or_else(Vec::new),
}))
}
}
}

View File

@@ -1,4 +1,4 @@
pub mod users;
pub mod characters;
pub mod db;
pub mod grpc;
pub mod grpc;
pub mod users;

View File

@@ -1,25 +1,27 @@
use std::collections::HashMap;
use database_service::db::Database;
use database_service::grpc::character_db_service_server::CharacterDbServiceServer;
use database_service::grpc::database_service::MyDatabaseService;
use database_service::grpc::user_service_server::UserServiceServer;
use database_service::grpc::character_db_service_server::CharacterDbServiceServer;
use utils::redis_cache::RedisCache;
use dotenv::dotenv;
use sqlx::postgres::PgPoolOptions;
use std::collections::HashMap;
use std::env;
use std::str::FromStr;
use std::sync::Arc;
use tokio::{select, signal};
use tokio::sync::Mutex;
use tonic::transport::Server;
use tracing::{info, Level};
use utils::consul_registration;
use utils::redis_cache::RedisCache;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
dotenv().ok();
tracing_subscriber::fmt()
.with_max_level(Level::from_str(&env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string())).unwrap_or_else(|_| Level::INFO))
.with_max_level(
Level::from_str(&env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string()))
.unwrap_or_else(|_| Level::INFO),
)
.init();
let addr = env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0".to_string());
@@ -27,14 +29,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let health_port = env::var("HEALTH_CHECK_PORT").unwrap_or_else(|_| "8080".to_string());
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let redis_url = std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
let redis_url =
std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
let consul_url = env::var("CONSUL_URL").unwrap_or_else(|_| "http://127.0.0.1:8500".to_string());
let service_name = env::var("SERVICE_NAME").unwrap_or_else(|_| "database-service".to_string());
let service_address = env::var("DATABASE_SERVICE_ADDR").unwrap_or_else(|_| "127.0.0.1".to_string());
let service_address =
env::var("DATABASE_SERVICE_ADDR").unwrap_or_else(|_| "127.0.0.1".to_string());
let service_port = port.clone();
let health_check_url = format!("http://{}:{}/health", service_address, health_port);
// Register service with Consul
let service_id = consul_registration::get_or_generate_service_id(env!("CARGO_PKG_NAME"));
let version = env!("CARGO_PKG_VERSION").to_string();
@@ -50,7 +54,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
meta,
&health_check_url,
)
.await?;
.await?;
consul_registration::start_health_check(addr.as_str()).await?;
@@ -62,20 +66,23 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.await
.expect("Failed to create PostgreSQL connection pool");
let redis_cache = Arc::new(Mutex::new(RedisCache::new(&redis_url)));
let db = Arc::new(Database::new(pool, redis_cache));
let my_service = MyDatabaseService { db };
// Pass `shared_cache` into services as needed
info!("Database Service running on {}", address);
tokio::spawn(Server::builder()
.add_service(UserServiceServer::new(my_service.clone()))
.add_service(CharacterDbServiceServer::new(my_service))
.serve(address));
tokio::spawn(
Server::builder()
.add_service(UserServiceServer::new(my_service.clone()))
.add_service(CharacterDbServiceServer::new(my_service))
.serve(address),
);
utils::signal_handler::wait_for_signal().await;
consul_registration::deregister_service(&consul_url, service_id.as_str()).await.expect("");
consul_registration::deregister_service(&consul_url, service_id.as_str())
.await
.expect("");
info!("service {} deregistered", service_name);
Ok(())
}

View File

@@ -1,9 +1,8 @@
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Row};
use utils::redis_cache::{RedisCache, Cache}; // Import RedisCache and Cache Trait
use serde::{Serialize, Deserialize};
use std::sync::Arc;
use tokio::sync::Mutex;
use tracing::{debug};
use utils::redis_cache::{Cache, RedisCache}; // Import RedisCache and Cache Trait
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct User {
@@ -29,7 +28,14 @@ impl UserRepository {
pub async fn get_user_by_id(&self, user_id: i32) -> Result<User, sqlx::Error> {
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)? {
if let Some(user) = self
.cache
.lock()
.await
.get::<User>(&cache_key)
.await
.map_err(|_| sqlx::Error::RowNotFound)?
{
return Ok(user);
}
@@ -40,14 +46,26 @@ impl UserRepository {
.fetch_one(&self.pool)
.await?;
self.cache.lock().await.set(&cache_key, &user, 300).await.map_err(|_| sqlx::Error::RowNotFound)?;
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<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)? {
if let Some(user) = self
.cache
.lock()
.await
.get::<User>(&cache_key)
.await
.map_err(|_| sqlx::Error::RowNotFound)?
{
return Ok(user);
}
@@ -58,14 +76,26 @@ impl UserRepository {
.fetch_one(&self.pool)
.await?;
self.cache.lock().await.set(&cache_key, &user, 300).await.map_err(|_| sqlx::Error::RowNotFound)?;
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, 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)? {
if let Some(user) = self
.cache
.lock()
.await
.get::<User>(&cache_key)
.await
.map_err(|_| sqlx::Error::RowNotFound)?
{
return Ok(user);
}
@@ -76,11 +106,21 @@ impl UserRepository {
.fetch_one(&self.pool)
.await?;
self.cache.lock().await.set(&cache_key, &user, 300).await.map_err(|_| sqlx::Error::RowNotFound)?;
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<i32, sqlx::Error> {
pub async fn create_user(
&self,
username: &str,
email: &str,
hashed_password: &str,
) -> Result<i32, sqlx::Error> {
let row = sqlx::query(
r#"
INSERT INTO users (username, email, hashed_password)
@@ -88,26 +128,33 @@ impl UserRepository {
RETURNING id
"#,
)
.bind(username)
.bind(email)
.bind(hashed_password)
.fetch_one(&self.pool)
.await?;
.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",
)
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)?;
self.cache
.lock()
.await
.delete(&cache_key)
.await
.map_err(|_| sqlx::Error::RowNotFound)?;
Ok(())
}
@@ -118,7 +165,12 @@ impl UserRepository {
.await?;
let cache_key = format!("user:{}", user_id);
self.cache.lock().await.delete(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)?;
self.cache
.lock()
.await
.delete(&cache_key)
.await
.map_err(|_| sqlx::Error::RowNotFound)?;
Ok(())
}
}

View File

@@ -4,7 +4,7 @@ use tokio;
async fn test_get_user() {
// // Set up a temporary in-memory PostgreSQL database
// let pool = PgPool::connect("postgres://user:password@localhost/test_database").await.unwrap();
//
//
// // Create the test table
// pool.execute(
// r#"
@@ -20,7 +20,7 @@ async fn test_get_user() {
// )
// .await
// .unwrap();
//
//
// // Test the `get_user` function
// let user = get_user(&pool, "123").await.unwrap();
// assert_eq!(user.user_id, "123");

View File

@@ -9,4 +9,4 @@ mod tests {
// let db = Database::new(&database_url).await;
// assert!(db.health_check().await);
}
}
}

View File

@@ -3,13 +3,13 @@ async fn test_redis_cache() {
// dotenv().ok();
// let redis_url = std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://127.0.0.1:6379".to_string());
// let cache = RedisCache::new(&redis_url);
//
//
// let key = &"test_key".to_string();
// let value = "test_value";
//
//
// // Test setting a value
// cache.set(key, &value, 10).await.unwrap();
//
//
// // Test getting the value
// let cached_value: Option<String> = cache.get(key).await.unwrap();
// assert_eq!(cached_value, Some("test_value".to_string()));