diff --git a/auth-service/build.rs b/auth-service/build.rs index 51b9cc7..40b5a0b 100644 --- a/auth-service/build.rs +++ b/auth-service/build.rs @@ -11,6 +11,6 @@ fn main() { tonic_build::configure() .build_server(false) // Generate gRPC client code .compile_well_known_types(true) - .compile_protos(&["../proto/database.proto"], &["../proto"]) + .compile_protos(&["../proto/user_db_api.proto"], &["../proto"]) .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); } diff --git a/auth-service/src/database_client.rs b/auth-service/src/database_client.rs index 7fca936..51e2ff5 100644 --- a/auth-service/src/database_client.rs +++ b/auth-service/src/database_client.rs @@ -1,4 +1,4 @@ -use crate::database::{database_service_client::DatabaseServiceClient, CreateUserRequest, CreateUserResponse, GetUserByEmailRequest, GetUserByUsernameRequest, GetUserRequest, GetUserResponse}; +use crate::database::{user_service_client::UserServiceClient, CreateUserRequest, CreateUserResponse, GetUserByEmailRequest, GetUserByUsernameRequest, GetUserRequest, GetUserResponse}; use async_trait::async_trait; use chrono::{DateTime, Utc}; use std::error::Error; @@ -18,7 +18,7 @@ pub trait DatabaseClientTrait: Sized { } #[derive(Clone)] pub struct DatabaseClient { - client: DatabaseServiceClient, + client: UserServiceClient, } #[derive(Debug)] @@ -31,7 +31,7 @@ pub struct PasswordReset { #[async_trait] impl DatabaseClientTrait for DatabaseClient { async fn connect(endpoint: &str) -> Result> { - let client = DatabaseServiceClient::connect(endpoint.to_string()).await?; + let client = UserServiceClient::connect(endpoint.to_string()).await?; Ok(Self { client }) } diff --git a/auth-service/src/lib.rs b/auth-service/src/lib.rs index 7ed4117..8167bba 100644 --- a/auth-service/src/lib.rs +++ b/auth-service/src/lib.rs @@ -7,7 +7,7 @@ pub mod auth { tonic::include_proto!("auth"); // Path matches the package name in auth.proto } pub mod database { - tonic::include_proto!("database"); // Matches package name in database.proto + tonic::include_proto!("user_db_api"); // Matches package name in user_db_api.proto } #[cfg(test)] diff --git a/character-service/build.rs b/character-service/build.rs index 99ef439..36bf665 100644 --- a/character-service/build.rs +++ b/character-service/build.rs @@ -11,6 +11,6 @@ fn main() { tonic_build::configure() .build_server(false) // Generate gRPC client code .compile_well_known_types(true) - .compile_protos(&["../proto/database.proto", "../proto/auth.proto"], &["../proto"]) + .compile_protos(&["../proto/user_db_api.proto", "../proto/auth.proto"], &["../proto"]) .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); } diff --git a/database-service/build.rs b/database-service/build.rs index faddf25..0a7196c 100644 --- a/database-service/build.rs +++ b/database-service/build.rs @@ -3,6 +3,6 @@ fn main() { .build_server(true) .compile_well_known_types(true) .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]") - .compile_protos(&["../proto/database.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)); } diff --git a/database-service/src/characters.rs b/database-service/src/characters.rs new file mode 100644 index 0000000..a8e48b4 --- /dev/null +++ b/database-service/src/characters.rs @@ -0,0 +1,110 @@ +use sqlx::{FromRow, Row}; +use crate::redis_cache::{Cache, RedisCache}; // Import RedisCache +use serde::{Serialize, Deserialize}; +use std::sync::Arc; +use tokio::sync::Mutex; + +#[derive(Debug, FromRow, Serialize, Deserialize)] +pub struct Character { + pub id: i32, + pub user_id: i32, + pub name: String, + pub level: i16, + pub experience: i64, + pub inventory: serde_json::Value, + pub stats: serde_json::Value, + pub looks: serde_json::Value, + pub position: serde_json::Value, + pub created_at: chrono::NaiveDateTime, + pub updated_at: chrono::NaiveDateTime, + pub deleted_at: Option, + pub is_active: bool, +} + +pub struct CharacterRepository { + pool: sqlx::PgPool, + cache: Arc>, // Thread-safe RedisCache +} + +impl CharacterRepository { + pub fn new(pool: sqlx::PgPool, cache: Arc>) -> Self { + Self { pool, cache } + } + + pub async fn get_character_by_id(&self, character_id: i32) -> Result { + let cache_key = format!("character:{}", character_id); + + // Try fetching from Redis cache + if let Some(character) = self.cache.lock().await.get::(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)? { + return Ok(character); + } + + // Fetch from database + let character = sqlx::query_as::<_, Character>( + "SELECT id, user_id, name, level, experience, inventory, stats, looks, position, \ + created_at, updated_at, deleted_at, is_active \ + FROM characters WHERE id = $1 AND is_active = true", + ) + .bind(character_id) + .fetch_one(&self.pool) + .await?; + + // Cache result + 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 { + let result = sqlx::query( + "INSERT INTO characters (user_id, name, level, experience, inventory, stats, looks, position, created_at, updated_at, is_active) \ + VALUES ($1, $2, 1, 0, $3, $4, $5, $6, NOW(), NOW(), true) RETURNING id", + ) + .bind(user_id) + .bind(name) + .bind(inventory) + .bind(stats) + .bind(looks) + .bind(position) + .fetch_one(&self.pool) + .await?; + + Ok(result.get("id")) + } + + pub async fn delete_character(&self, character_id: i32) -> Result<(), sqlx::Error> { + sqlx::query( + "UPDATE characters SET deleted_at = NOW(), is_active = false WHERE id = $1", + ) + .bind(character_id) + .execute(&self.pool) + .await?; + + // Invalidate cache + let cache_key = format!("character:{}", character_id); + self.cache.lock().await.delete(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)?; + Ok(()) + } + + pub async fn get_characters_by_user(&self, user_id: i32) -> Result, sqlx::Error> { + let cache_key = format!("character:user:{}", user_id); + + // Try fetching from Redis cache + if let Some(characters) = self.cache.lock().await.get::>(&cache_key).await.map_err(|_| sqlx::Error::RowNotFound)? { + return Ok(characters); + } + + // Fetch from database + let characters = sqlx::query_as::<_, Character>( + "SELECT id, user_id, name, level, experience, inventory, stats, looks, position, \ + created_at, updated_at, deleted_at, is_active \ + FROM characters WHERE user_id = $1 AND is_active = true", + ) + .bind(user_id) + .fetch_all(&self.pool) + .await?; + + // Cache result + self.cache.lock().await.set(&cache_key, &characters, 300).await.map_err(|_| sqlx::Error::RowNotFound)?; + Ok(characters) + } +} diff --git a/database-service/src/db.rs b/database-service/src/db.rs index 7483847..005a02d 100644 --- a/database-service/src/db.rs +++ b/database-service/src/db.rs @@ -1,24 +1,23 @@ +use crate::users::UserRepository; +use crate::characters::CharacterRepository; use crate::redis_cache::RedisCache; -use crate::users::UsersService; use sqlx::PgPool; use std::sync::Arc; +use tokio::sync::Mutex; pub struct Database { - pub users_service: UsersService, // User-specific functionality + pub user_repo: Arc, + pub character_repo: Arc, } impl Database { - pub async fn new(pool: PgPool, cache: Arc) -> Self { - let users_service = UsersService { pool, cache }; + pub fn new(pool: PgPool, redis_cache: Arc>) -> Self { + let user_repo = Arc::new(UserRepository::new(pool.clone(), redis_cache.clone())); + let character_repo = Arc::new(CharacterRepository::new(pool.clone(), redis_cache)); - Self { users_service } - } - - pub async fn health_check(&self) -> bool { - // Simple query to check database health - sqlx::query("SELECT 1") - .execute(&self.users_service.pool) - .await - .is_ok() + Self { + user_repo, + character_repo, + } } } diff --git a/database-service/src/grpc/character_service.rs b/database-service/src/grpc/character_service.rs new file mode 100644 index 0000000..1c3156c --- /dev/null +++ b/database-service/src/grpc/character_service.rs @@ -0,0 +1,93 @@ +use crate::grpc::{CharacterRequest, CharacterResponse, CreateCharacterRequest, DeleteCharacterRequest, Empty}; +use crate::grpc::character_service_server::CharacterService; +use crate::grpc::database_service::MyDatabaseService; +use tonic::{Request, Response, Status}; + +#[tonic::async_trait] +impl CharacterService for MyDatabaseService { + async fn get_character( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let repo = &self.db.character_repo; + + let character = repo + .get_character_by_id(req.character_id) + .await + .map_err(|_| Status::not_found("Character not found"))?; + + let response = CharacterResponse { + id: character.id, + user_id: character.user_id, + name: character.name, + level: character.level as i32, + experience: character.experience, + inventory: character.inventory.to_string(), + stats: character.stats.to_string(), + looks: character.looks.to_string(), + position: character.position.to_string(), + created_at: character.created_at.to_string(), + updated_at: character.updated_at.to_string(), + is_active: character.is_active, + }; + + Ok(Response::new(response)) + } + + async fn create_character( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let repo = &self.db.character_repo; + + let character_id = repo + .create_character( + req.user_id, + &req.name, + serde_json::from_str(&req.inventory).unwrap_or_default(), + serde_json::from_str(&req.stats).unwrap_or_default(), + serde_json::from_str(&req.looks).unwrap_or_default(), + serde_json::from_str(&req.position).unwrap_or_default(), + ) + .await + .map_err(|_| Status::internal("Failed to create character"))?; + + let character = repo + .get_character_by_id(character_id) + .await + .map_err(|_| Status::not_found("Character not found"))?; + + let response = CharacterResponse { + id: character.id, + user_id: character.user_id, + name: character.name, + level: character.level as i32, + experience: character.experience, + inventory: character.inventory.to_string(), + stats: character.stats.to_string(), + looks: character.looks.to_string(), + position: character.position.to_string(), + created_at: character.created_at.to_string(), + updated_at: character.updated_at.to_string(), + is_active: character.is_active, + }; + + Ok(Response::new(response)) + } + + async fn delete_character( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let repo = &self.db.character_repo; + + repo.delete_character(req.character_id) + .await + .map_err(|_| Status::internal("Failed to delete character"))?; + + Ok(Response::new(Empty {})) + } +} diff --git a/database-service/src/grpc/database_service.rs b/database-service/src/grpc/database_service.rs new file mode 100644 index 0000000..e12d691 --- /dev/null +++ b/database-service/src/grpc/database_service.rs @@ -0,0 +1,7 @@ +use crate::db::Database; +use std::sync::Arc; + +#[derive(Clone)] +pub struct MyDatabaseService { + pub db: Arc, // Use the Database struct from users.rs +} \ No newline at end of file diff --git a/database-service/src/grpc/mod.rs b/database-service/src/grpc/mod.rs new file mode 100644 index 0000000..e43987d --- /dev/null +++ b/database-service/src/grpc/mod.rs @@ -0,0 +1,6 @@ +pub mod database_service; +pub mod user_service; +mod character_service; + +tonic::include_proto!("user_db_api"); +tonic::include_proto!("character_db_api"); \ No newline at end of file diff --git a/database-service/src/grpc.rs b/database-service/src/grpc/user_service.rs similarity index 74% rename from database-service/src/grpc.rs rename to database-service/src/grpc/user_service.rs index 959aac6..9243bd1 100644 --- a/database-service/src/grpc.rs +++ b/database-service/src/grpc/user_service.rs @@ -1,22 +1,17 @@ -use crate::database::{CreateUserRequest, CreateUserResponse, GetUserByEmailRequest, GetUserByUsernameRequest, GetUserRequest, GetUserResponse}; -use crate::db::Database; +use crate::grpc::{CreateUserRequest, CreateUserResponse, GetUserByEmailRequest, GetUserByUsernameRequest, GetUserRequest, GetUserResponse}; +use crate::grpc::user_service_server::UserService; +use crate::grpc::database_service::MyDatabaseService; use tonic::{Request, Response, Status}; -use crate::database::database_service_server::DatabaseService; - -pub struct MyDatabaseService { - pub db: Database, // Use the Database struct from users.rs -} - #[tonic::async_trait] -impl DatabaseService for MyDatabaseService { +impl UserService for MyDatabaseService { async fn get_user( &self, request: Request, ) -> Result, Status> { let req = request.into_inner(); - let user = self.db.users_service.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"))?; @@ -35,7 +30,7 @@ impl DatabaseService for MyDatabaseService { ) -> Result, Status> { let req = request.into_inner(); - let user_id = self.db.users_service.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"))?; @@ -49,7 +44,7 @@ impl DatabaseService for MyDatabaseService { ) -> Result, Status> { let req = request.into_inner(); - let user = self.db.users_service.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"))?; @@ -68,7 +63,7 @@ impl DatabaseService for MyDatabaseService { ) -> Result, Status> { let req = request.into_inner(); - let user = self.db.users_service.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"))?; diff --git a/database-service/src/lib.rs b/database-service/src/lib.rs index fb1b43a..cabb231 100644 --- a/database-service/src/lib.rs +++ b/database-service/src/lib.rs @@ -1,8 +1,5 @@ pub mod users; +pub mod characters; pub mod redis_cache; pub mod db; -pub mod grpc; - -pub mod database { - tonic::include_proto!("database"); -} \ No newline at end of file +pub mod grpc; \ No newline at end of file diff --git a/database-service/src/main.rs b/database-service/src/main.rs index 2792c63..ccf3eb1 100644 --- a/database-service/src/main.rs +++ b/database-service/src/main.rs @@ -1,19 +1,18 @@ use std::collections::HashMap; -use database::database_service_server::DatabaseServiceServer; -use database_service::database; use database_service::db::Database; -use database_service::grpc::MyDatabaseService; +use database_service::grpc::database_service::MyDatabaseService; +use database_service::grpc::user_service_server::UserServiceServer; +use database_service::grpc::character_service_server::CharacterServiceServer; use database_service::redis_cache::RedisCache; use dotenv::dotenv; use sqlx::postgres::PgPoolOptions; use std::env; -use std::net::ToSocketAddrs; 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 warp::Filter; use utils::consul_registration; #[tokio::main] @@ -32,15 +31,14 @@ async fn main() -> Result<(), Box> { 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); - let health_check_endpoint_addr = format!("{}:{}", service_address, health_port); // Register service with Consul let service_id = consul_registration::generate_service_id(); let tags = vec!["version-1.0".to_string()]; - let mut meta = HashMap::new(); + let meta = HashMap::new(); consul_registration::register_service( &consul_url, service_id.as_str(), @@ -63,18 +61,17 @@ async fn main() -> Result<(), Box> { .await .expect("Failed to create PostgreSQL connection pool"); - let redis_cache = RedisCache::new(&redis_url); - let cache = Arc::new(redis_cache); // Share the cache instance between tasks - let database_service = MyDatabaseService { - db: Database::new(pool, cache).await, - }; + 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(DatabaseServiceServer::new(database_service)) - .serve(address)); + .add_service(UserServiceServer::new(my_service.clone())) + .add_service(CharacterServiceServer::new(my_service)) + .serve(address)); select! { _ = signal::ctrl_c() => {}, diff --git a/database-service/src/users.rs b/database-service/src/users.rs index 46eb1bf..0ea5ec9 100644 --- a/database-service/src/users.rs +++ b/database-service/src/users.rs @@ -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, + pub created_at: chrono::NaiveDateTime, + pub updated_at: chrono::NaiveDateTime, } -pub struct UsersService { - pub pool: PgPool, - pub cache: Arc, // Shared Redis cache +pub struct UserRepository { + pool: sqlx::PgPool, + cache: Arc>, // Thread-safe RedisCache } - -impl UsersService { - pub async fn create_user( - &self, - username: &str, - email: &str, - hashed_password: &str, - ) -> Result { - 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>) -> Self { + Self { pool, cache } } pub async fn get_user_by_id(&self, user_id: i32) -> Result { - // Check Redis cache first - if let Ok(Some(cached_user)) = self.cache.get::(&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::(&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 { - // Check Redis cache first - if let Ok(Some(cached_user)) = self.cache.get::(&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 { - // Check Redis cache first - if let Ok(Some(cached_user)) = self.cache.get::(&format!("user_by_email:{}", email)).await { - return Ok(cached_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); } - // 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 { + 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, roles: &[String]) -> Result { + 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(()) } } diff --git a/proto/character_db_api.proto b/proto/character_db_api.proto new file mode 100644 index 0000000..ab3ec3e --- /dev/null +++ b/proto/character_db_api.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +package character_db_api; + +service CharacterService { + rpc GetCharacter (CharacterRequest) returns (CharacterResponse); + rpc CreateCharacter (CreateCharacterRequest) returns (CharacterResponse); + rpc DeleteCharacter (DeleteCharacterRequest) returns (Empty); +} + +message CharacterRequest { + int32 character_id = 1; +} + +message CreateCharacterRequest { + int32 user_id = 1; + string name = 2; + string inventory = 3; // JSON serialized + string stats = 4; // JSON serialized + string looks = 5; // JSON serialized + string position = 6; // JSON serialized +} + +message DeleteCharacterRequest { + int32 character_id = 1; +} + +message CharacterResponse { + int32 id = 1; + int32 user_id = 2; + string name = 3; + int32 level = 4; + int64 experience = 5; + string inventory = 6; + string stats = 7; + string looks = 8; + string position = 9; + string created_at = 10; + string updated_at = 11; + bool is_active = 12; +} + +message Empty {} diff --git a/proto/database.proto b/proto/user_db_api.proto similarity index 94% rename from proto/database.proto rename to proto/user_db_api.proto index 081b5b2..ff2ec36 100644 --- a/proto/database.proto +++ b/proto/user_db_api.proto @@ -1,8 +1,8 @@ syntax = "proto3"; -package database; +package user_db_api; -service DatabaseService { +service UserService { rpc GetUser(GetUserRequest) returns (GetUserResponse); rpc CreateUser(CreateUserRequest) returns (CreateUserResponse); rpc GetUserByUsername(GetUserByUsernameRequest) returns (GetUserResponse); diff --git a/utils/src/service_discovery.rs b/utils/src/service_discovery.rs index 0b1d62a..4dfdb97 100644 --- a/utils/src/service_discovery.rs +++ b/utils/src/service_discovery.rs @@ -35,8 +35,6 @@ pub async fn get_service_address(consul_url: &str, service_name: &str) -> Result Ok(nodes) } } - -// Example of filtering services with a specific tag async fn get_services_with_tag( service_name: &str, tag: &str, diff --git a/world-service/build.rs b/world-service/build.rs index 899f569..5bec92a 100644 --- a/world-service/build.rs +++ b/world-service/build.rs @@ -11,6 +11,6 @@ fn main() { tonic_build::configure() .build_server(false) // Generate gRPC client code .compile_well_known_types(true) - .compile_protos(&["../proto/database.proto", "../proto/auth.proto"], &["../proto"]) + .compile_protos(&["../proto/user_db_api.proto", "../proto/auth.proto"], &["../proto"]) .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); }