From beffee8f8e216f5cdc8ddf82c8a69d01e5a2a201 Mon Sep 17 00:00:00 2001 From: SquidSpirit Date: Wed, 6 Aug 2025 18:17:44 +0800 Subject: [PATCH] BLOG-90 feat: add common feature and improve error handling across services --- backend/Cargo.lock | 10 ++++++++ backend/Cargo.toml | 13 ++++++++++- backend/feature/auth/Cargo.toml | 2 ++ .../src/framework/db/user_db_service_impl.rs | 7 +++--- .../framework/oidc/auth_oidc_service_impl.rs | 2 +- backend/feature/common/Cargo.toml | 7 ++++++ backend/feature/common/src/framework.rs | 1 + backend/feature/common/src/framework/error.rs | 21 +++++++++++++++++ backend/feature/common/src/lib.rs | 1 + backend/feature/image/Cargo.toml | 1 + .../src/framework/db/image_db_service_impl.rs | 5 ++-- .../framework/storage/image_storage_impl.rs | 9 +++++--- backend/feature/post/Cargo.toml | 1 + .../src/framework/db/label_db_service_impl.rs | 9 ++++---- .../src/framework/db/post_db_service_impl.rs | 23 ++++++++++--------- backend/server/src/configuration/sentry.rs | 1 + 16 files changed, 88 insertions(+), 25 deletions(-) create mode 100644 backend/feature/common/Cargo.toml create mode 100644 backend/feature/common/src/framework.rs create mode 100644 backend/feature/common/src/framework/error.rs create mode 100644 backend/feature/common/src/lib.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index de4590d..0b6c222 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -426,6 +426,7 @@ dependencies = [ "actix-web", "anyhow", "async-trait", + "common", "log", "openidconnect", "sentry", @@ -619,6 +620,13 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "common" +version = "0.2.0" +dependencies = [ + "sqlx", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1743,6 +1751,7 @@ dependencies = [ "anyhow", "async-trait", "auth", + "common", "futures", "log", "sentry", @@ -2399,6 +2408,7 @@ dependencies = [ "async-trait", "auth", "chrono", + "common", "log", "sentry", "serde", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index fd697c9..8940323 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,11 +1,21 @@ [workspace] -members = ["server", "feature/auth", "feature/image", "feature/post"] +members = [ + "server", + "feature/auth", + "feature/common", + "feature/image", + "feature/post", + "feature/common", +] resolver = "2" [workspace.package] version = "0.2.0" edition = "2024" +[profile.release] +debug = true + [workspace.dependencies] actix-multipart = "0.7.2" actix-session = { version = "0.10.1", features = ["redis-session"] } @@ -41,5 +51,6 @@ utoipa-redoc = { version = "6.0.0", features = ["actix-web"] } server.path = "server" auth.path = "feature/auth" +common.path = "feature/common" image.path = "feature/image" post.path = "feature/post" diff --git a/backend/feature/auth/Cargo.toml b/backend/feature/auth/Cargo.toml index cd238bb..248b4ff 100644 --- a/backend/feature/auth/Cargo.toml +++ b/backend/feature/auth/Cargo.toml @@ -14,3 +14,5 @@ sentry.workspace = true serde.workspace = true sqlx.workspace = true utoipa.workspace = true + +common.workspace = true diff --git a/backend/feature/auth/src/framework/db/user_db_service_impl.rs b/backend/feature/auth/src/framework/db/user_db_service_impl.rs index d4d911d..f7752fe 100644 --- a/backend/feature/auth/src/framework/db/user_db_service_impl.rs +++ b/backend/feature/auth/src/framework/db/user_db_service_impl.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use common::framework::error::DatabaseError; use sqlx::{Pool, Postgres}; use crate::{ @@ -31,7 +32,7 @@ impl UserDbService for UserDbServiceImpl { ) .fetch_optional(&self.db_pool) .await - .map_err(|e| AuthError::Unexpected(e.into()))?; + .map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?; match record { Some(record) => Ok(record.into_mapper()), @@ -56,7 +57,7 @@ impl UserDbService for UserDbServiceImpl { ) .fetch_optional(&self.db_pool) .await - .map_err(|e| AuthError::Unexpected(e.into()))?; + .map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?; match record { Some(record) => Ok(record.into_mapper()), @@ -78,7 +79,7 @@ impl UserDbService for UserDbServiceImpl { ) .fetch_one(&self.db_pool) .await - .map_err(|e| AuthError::Unexpected(e.into()))?; + .map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?; Ok(id) } diff --git a/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs b/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs index 037fc0c..122ac04 100644 --- a/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs +++ b/backend/feature/auth/src/framework/oidc/auth_oidc_service_impl.rs @@ -80,7 +80,7 @@ impl AuthOidcService for AuthOidcServiceImpl { let token_response = self .oidc_client .exchange_code(AuthorizationCode::new(code.to_string())) - .map_err(|e| AuthError::Unexpected(e.into()))? + .unwrap() .request_async(&self.http_client) .await .map_err(|_| AuthError::InvalidAuthCode)?; diff --git a/backend/feature/common/Cargo.toml b/backend/feature/common/Cargo.toml new file mode 100644 index 0000000..dee37f8 --- /dev/null +++ b/backend/feature/common/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "common" +version.workspace = true +edition.workspace = true + +[dependencies] +sqlx.workspace = true diff --git a/backend/feature/common/src/framework.rs b/backend/feature/common/src/framework.rs new file mode 100644 index 0000000..a281f3e --- /dev/null +++ b/backend/feature/common/src/framework.rs @@ -0,0 +1 @@ +pub mod error; \ No newline at end of file diff --git a/backend/feature/common/src/framework/error.rs b/backend/feature/common/src/framework/error.rs new file mode 100644 index 0000000..b1b7f15 --- /dev/null +++ b/backend/feature/common/src/framework/error.rs @@ -0,0 +1,21 @@ +use std::fmt::Display; + +#[derive(Debug)] +pub struct IOError(pub std::io::Error); + +#[derive(Debug)] +pub struct DatabaseError(pub sqlx::Error); + +impl std::error::Error for IOError {} +impl Display for IOError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl std::error::Error for DatabaseError {} +impl Display for DatabaseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/backend/feature/common/src/lib.rs b/backend/feature/common/src/lib.rs new file mode 100644 index 0000000..fa86c78 --- /dev/null +++ b/backend/feature/common/src/lib.rs @@ -0,0 +1 @@ +pub mod framework; \ No newline at end of file diff --git a/backend/feature/image/Cargo.toml b/backend/feature/image/Cargo.toml index 0e1d8dd..1ec81b9 100644 --- a/backend/feature/image/Cargo.toml +++ b/backend/feature/image/Cargo.toml @@ -16,3 +16,4 @@ sqlx.workspace = true utoipa.workspace = true auth.workspace = true +common.workspace = true diff --git a/backend/feature/image/src/framework/db/image_db_service_impl.rs b/backend/feature/image/src/framework/db/image_db_service_impl.rs index 37fa99f..453c441 100644 --- a/backend/feature/image/src/framework/db/image_db_service_impl.rs +++ b/backend/feature/image/src/framework/db/image_db_service_impl.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use common::framework::error::DatabaseError; use sqlx::{Pool, Postgres}; use crate::{ @@ -34,7 +35,7 @@ impl ImageDbService for ImageDbServiceImpl { match id { Ok(id) => Ok(id), - Err(e) => Err(ImageError::Unexpected(anyhow::Error::from(e))), + Err(e) => Err(ImageError::Unexpected(DatabaseError(e).into())), } } @@ -59,7 +60,7 @@ impl ImageDbService for ImageDbServiceImpl { }), None => Err(ImageError::NotFound), }, - Err(e) => Err(ImageError::Unexpected(anyhow::Error::from(e))), + Err(e) => Err(ImageError::Unexpected(DatabaseError(e).into())), } } } diff --git a/backend/feature/image/src/framework/storage/image_storage_impl.rs b/backend/feature/image/src/framework/storage/image_storage_impl.rs index 62b5ac1..cf6a5a7 100644 --- a/backend/feature/image/src/framework/storage/image_storage_impl.rs +++ b/backend/feature/image/src/framework/storage/image_storage_impl.rs @@ -3,6 +3,8 @@ use std::{ io::Write, }; +use common::framework::error::IOError; + use crate::{ adapter::gateway::image_storage::ImageStorage, application::error::image_error::ImageError, }; @@ -22,10 +24,11 @@ impl ImageStorageImpl { impl ImageStorage for ImageStorageImpl { fn write_data(&self, id: i32, data: &[u8]) -> Result<(), ImageError> { let dir_path = format!("{}/images", self.sotrage_path); - fs::create_dir_all(&dir_path).map_err(|e| ImageError::Unexpected(e.into()))?; + fs::create_dir_all(&dir_path).map_err(|e| ImageError::Unexpected(IOError(e).into()))?; let file_path = format!("{}/{}", dir_path, id); - let mut file = File::create(&file_path).map_err(|e| ImageError::Unexpected(e.into()))?; + let mut file = + File::create(&file_path).map_err(|e| ImageError::Unexpected(IOError(e).into()))?; file.write_all(data) .map_err(|e| ImageError::Unexpected(e.into()))?; @@ -34,7 +37,7 @@ impl ImageStorage for ImageStorageImpl { fn read_data(&self, id: i32) -> Result, ImageError> { let file_path = format!("{}/images/{}", self.sotrage_path, id); - let data = fs::read(&file_path).map_err(|e| ImageError::Unexpected(e.into()))?; + let data = fs::read(&file_path).map_err(|e| ImageError::Unexpected(IOError(e).into()))?; Ok(data) } } diff --git a/backend/feature/post/Cargo.toml b/backend/feature/post/Cargo.toml index f7789c5..2903619 100644 --- a/backend/feature/post/Cargo.toml +++ b/backend/feature/post/Cargo.toml @@ -15,3 +15,4 @@ sqlx.workspace = true utoipa.workspace = true auth.workspace = true +common.workspace = true diff --git a/backend/feature/post/src/framework/db/label_db_service_impl.rs b/backend/feature/post/src/framework/db/label_db_service_impl.rs index ca7e1b1..f189fd1 100644 --- a/backend/feature/post/src/framework/db/label_db_service_impl.rs +++ b/backend/feature/post/src/framework/db/label_db_service_impl.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use common::framework::error::DatabaseError; use sqlx::{Pool, Postgres}; use crate::{ @@ -31,7 +32,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .fetch_one(&self.db_pool) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; Ok(id) } @@ -49,7 +50,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .execute(&self.db_pool) .await - .map_err(|e| PostError::Unexpected(e.into()))? + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))? .rows_affected(); if affected_rows == 0 { @@ -71,7 +72,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .fetch_optional(&self.db_pool) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; match record { Some(record) => Ok(record.into_mapper()), @@ -91,7 +92,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .fetch_all(&self.db_pool) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; let mappers = records .into_iter() diff --git a/backend/feature/post/src/framework/db/post_db_service_impl.rs b/backend/feature/post/src/framework/db/post_db_service_impl.rs index 1a60019..94d2de8 100644 --- a/backend/feature/post/src/framework/db/post_db_service_impl.rs +++ b/backend/feature/post/src/framework/db/post_db_service_impl.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use async_trait::async_trait; +use common::framework::error::DatabaseError; use sqlx::{Pool, Postgres}; use crate::{ @@ -64,7 +65,7 @@ impl PostDbService for PostDbServiceImpl { .build_query_as::() .fetch_all(&self.db_pool) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; let mut post_info_mappers_map = HashMap::::new(); @@ -136,7 +137,7 @@ impl PostDbService for PostDbServiceImpl { .build_query_as::() .fetch_all(&self.db_pool) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; if records.is_empty() { return Err(PostError::NotFound); @@ -188,7 +189,7 @@ impl PostDbService for PostDbServiceImpl { .db_pool .begin() .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; let post_id = sqlx::query_scalar!( r#" @@ -205,7 +206,7 @@ impl PostDbService for PostDbServiceImpl { ) .fetch_one(&mut *tx) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; for (order, &label_id) in label_ids.iter().enumerate() { sqlx::query!( @@ -221,12 +222,12 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; } tx.commit() .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; Ok(post_id) } @@ -236,7 +237,7 @@ impl PostDbService for PostDbServiceImpl { .db_pool .begin() .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; let affected_rows = sqlx::query!( r#" @@ -258,7 +259,7 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|e| PostError::Unexpected(e.into()))? + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))? .rows_affected(); if affected_rows == 0 { @@ -274,7 +275,7 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; for (order, &label_id) in label_ids.iter().enumerate() { sqlx::query!( @@ -290,12 +291,12 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; } tx.commit() .await - .map_err(|e| PostError::Unexpected(e.into()))?; + .map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?; Ok(()) } diff --git a/backend/server/src/configuration/sentry.rs b/backend/server/src/configuration/sentry.rs index e9df39c..ec4bcbe 100644 --- a/backend/server/src/configuration/sentry.rs +++ b/backend/server/src/configuration/sentry.rs @@ -15,6 +15,7 @@ impl SentryConfiguration { traces_sample_rate: 1.0, send_default_pii: true, max_request_body_size: sentry::MaxRequestBodySize::Always, + attach_stacktrace: true, ..Default::default() }, }