diff options
Diffstat (limited to 'crates/profile-service/src')
-rw-r--r-- | crates/profile-service/src/cnfg.rs | 8 | ||||
-rw-r--r-- | crates/profile-service/src/main.rs | 111 | ||||
-rw-r--r-- | crates/profile-service/src/server.rs | 2 | ||||
-rw-r--r-- | crates/profile-service/src/server/interceptor.rs | 11 | ||||
-rw-r--r-- | crates/profile-service/src/server/manager.rs | 45 | ||||
-rw-r--r-- | crates/profile-service/src/state.rs | 42 |
6 files changed, 217 insertions, 2 deletions
diff --git a/crates/profile-service/src/cnfg.rs b/crates/profile-service/src/cnfg.rs new file mode 100644 index 0000000..fec4cf7 --- /dev/null +++ b/crates/profile-service/src/cnfg.rs @@ -0,0 +1,8 @@ +use serde::Deserialize; + +#[derive(Deserialize, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct LocalConfig { + pub temp_ttl: u64, + pub cache_ttl: u64, +} diff --git a/crates/profile-service/src/main.rs b/crates/profile-service/src/main.rs index e7a11a9..5fe1331 100644 --- a/crates/profile-service/src/main.rs +++ b/crates/profile-service/src/main.rs @@ -1,3 +1,110 @@ -fn main() { - println!("Hello, world!"); +mod cnfg; +mod server; +mod state; +use std::net::{Ipv6Addr, SocketAddr}; + +use clap::Parser; +use sellershut_core::profile::profile_server::ProfileServer; +use stack_up::{Configuration, Services, tracing::Tracing}; +use tokio::signal; +use tonic::transport::{Server, server::TcpIncoming}; +use tracing::{error, info}; + +use crate::{ + server::interceptor::MyInterceptor, + state::{AppHandle, AppState}, +}; + +/// sellershut-profiles +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + /// Path to config file + #[arg(short, long)] + config_file: Option<std::path::PathBuf>, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let args = Args::parse(); + let config = include_str!("../profile.toml"); + + let mut config = config::Config::builder() + .add_source(config::File::from_str(config, config::FileFormat::Toml)); + + if let Some(cf) = args.config_file.as_ref().and_then(|v| v.to_str()) { + config = config.add_source(config::File::new(cf, config::FileFormat::Toml)); + }; + + let mut config: Configuration = config.build()?.try_deserialize()?; + config.application.name = env!("CARGO_CRATE_NAME").into(); + config.application.version = env!("CARGO_PKG_VERSION").into(); + + let tracing = Tracing::builder().build(&config.monitoring); + + let mut services = Services::builder() + .postgres(&config.database) + .await + .inspect_err(|e| error!("database: {e}"))? + .cache(&config.cache) + .await + .inspect_err(|e| error!("cache: {e}"))? + .build(); + + let postgres = services + .postgres + .take() + .ok_or_else(|| anyhow::anyhow!("database is not ready"))?; + + let cache = services + .cache + .take() + .ok_or_else(|| anyhow::anyhow!("cache is not ready"))?; + + let services = crate::state::Services { postgres, cache }; + + let state = AppState::create(services, &config).await?; + + let addr = SocketAddr::from((Ipv6Addr::UNSPECIFIED, config.application.port)); + + let listener = tokio::net::TcpListener::bind(addr).await?; + + info!(addr = ?addr, "starting server"); + + Server::builder() + .trace_fn(|_| tracing::info_span!(env!("CARGO_PKG_NAME"))) + // .add_service(QueryUsersServer::new(state.clone())) + .add_service(ProfileServer::with_interceptor( + state.clone(), + MyInterceptor, + )) + .serve_with_incoming_shutdown(TcpIncoming::from(listener), shutdown_signal(state)) + .await?; + + Ok(()) +} +async fn shutdown_signal(state: AppHandle) { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => { + }, + _ = terminate => { + }, + } } diff --git a/crates/profile-service/src/server.rs b/crates/profile-service/src/server.rs new file mode 100644 index 0000000..b2e04f9 --- /dev/null +++ b/crates/profile-service/src/server.rs @@ -0,0 +1,2 @@ +pub mod interceptor; +pub mod manager; diff --git a/crates/profile-service/src/server/interceptor.rs b/crates/profile-service/src/server/interceptor.rs new file mode 100644 index 0000000..6fbe7fa --- /dev/null +++ b/crates/profile-service/src/server/interceptor.rs @@ -0,0 +1,11 @@ +use tonic::{Status, service::Interceptor}; +use tracing::Span; + +#[derive(Clone, Copy)] +pub struct MyInterceptor; + +impl Interceptor for MyInterceptor { + fn call(&mut self, request: tonic::Request<()>) -> Result<tonic::Request<()>, Status> { + Ok(request) + } +} diff --git a/crates/profile-service/src/server/manager.rs b/crates/profile-service/src/server/manager.rs new file mode 100644 index 0000000..bd7e149 --- /dev/null +++ b/crates/profile-service/src/server/manager.rs @@ -0,0 +1,45 @@ +use prost::Message; +use sellershut_core::profile::{ + CompleteUserRequest, CreateUserRequest, CreateUserResponse, User, profile_server::Profile, +}; +use stack_up::redis::AsyncCommands; +use tonic::{Request, Response, Status, async_trait}; +use tracing::trace; +use uuid::Uuid; + +use crate::state::AppHandle; + +#[async_trait] +impl Profile for AppHandle { + #[doc = " Create a new user profile"] + async fn create_user( + &self, + request: Request<CreateUserRequest>, + ) -> Result<Response<CreateUserResponse>, Status> { + trace!("creating user"); + let data = request.into_inner(); + let id = Uuid::now_v7().to_string(); + + let bytes = data.encode_to_vec(); + let mut cache = self + .services + .cache + .get() + .await + .map_err(|e| Status::internal("storage not ready"))?; + cache + .set_ex::<_, _, ()>(&id, &bytes, self.local_config.temp_ttl) + .await + .map_err(|e| Status::internal("storage not ready"))?; + + Ok(Response::new(CreateUserResponse { temp_id: id })) + } + + #[doc = " Complete Profile"] + async fn complete_profile( + &self, + request: Request<CompleteUserRequest>, + ) -> Result<Response<User>, Status> { + todo!() + } +} diff --git a/crates/profile-service/src/state.rs b/crates/profile-service/src/state.rs new file mode 100644 index 0000000..1ccfbfd --- /dev/null +++ b/crates/profile-service/src/state.rs @@ -0,0 +1,42 @@ +use std::sync::Arc; + +use sqlx::PgPool; +use stack_up::{Configuration, cache::RedisManager}; + +use crate::cnfg::LocalConfig; + +#[derive(Clone)] +pub struct AppHandle(Arc<AppState>); + +impl std::ops::Deref for AppHandle { + type Target = Arc<AppState>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Clone)] +pub struct Services { + pub postgres: PgPool, + pub cache: RedisManager, +} + +pub struct AppState { + pub services: Services, + pub local_config: LocalConfig, +} + +impl AppState { + pub async fn create( + services: Services, + configuration: &Configuration, + ) -> Result<AppHandle, anyhow::Error> { + let local_config: LocalConfig = serde_json::from_value(configuration.misc.clone())?; + + Ok(AppHandle(Arc::new(Self { + services, + local_config, + }))) + } +} |