use prost::Message; use sellershut_core::users::{ CompleteUserRequest, CreateUserRequest, CreateUserResponse, User, UserType, users_service_server::UsersService, }; use stack_up::redis::AsyncCommands; use time::OffsetDateTime; use tonic::{Request, Response, Status, async_trait}; use tracing::{error, trace}; use uuid::Uuid; use crate::state::AppHandle; struct DbUser { id: String, username: String, inbox: String, outbox: String, email: Option, avatar: Option, created_at: OffsetDateTime, updated_at: OffsetDateTime, description: Option, user_type: String, public_key: String, local: bool, } impl TryFrom for User { type Error = Status; fn try_from(value: DbUser) -> Result { let user_type = UserType::from_str_name(&value.user_type) .ok_or_else(|| Status::internal("invalid user type"))? .into(); Ok(Self { id: value.id, email: value.email, username: value.username, avatar: value.avatar, created_at: Some(value.created_at.into()), updated_at: Some(value.updated_at.into()), description: value.description, user_type, public_key: value.public_key, inbox: value.inbox, outbox: value.outbox, local: value.local, }) } } #[async_trait] impl UsersService for AppHandle { #[doc = " Create a new user profile"] async fn create_user( &self, request: Request, ) -> Result, 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.cache().await?; cache .set_ex::<_, _, ()>(&id, &bytes, self.local_config.temp_ttl) .await .inspect_err(|e| error!("{e}")) .map_err(|_e| Status::internal("storage not ready"))?; Ok(Response::new(CreateUserResponse { temp_id: id })) } #[doc = " Complete Profile"] async fn complete_user( &self, request: Request, ) -> Result, Status> { let request = request.into_inner(); let mut cache = self.cache().await?; let resp = cache .get_del::<_, Vec>(&request.id) .await .inspect_err(|e| error!("{e}")) .map_err(|_e| Status::internal("storage not ready"))?; if resp.is_empty() { return Err(Status::data_loss("user unavailable")); } let create_user = CreateUserRequest::decode(resp.as_ref()) .map_err(|_e| Status::data_loss("internal data corrupted"))?; let user = sqlx::query_as!( DbUser, "insert into profile ( id, username, inbox, outbox, local, avatar, description, user_type, public_key, email ) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning * ", request.id, request.username, request.inbox, request.outbox, request.local, request.avatar.or(create_user.avatar), request.description, request.user_type.to_string(), request.public_key, create_user.email, ) .fetch_one(&self.services.postgres) .await .map_err(|_e| Status::internal("storage error"))?; let user = User::try_from(user)?; Ok(Response::new(user)) } }