From 3c1f8c40d61ce11c86fe649f0732050266de5338ed46589cce1f1f8d4579ea39 Mon Sep 17 00:00:00 2001 From: raven <7156279+RavenX8@users.noreply.github.com> Date: Fri, 20 Dec 2024 14:43:25 -0500 Subject: [PATCH] - add: session-service - move: redis_cache from database service to utils so it can be reused - update: redis_cache set function to allow creating a key without a lifetime - update: services to use the new get_or_generate_service_id function --- api-service/src/main.rs | 2 +- auth-service/src/main.rs | 2 +- character-service/src/main.rs | 2 +- database-service/src/characters.rs | 2 +- database-service/src/main.rs | 4 +- database-service/src/users.rs | 2 +- packet-service/src/main.rs | 2 +- proto/session_service_api.proto | 36 +++++++ session-service/Cargo.toml | 20 ++++ session-service/Dockerfile | 33 +++++++ session-service/build.rs | 16 ++++ session-service/src/main.rs | 77 +++++++++++++++ session-service/src/session_service.rs | 94 +++++++++++++++++++ utils/src/lib.rs | 1 + .../src/redis_cache.rs | 6 +- world-service/src/main.rs | 2 +- 16 files changed, 291 insertions(+), 10 deletions(-) create mode 100644 proto/session_service_api.proto create mode 100644 session-service/Cargo.toml create mode 100644 session-service/Dockerfile create mode 100644 session-service/build.rs create mode 100644 session-service/src/main.rs create mode 100644 session-service/src/session_service.rs rename {database-service => utils}/src/redis_cache.rs (94%) diff --git a/api-service/src/main.rs b/api-service/src/main.rs index fa646ff..4bebf1b 100644 --- a/api-service/src/main.rs +++ b/api-service/src/main.rs @@ -34,7 +34,7 @@ async fn main() -> Result<(), Box> { let health_check_url = format!("http://{}:{}/health", service_address, health_port); // Register service with Consul - let service_id = consul_registration::generate_service_id(); + let service_id = consul_registration::get_or_generate_service_id(); let tags = vec!["version-1.0".to_string()]; let meta = HashMap::new(); consul_registration::register_service( diff --git a/auth-service/src/main.rs b/auth-service/src/main.rs index 91c5a47..7d82e7e 100644 --- a/auth-service/src/main.rs +++ b/auth-service/src/main.rs @@ -38,7 +38,7 @@ async fn main() -> Result<(), Box> { let db_nodes = get_service_address(&consul_url, "database-service").await?; // Register service with Consul - let service_id = consul_registration::generate_service_id(); + let service_id = consul_registration::get_or_generate_service_id(); let tags = vec!["version-1.0".to_string()]; let mut meta = HashMap::new(); consul_registration::register_service( diff --git a/character-service/src/main.rs b/character-service/src/main.rs index 0776f71..d8aa656 100644 --- a/character-service/src/main.rs +++ b/character-service/src/main.rs @@ -28,7 +28,7 @@ async fn main() -> Result<(), Box> { let db_nodes = get_service_address(&consul_url, "database-service").await?; // Register service with Consul - let service_id = consul_registration::generate_service_id(); + let service_id = consul_registration::get_or_generate_service_id(); let tags = vec!["version-1.0".to_string()]; let mut meta = HashMap::new(); meta.insert("name".to_string(), "Rose".to_string()); diff --git a/database-service/src/characters.rs b/database-service/src/characters.rs index a8e48b4..b1115c0 100644 --- a/database-service/src/characters.rs +++ b/database-service/src/characters.rs @@ -1,5 +1,5 @@ use sqlx::{FromRow, Row}; -use crate::redis_cache::{Cache, RedisCache}; // Import RedisCache +use utils::redis_cache::{Cache, RedisCache}; // Import RedisCache use serde::{Serialize, Deserialize}; use std::sync::Arc; use tokio::sync::Mutex; diff --git a/database-service/src/main.rs b/database-service/src/main.rs index ccf3eb1..56f8a4b 100644 --- a/database-service/src/main.rs +++ b/database-service/src/main.rs @@ -3,7 +3,7 @@ use database_service::db::Database; 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 utils::redis_cache::RedisCache; use dotenv::dotenv; use sqlx::postgres::PgPoolOptions; use std::env; @@ -36,7 +36,7 @@ async fn main() -> Result<(), Box> { let health_check_url = format!("http://{}:{}/health", service_address, health_port); // Register service with Consul - let service_id = consul_registration::generate_service_id(); + let service_id = consul_registration::get_or_generate_service_id(); let tags = vec!["version-1.0".to_string()]; let meta = HashMap::new(); consul_registration::register_service( diff --git a/database-service/src/users.rs b/database-service/src/users.rs index 4aa2c7c..5125d4c 100644 --- a/database-service/src/users.rs +++ b/database-service/src/users.rs @@ -1,5 +1,5 @@ use sqlx::{FromRow, Row}; -use crate::redis_cache::{RedisCache, Cache}; // Import RedisCache and Cache Trait +use utils::redis_cache::{RedisCache, Cache}; // Import RedisCache and Cache Trait use serde::{Serialize, Deserialize}; use std::sync::Arc; use tokio::sync::Mutex; diff --git a/packet-service/src/main.rs b/packet-service/src/main.rs index 8991145..da501a1 100644 --- a/packet-service/src/main.rs +++ b/packet-service/src/main.rs @@ -85,7 +85,7 @@ async fn main() -> Result<(), Box> { let auth_node = get_service_address(&consul_url, "auth-service").await?; // Register service with Consul - let service_id = consul_registration::generate_service_id(); + let service_id = consul_registration::get_or_generate_service_id(); let tags = vec!["version-1.0".to_string()]; let mut meta = HashMap::new(); consul_registration::register_service( diff --git a/proto/session_service_api.proto b/proto/session_service_api.proto new file mode 100644 index 0000000..21a2fbd --- /dev/null +++ b/proto/session_service_api.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; + +package session_service_api; + +service SessionService { + rpc CreateSession (CreateSessionRequest) returns (SessionResponse); + rpc GetSession (GetSessionRequest) returns (SessionResponse); + rpc DeleteSession (DeleteSessionRequest) returns (Empty); +} + +message CreateSessionRequest { + string session_id = 1; + int32 user_id = 2; + string username = 3; + int32 character_id = 4; + string ip_address = 5; +} + +message GetSessionRequest { + string session_id = 1; +} + +message DeleteSessionRequest { + string session_id = 1; +} + +message SessionResponse { + string session_id = 1; + int32 user_id = 2; + string username = 3; + int32 character_id = 4; + string login_time = 5; + string ip_address = 6; +} + +message Empty {} diff --git a/session-service/Cargo.toml b/session-service/Cargo.toml new file mode 100644 index 0000000..f63417c --- /dev/null +++ b/session-service/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "session-service" +version = "0.1.0" +edition = "2021" + +[dependencies] +utils = { path = "../utils" } +dotenv = "0.15" +tokio = { version = "1.41.1", features = ["full"] } +serde = { version = "1.0", features = ["derive"] } +tracing = "0.1" +tracing-subscriber = "0.3.18" +tonic = "0.12.3" +prost = "0.13.4" +warp = "0.3.7" +chrono = "0.4.39" +serde_json = "1.0.133" + +[build-dependencies] +tonic-build = "0.12.3" \ No newline at end of file diff --git a/session-service/Dockerfile b/session-service/Dockerfile new file mode 100644 index 0000000..ca3800f --- /dev/null +++ b/session-service/Dockerfile @@ -0,0 +1,33 @@ +FROM rust:1.83 AS builder +LABEL authors="raven" + +WORKDIR /usr/src/proto +COPY ./proto . + +WORKDIR /usr/src/utils +COPY ./utils . + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive \ + apt-get install --no-install-recommends --assume-yes \ + protobuf-compiler + +WORKDIR /usr/src/session-service +COPY ./session-service . + +RUN cargo build --release + +FROM debian:bookworm-slim + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive \ + apt-get install --no-install-recommends --assume-yes \ + pkg-config \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /usr/src/session-service/target/release/session-service /usr/local/bin/session-service + +EXPOSE 50055 + +CMD ["session-service"] \ No newline at end of file diff --git a/session-service/build.rs b/session-service/build.rs new file mode 100644 index 0000000..8bd24e7 --- /dev/null +++ b/session-service/build.rs @@ -0,0 +1,16 @@ +fn main() { + // gRPC Server code + tonic_build::configure() + .build_server(true) // Generate gRPC server code + .compile_well_known_types(true) + .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]") + .compile_protos(&["../proto/session_service_api.proto"], &["../proto"]) + .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); + + // gRPC Client code + tonic_build::configure() + .build_server(false) + .compile_well_known_types(true) + .compile_protos(&["../proto/user_db_api.proto", "../proto/auth.proto"], &["../proto"]) + .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); +} diff --git a/session-service/src/main.rs b/session-service/src/main.rs new file mode 100644 index 0000000..f517d02 --- /dev/null +++ b/session-service/src/main.rs @@ -0,0 +1,77 @@ +mod session_service; + +use dotenv::dotenv; +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::Level; +use utils::consul_registration; +use utils::redis_cache::RedisCache; +use utils::service_discovery::get_service_address; +use crate::api::session_service_server::SessionServiceServer; +use crate::session_service::SessionServiceImpl; + +pub mod api { + tonic::include_proto!("session_service_api"); +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + 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)) + .init(); + + // Set the gRPC server address + let addr = env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0".to_string()); + let port = env::var("SESSION_SERVICE_PORT").unwrap_or_else(|_| "50055".to_string()); + let health_port = env::var("HEALTH_CHECK_PORT").unwrap_or_else(|_| "8084".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(|_| "session-service".to_string()); + let service_address = env::var("SESSION_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(); + let tags = vec!["version-1.0".to_string()]; + let meta = HashMap::new(); + consul_registration::register_service( + &consul_url, + service_id.as_str(), + service_name.as_str(), + service_address.as_str(), + service_port.parse().unwrap_or(50055), + tags, + meta, + &health_check_url, + ) + .await?; + + // Start health-check endpoint + consul_registration::start_health_check(addr.as_str()).await?; + + let full_addr = format!("{}:{}", &addr, port); + let address = full_addr.parse().expect("Invalid address"); + let redis_cache = Arc::new(Mutex::new(RedisCache::new(&redis_url))); + let session_service = SessionServiceImpl { + redis: redis_cache + }; + + tokio::spawn(Server::builder() + .add_service(SessionServiceServer::new(session_service)) + .serve(address)); + + select! { + _ = signal::ctrl_c() => {}, + } + + consul_registration::deregister_service(&consul_url, service_id.as_str()).await.expect(""); + Ok(()) +} diff --git a/session-service/src/session_service.rs b/session-service/src/session_service.rs new file mode 100644 index 0000000..0570172 --- /dev/null +++ b/session-service/src/session_service.rs @@ -0,0 +1,94 @@ +use std::sync::Arc; +use tokio::sync::Mutex; +use tonic::{Request, Response, Status}; +use serde::{Serialize, Deserialize}; +use crate::api::{ + CreateSessionRequest, SessionResponse, GetSessionRequest, DeleteSessionRequest, Empty, +}; +use crate::api::session_service_server::SessionService; +use utils::redis_cache::{Cache, RedisCache}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Session { + pub user_id: i32, + pub username: String, + pub character_id: i32, + pub login_time: String, + pub ip_address: String, +} + +pub struct SessionServiceImpl { + pub redis: Arc>, +} + +#[tonic::async_trait] +impl SessionService for SessionServiceImpl { + async fn create_session( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + + let session = Session { + user_id: req.user_id, + username: req.username, + character_id: req.character_id, + login_time: chrono::Utc::now().to_rfc3339(), + ip_address: req.ip_address, + }; + + let session_id = req.session_id; + let session_data = serde_json::to_string(&session).map_err(|_| Status::internal("Failed to serialize session"))?; + + let conn = self.redis.lock().await; + conn.set(&session_id.clone(), &session_data, 0).await.map_err(|_| Status::internal("Failed to store session in Redis"))?; + + let response = SessionResponse { + session_id, + user_id: session.user_id, + username: session.username, + character_id: session.character_id, + login_time: session.login_time, + ip_address: session.ip_address, + }; + + Ok(Response::new(response)) + } + + async fn get_session( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let conn = self.redis.lock().await; + + if let Some(session_data) = conn.get::(&req.session_id).await.map_err(|_| Status::internal("Failed to fetch session from Redis"))? { + let session: Session = serde_json::from_str(&session_data).map_err(|_| Status::internal("Failed to deserialize session"))?; + + let response = SessionResponse { + session_id: req.session_id, + user_id: session.user_id, + username: session.username, + character_id: session.character_id, + login_time: session.login_time, + ip_address: session.ip_address, + }; + + Ok(Response::new(response)) + } else { + Err(Status::not_found("Session not found")) + } + } + + async fn delete_session( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let mut conn = self.redis.lock().await; + + conn.delete(&req.session_id).await.map_err(|_| Status::internal("Failed to delete session from Redis"))?; + + Ok(Response::new(Empty {})) + } +} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index fecaa68..1aa4859 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -1,3 +1,4 @@ pub mod consul_registration; pub mod service_discovery; pub mod null_string; +pub mod redis_cache; diff --git a/database-service/src/redis_cache.rs b/utils/src/redis_cache.rs similarity index 94% rename from database-service/src/redis_cache.rs rename to utils/src/redis_cache.rs index c2855c1..22824c0 100644 --- a/database-service/src/redis_cache.rs +++ b/utils/src/redis_cache.rs @@ -54,7 +54,11 @@ impl Cache for RedisCache { "Serialization error", format!("Serialization error: {}", err), )))?; - conn.set_ex(key, serialized_value, ttl).await + if ttl > 0 { + conn.set_ex(key, serialized_value, ttl).await + } else { + conn.set(key, serialized_value).await + } } async fn get Deserialize<'de> + Send + Sync>(&self, key: &String) -> Result, redis::RedisError> { diff --git a/world-service/src/main.rs b/world-service/src/main.rs index bd184df..09a5d04 100644 --- a/world-service/src/main.rs +++ b/world-service/src/main.rs @@ -28,7 +28,7 @@ async fn main() -> Result<(), Box> { let db_nodes = get_service_address(&consul_url, "database-service").await?; // Register service with Consul - let service_id = consul_registration::generate_service_id(); + let service_id = consul_registration::get_or_generate_service_id(); let tags = vec!["version-1.0".to_string()]; let mut meta = HashMap::new(); meta.insert("name".to_string(), "Athena".to_string());