diff --git a/backend/Cargo.lock b/backend/Cargo.lock index c367e74..de4590d 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -424,9 +424,11 @@ version = "0.2.0" dependencies = [ "actix-session", "actix-web", + "anyhow", "async-trait", "log", "openidconnect", + "sentry", "serde", "sqlx", "utoipa", @@ -656,6 +658,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -804,6 +816,16 @@ dependencies = [ "syn", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + [[package]] name = "der" version = "0.7.10" @@ -1081,6 +1103,18 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + [[package]] name = "flate2" version = "1.1.1" @@ -1114,6 +1148,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1372,6 +1421,17 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "hostname" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" +dependencies = [ + "cfg-if", + "libc", + "windows-link", +] + [[package]] name = "http" version = "0.2.12" @@ -1465,6 +1525,22 @@ dependencies = [ "webpki-roots 1.0.2", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.16" @@ -1664,10 +1740,12 @@ version = "0.2.0" dependencies = [ "actix-multipart", "actix-web", + "anyhow", "async-trait", "auth", "futures", "log", + "sentry", "serde", "sqlx", "utoipa", @@ -1922,6 +2000,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2057,6 +2152,50 @@ dependencies = [ "url", ] +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-float" version = "2.10.1" @@ -2066,6 +2205,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "os_info" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" +dependencies = [ + "log", + "plist", + "serde", + "windows-sys 0.52.0", +] + [[package]] name = "p256" version = "0.13.2" @@ -2199,6 +2350,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plist" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" +dependencies = [ + "base64 0.22.1", + "indexmap 2.9.0", + "quick-xml", + "serde", + "time", +] + [[package]] name = "polyval" version = "0.6.2" @@ -2231,10 +2395,12 @@ name = "post" version = "0.2.0" dependencies = [ "actix-web", + "anyhow", "async-trait", "auth", "chrono", "log", + "sentry", "serde", "sqlx", "utoipa", @@ -2273,6 +2439,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.38.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" version = "0.11.8" @@ -2505,9 +2680,11 @@ dependencies = [ "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -2518,6 +2695,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls", "tower", "tower-http", @@ -2621,6 +2799,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.11.0" @@ -2653,6 +2840,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "schemars" version = "0.9.0" @@ -2697,12 +2893,168 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +[[package]] +name = "sentry" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989425268ab5c011e06400187eed6c298272f8ef913e49fcadc3fda788b45030" +dependencies = [ + "httpdate", + "native-tls", + "reqwest", + "sentry-actix", + "sentry-anyhow", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-debug-images", + "sentry-panic", + "sentry-tracing", + "tokio", + "ureq", +] + +[[package]] +name = "sentry-actix" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5c675bdf6118764a8e265c3395c311b4d905d12866c92df52870c0223d2ffc1" +dependencies = [ + "actix-http", + "actix-web", + "bytes", + "futures-util", + "sentry-core", +] + +[[package]] +name = "sentry-anyhow" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b4523c2595d6730bfbe401e95a6423fe9cb16dc3b6046f340551591cffe723" +dependencies = [ + "anyhow", + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-backtrace" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68e299dd3f7bcf676875eee852c9941e1d08278a743c32ca528e2debf846a653" +dependencies = [ + "backtrace", + "regex", + "sentry-core", +] + +[[package]] +name = "sentry-contexts" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac0c5d6892cd4c414492fc957477b620026fb3411fca9fa12774831da561c88" +dependencies = [ + "hostname", + "libc", + "os_info", + "rustc_version", + "sentry-core", + "uname", +] + +[[package]] +name = "sentry-core" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deaa38b94e70820ff3f1f9db3c8b0aef053b667be130f618e615e0ff2492cbcc" +dependencies = [ + "rand 0.9.1", + "sentry-types", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "sentry-debug-images" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00950648aa0d371c7f57057434ad5671bd4c106390df7e7284739330786a01b6" +dependencies = [ + "findshlibs", + "sentry-core", +] + +[[package]] +name = "sentry-panic" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b7a23b13c004873de3ce7db86eb0f59fe4adfc655a31f7bbc17fd10bacc9bfe" +dependencies = [ + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-tracing" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac841c7050aa73fc2bec8f7d8e9cb1159af0b3095757b99820823f3e54e5080" +dependencies = [ + "bitflags", + "sentry-backtrace", + "sentry-core", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sentry-types" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e477f4d4db08ddb4ab553717a8d3a511bc9e81dde0c808c680feacbb8105c412" +dependencies = [ + "debugid", + "hex", + "rand 0.9.1", + "serde", + "serde_json", + "thiserror 2.0.12", + "time", + "url", + "uuid", +] + [[package]] name = "serde" version = "1.0.219" @@ -2822,6 +3174,7 @@ dependencies = [ "openidconnect", "percent-encoding", "post", + "sentry", "sqlx", "utoipa", "utoipa-redoc", @@ -3302,6 +3655,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-retry" version = "0.3.0" @@ -3422,6 +3785,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "tracing-core", ] [[package]] @@ -3436,6 +3809,15 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +[[package]] +name = "uname" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8" +dependencies = [ + "libc", +] + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -3485,6 +3867,36 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0fde9bc91026e381155f8c67cb354bcd35260b2f4a29bcc84639f762760c39" +dependencies = [ + "base64 0.22.1", + "der", + "log", + "native-tls", + "percent-encoding", + "rustls-pemfile", + "rustls-pki-types", + "ureq-proto", + "utf-8", + "webpki-root-certs 0.26.11", +] + +[[package]] +name = "ureq-proto" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59db78ad1923f2b1be62b6da81fe80b173605ca0d57f85da2e005382adf693f7" +dependencies = [ + "base64 0.22.1", + "http 1.3.1", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.4" @@ -3497,6 +3909,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf16_iter" version = "1.0.5" @@ -3552,6 +3970,23 @@ dependencies = [ "utoipa", ] +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcpkg" version = "0.2.15" @@ -3685,6 +4120,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" +dependencies = [ + "webpki-root-certs 1.0.2", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4ffd8df1c57e87c325000a3d6ef93db75279dc3a231125aac571650f22b12a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.26.10" @@ -3713,6 +4166,28 @@ dependencies = [ "wasite", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.61.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index e6cd1b2..fd697c9 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -10,6 +10,7 @@ edition = "2024" actix-multipart = "0.7.2" actix-session = { version = "0.10.1", features = ["redis-session"] } actix-web = "4.10.2" +anyhow = "1.0.98" async-trait = "0.1.88" chrono = "0.4.41" dotenv = "0.15.0" @@ -22,6 +23,7 @@ openidconnect = { version = "4.0.1", features = [ "reqwest-blocking", ] } percent-encoding = "2.3.1" +sentry = { version = "0.42.0", features = ["actix", "anyhow"] } serde = { version = "1.0.219", features = ["derive"] } sqlx = { version = "0.8.5", features = [ "chrono", diff --git a/backend/Dockerfile b/backend/Dockerfile index 2b90549..1ea55b8 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -13,6 +13,7 @@ COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/server . EXPOSE 8080 VOLUME ["/app/static"] ENV RUST_LOG=info +ENV RUST_BACKTRACE=1 ENV HOST=0.0.0.0 ENV PORT=8080 ENV STORAGE_PATH=/app/static @@ -27,5 +28,6 @@ ENV OIDC_ISSUER_URL= ENV OIDC_REDIRECT_URL= ENV OIDC_CLIENT_ID= ENV OIDC_CLIENT_SECRET= +ENV SENTRY_DSN= CMD ["./server"] diff --git a/backend/feature/auth/Cargo.toml b/backend/feature/auth/Cargo.toml index f820b30..cd238bb 100644 --- a/backend/feature/auth/Cargo.toml +++ b/backend/feature/auth/Cargo.toml @@ -6,9 +6,11 @@ edition.workspace = true [dependencies] actix-session.workspace = true actix-web.workspace = true +anyhow.workspace = true async-trait.workspace = true log.workspace = true openidconnect.workspace = true +sentry.workspace = true serde.workspace = true sqlx.workspace = true utoipa.workspace = true diff --git a/backend/feature/auth/src/application/error/auth_error.rs b/backend/feature/auth/src/application/error/auth_error.rs index 9fa2375..d72e6b1 100644 --- a/backend/feature/auth/src/application/error/auth_error.rs +++ b/backend/feature/auth/src/application/error/auth_error.rs @@ -1,10 +1,24 @@ -#[derive(Debug, PartialEq)] +use std::fmt::Display; + +#[derive(Debug)] pub enum AuthError { - DatabaseError(String), - OidcError(String), InvalidState, InvalidNonce, InvalidAuthCode, InvalidIdToken, UserNotFound, + Unexpected(anyhow::Error), +} + +impl Display for AuthError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AuthError::InvalidState => write!(f, "Invalid state"), + AuthError::InvalidNonce => write!(f, "Invalid nonce"), + AuthError::InvalidAuthCode => write!(f, "Invalid authentication code"), + AuthError::InvalidIdToken => write!(f, "Invalid ID token"), + AuthError::UserNotFound => write!(f, "User not found"), + AuthError::Unexpected(e) => write!(f, "Unexpected error: {}", e), + } + } } 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 c007d3d..d4d911d 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 @@ -31,7 +31,7 @@ impl UserDbService for UserDbServiceImpl { ) .fetch_optional(&self.db_pool) .await - .map_err(|e| AuthError::DatabaseError(e.to_string()))?; + .map_err(|e| AuthError::Unexpected(e.into()))?; match record { Some(record) => Ok(record.into_mapper()), @@ -56,7 +56,7 @@ impl UserDbService for UserDbServiceImpl { ) .fetch_optional(&self.db_pool) .await - .map_err(|e| AuthError::DatabaseError(e.to_string()))?; + .map_err(|e| AuthError::Unexpected(e.into()))?; match record { Some(record) => Ok(record.into_mapper()), @@ -78,7 +78,7 @@ impl UserDbService for UserDbServiceImpl { ) .fetch_one(&self.db_pool) .await - .map_err(|e| AuthError::DatabaseError(e.to_string()))?; + .map_err(|e| AuthError::Unexpected(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 d4a85dd..037fc0c 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::OidcError(e.to_string()))? + .map_err(|e| AuthError::Unexpected(e.into()))? .request_async(&self.http_client) .await .map_err(|_| AuthError::InvalidAuthCode)?; diff --git a/backend/feature/auth/src/framework/web/auth_middleware.rs b/backend/feature/auth/src/framework/web/auth_middleware.rs index c1ff8d0..4abe167 100644 --- a/backend/feature/auth/src/framework/web/auth_middleware.rs +++ b/backend/feature/auth/src/framework/web/auth_middleware.rs @@ -1,7 +1,10 @@ -use std::future::{self, Ready}; +use std::{ + fmt::Display, + future::{self, Ready}, +}; use actix_session::SessionExt; -use actix_web::{Error, FromRequest, HttpRequest, dev::Payload, error::ErrorUnauthorized}; +use actix_web::{FromRequest, HttpRequest, dev::Payload}; use crate::framework::web::constants::SESSION_KEY_USER_ID; @@ -14,20 +17,39 @@ impl UserId { } impl FromRequest for UserId { - type Error = Error; - type Future = Ready>; + type Error = UnauthorizedError; + type Future = Ready>; fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let user_id_result = req.get_session().get::(SESSION_KEY_USER_ID); let user_id = match user_id_result { Ok(id) => id, - _ => return future::ready(Err(ErrorUnauthorized(""))), + _ => return future::ready(Err(UnauthorizedError)), }; match user_id { Some(id) => future::ready(Ok(UserId(id))), - None => future::ready(Err(ErrorUnauthorized(""))), + None => future::ready(Err(UnauthorizedError)), } } } + +#[derive(Debug)] +pub struct UnauthorizedError; + +impl Display for UnauthorizedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Unauthorized access") + } +} + +impl actix_web::ResponseError for UnauthorizedError { + fn status_code(&self) -> actix_web::http::StatusCode { + actix_web::http::StatusCode::UNAUTHORIZED + } + + fn error_response(&self) -> actix_web::HttpResponse { + actix_web::HttpResponse::Unauthorized().finish() + } +} diff --git a/backend/feature/auth/src/framework/web/get_logged_in_user_handler.rs b/backend/feature/auth/src/framework/web/get_logged_in_user_handler.rs index a1415f8..020bd0c 100644 --- a/backend/feature/auth/src/framework/web/get_logged_in_user_handler.rs +++ b/backend/feature/auth/src/framework/web/get_logged_in_user_handler.rs @@ -1,7 +1,10 @@ use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; +use sentry::integrations::anyhow::capture_anyhow; use crate::{ adapter::delivery::{auth_controller::AuthController, user_response_dto::UserResponseDto}, + application::error::auth_error::AuthError, framework::web::auth_middleware::UserId, }; @@ -26,7 +29,10 @@ pub async fn get_logged_in_user_handler( match result { Ok(user) => HttpResponse::Ok().json(user), Err(e) => { - log::error!("{e:?}"); + match e { + AuthError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } } diff --git a/backend/feature/auth/src/framework/web/oidc_callback_handler.rs b/backend/feature/auth/src/framework/web/oidc_callback_handler.rs index a6494c7..cafda4b 100644 --- a/backend/feature/auth/src/framework/web/oidc_callback_handler.rs +++ b/backend/feature/auth/src/framework/web/oidc_callback_handler.rs @@ -1,5 +1,7 @@ use actix_session::Session; use actix_web::{HttpResponse, Responder, http::header, web}; +use anyhow::anyhow; +use sentry::integrations::anyhow::capture_anyhow; use crate::{ adapter::delivery::{ @@ -48,7 +50,7 @@ pub async fn oidc_callback_handler( match result { Ok(user) => { if let Err(e) = session.insert::(SESSION_KEY_USER_ID, user.id) { - log::error!("{e:?}"); + capture_anyhow(&e.into()); return HttpResponse::InternalServerError().finish(); } HttpResponse::Found() @@ -61,7 +63,10 @@ pub async fn oidc_callback_handler( | AuthError::InvalidNonce | AuthError::InvalidState => HttpResponse::BadRequest().finish(), _ => { - log::error!("{e:?}"); + match e { + AuthError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } }, diff --git a/backend/feature/auth/src/framework/web/oidc_login_handler.rs b/backend/feature/auth/src/framework/web/oidc_login_handler.rs index b804676..864b03f 100644 --- a/backend/feature/auth/src/framework/web/oidc_login_handler.rs +++ b/backend/feature/auth/src/framework/web/oidc_login_handler.rs @@ -1,8 +1,11 @@ use actix_session::Session; use actix_web::{HttpResponse, Responder, http::header, web}; +use anyhow::anyhow; +use sentry::integrations::anyhow::capture_anyhow; use crate::{ adapter::delivery::auth_controller::AuthController, + application::error::auth_error::AuthError, framework::web::constants::{SESSION_KEY_AUTH_NONCE, SESSION_KEY_AUTH_STATE}, }; @@ -24,11 +27,11 @@ pub async fn oidc_login_handler( match result { Ok(auth_url) => { if let Err(e) = session.insert::(SESSION_KEY_AUTH_STATE, auth_url.state) { - log::error!("{e:?}"); + capture_anyhow(&e.into()); return HttpResponse::InternalServerError().finish(); } if let Err(e) = session.insert::(SESSION_KEY_AUTH_NONCE, auth_url.nonce) { - log::error!("{e:?}"); + capture_anyhow(&e.into()); return HttpResponse::InternalServerError().finish(); } HttpResponse::Found() @@ -36,7 +39,10 @@ pub async fn oidc_login_handler( .finish() } Err(e) => { - log::error!("{e:?}"); + match e { + AuthError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } } diff --git a/backend/feature/image/Cargo.toml b/backend/feature/image/Cargo.toml index 36aee66..0e1d8dd 100644 --- a/backend/feature/image/Cargo.toml +++ b/backend/feature/image/Cargo.toml @@ -6,9 +6,11 @@ edition.workspace = true [dependencies] actix-multipart.workspace = true actix-web.workspace = true +anyhow.workspace = true async-trait.workspace = true futures.workspace = true log.workspace = true +sentry.workspace = true serde.workspace = true sqlx.workspace = true utoipa.workspace = true diff --git a/backend/feature/image/src/adapter/delivery/image_controller.rs b/backend/feature/image/src/adapter/delivery/image_controller.rs index 4f85440..4dd333f 100644 --- a/backend/feature/image/src/adapter/delivery/image_controller.rs +++ b/backend/feature/image/src/adapter/delivery/image_controller.rs @@ -57,7 +57,7 @@ impl ImageController for ImageControllerImpl { image: ImageRequestDto, ) -> Result { if !self.mime_type_whitelist.contains(&image.mime_type) { - return Err(ImageError::UnsupportedMimeType); + return Err(ImageError::UnsupportedMimeType(image.mime_type)); } let mime_type = image.mime_type.clone(); diff --git a/backend/feature/image/src/application/error/image_error.rs b/backend/feature/image/src/application/error/image_error.rs index 5c10b00..0d046b2 100644 --- a/backend/feature/image/src/application/error/image_error.rs +++ b/backend/feature/image/src/application/error/image_error.rs @@ -1,7 +1,18 @@ -#[derive(Debug, PartialEq)] +use std::fmt::Display; + +#[derive(Debug)] pub enum ImageError { - DatabaseError(String), - StorageError(String), NotFound, - UnsupportedMimeType, + UnsupportedMimeType(String), + Unexpected(anyhow::Error), +} + +impl Display for ImageError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ImageError::NotFound => write!(f, "Image not found"), + ImageError::UnsupportedMimeType(mime) => write!(f, "Unsupported MIME type: {}", mime), + ImageError::Unexpected(e) => write!(f, "Unexpected error: {}", e), + } + } } 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 e61cc4c..37fa99f 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 @@ -34,7 +34,7 @@ impl ImageDbService for ImageDbServiceImpl { match id { Ok(id) => Ok(id), - Err(e) => Err(ImageError::DatabaseError(e.to_string())), + Err(e) => Err(ImageError::Unexpected(anyhow::Error::from(e))), } } @@ -59,7 +59,7 @@ impl ImageDbService for ImageDbServiceImpl { }), None => Err(ImageError::NotFound), }, - Err(e) => Err(ImageError::DatabaseError(e.to_string())), + Err(e) => Err(ImageError::Unexpected(anyhow::Error::from(e))), } } } 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 eb7b61a..62b5ac1 100644 --- a/backend/feature/image/src/framework/storage/image_storage_impl.rs +++ b/backend/feature/image/src/framework/storage/image_storage_impl.rs @@ -22,20 +22,19 @@ 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::StorageError(e.to_string()))?; + fs::create_dir_all(&dir_path).map_err(|e| ImageError::Unexpected(e.into()))?; let file_path = format!("{}/{}", dir_path, id); - let mut file = - File::create(&file_path).map_err(|e| ImageError::StorageError(e.to_string()))?; + let mut file = File::create(&file_path).map_err(|e| ImageError::Unexpected(e.into()))?; file.write_all(data) - .map_err(|e| ImageError::StorageError(e.to_string()))?; + .map_err(|e| ImageError::Unexpected(e.into()))?; Ok(()) } 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::StorageError(e.to_string()))?; + let data = fs::read(&file_path).map_err(|e| ImageError::Unexpected(e.into()))?; Ok(data) } } diff --git a/backend/feature/image/src/framework/web/get_image_by_id_handler.rs b/backend/feature/image/src/framework/web/get_image_by_id_handler.rs index 0fd65c9..e345c8c 100644 --- a/backend/feature/image/src/framework/web/get_image_by_id_handler.rs +++ b/backend/feature/image/src/framework/web/get_image_by_id_handler.rs @@ -1,4 +1,6 @@ use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; +use sentry::integrations::anyhow::capture_anyhow; use utoipa::ToSchema; use crate::{ @@ -29,8 +31,12 @@ pub async fn get_image_by_id_handler( .body(image_response.data), Err(e) => match e { ImageError::NotFound => HttpResponse::NotFound().finish(), + ImageError::Unexpected(e) => { + capture_anyhow(&e); + HttpResponse::InternalServerError().finish() + } _ => { - log::error!("{e:?}"); + capture_anyhow(&anyhow!(e)); HttpResponse::InternalServerError().finish() } }, diff --git a/backend/feature/image/src/framework/web/upload_image_handler.rs b/backend/feature/image/src/framework/web/upload_image_handler.rs index 67fc86e..3964a8f 100644 --- a/backend/feature/image/src/framework/web/upload_image_handler.rs +++ b/backend/feature/image/src/framework/web/upload_image_handler.rs @@ -1,7 +1,9 @@ use actix_multipart::Multipart; use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; use auth::framework::web::auth_middleware::UserId; use futures::StreamExt; +use sentry::integrations::anyhow::capture_anyhow; use utoipa::ToSchema; use crate::{ @@ -73,9 +75,15 @@ pub async fn upload_image_handler( match result { Ok(image_info) => HttpResponse::Created().json(image_info), Err(e) => match e { - ImageError::UnsupportedMimeType => HttpResponse::BadRequest().body(format!("{e:?}")), + ImageError::UnsupportedMimeType(mime_type) => { + HttpResponse::BadRequest().body(format!("Unsupported MIME type: {}", mime_type)) + } + ImageError::Unexpected(e) => { + capture_anyhow(&e); + HttpResponse::InternalServerError().finish() + } _ => { - log::error!("{e:?}"); + capture_anyhow(&anyhow!(e)); HttpResponse::InternalServerError().finish() } }, diff --git a/backend/feature/post/Cargo.toml b/backend/feature/post/Cargo.toml index 73d6eff..f7789c5 100644 --- a/backend/feature/post/Cargo.toml +++ b/backend/feature/post/Cargo.toml @@ -5,9 +5,11 @@ edition.workspace = true [dependencies] actix-web.workspace = true +anyhow.workspace = true async-trait.workspace = true chrono.workspace = true log.workspace = true +sentry.workspace = true serde.workspace = true sqlx.workspace = true utoipa.workspace = true diff --git a/backend/feature/post/src/application/error/post_error.rs b/backend/feature/post/src/application/error/post_error.rs index bcd0086..f917ee4 100644 --- a/backend/feature/post/src/application/error/post_error.rs +++ b/backend/feature/post/src/application/error/post_error.rs @@ -1,5 +1,16 @@ -#[derive(Debug, PartialEq)] +use std::fmt::Display; + +#[derive(Debug)] pub enum PostError { - DatabaseError(String), NotFound, + Unexpected(anyhow::Error), +} + +impl Display for PostError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PostError::NotFound => write!(f, "Post not found"), + PostError::Unexpected(e) => write!(f, "Unexpected error: {}", e), + } + } } 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 d08b750..ca7e1b1 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 @@ -31,7 +31,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .fetch_one(&self.db_pool) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; Ok(id) } @@ -49,7 +49,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .execute(&self.db_pool) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))? + .map_err(|e| PostError::Unexpected(e.into()))? .rows_affected(); if affected_rows == 0 { @@ -71,7 +71,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .fetch_optional(&self.db_pool) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; match record { Some(record) => Ok(record.into_mapper()), @@ -91,7 +91,7 @@ impl LabelDbService for LabelDbServiceImpl { ) .fetch_all(&self.db_pool) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(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 6fdcfba..1a60019 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 @@ -64,7 +64,7 @@ impl PostDbService for PostDbServiceImpl { .build_query_as::() .fetch_all(&self.db_pool) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; let mut post_info_mappers_map = HashMap::::new(); @@ -136,7 +136,7 @@ impl PostDbService for PostDbServiceImpl { .build_query_as::() .fetch_all(&self.db_pool) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; if records.is_empty() { return Err(PostError::NotFound); @@ -188,7 +188,7 @@ impl PostDbService for PostDbServiceImpl { .db_pool .begin() .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; let post_id = sqlx::query_scalar!( r#" @@ -205,7 +205,7 @@ impl PostDbService for PostDbServiceImpl { ) .fetch_one(&mut *tx) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; for (order, &label_id) in label_ids.iter().enumerate() { sqlx::query!( @@ -221,12 +221,12 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; } tx.commit() .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; Ok(post_id) } @@ -236,7 +236,7 @@ impl PostDbService for PostDbServiceImpl { .db_pool .begin() .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; let affected_rows = sqlx::query!( r#" @@ -258,7 +258,7 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))? + .map_err(|e| PostError::Unexpected(e.into()))? .rows_affected(); if affected_rows == 0 { @@ -274,7 +274,7 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; for (order, &label_id) in label_ids.iter().enumerate() { sqlx::query!( @@ -290,12 +290,12 @@ impl PostDbService for PostDbServiceImpl { ) .execute(&mut *tx) .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; } tx.commit() .await - .map_err(|err| PostError::DatabaseError(err.to_string()))?; + .map_err(|e| PostError::Unexpected(e.into()))?; Ok(()) } diff --git a/backend/feature/post/src/framework/web/create_label_handler.rs b/backend/feature/post/src/framework/web/create_label_handler.rs index 499f7c0..4ee1aa7 100644 --- a/backend/feature/post/src/framework/web/create_label_handler.rs +++ b/backend/feature/post/src/framework/web/create_label_handler.rs @@ -1,9 +1,14 @@ use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; use auth::framework::web::auth_middleware::UserId; +use sentry::integrations::anyhow::capture_anyhow; -use crate::adapter::delivery::{ - create_label_request_dto::CreateLabelRequestDto, label_response_dto::LabelResponseDto, - post_controller::PostController, +use crate::{ + adapter::delivery::{ + create_label_request_dto::CreateLabelRequestDto, label_response_dto::LabelResponseDto, + post_controller::PostController, + }, + application::error::post_error::PostError, }; #[utoipa::path( @@ -28,7 +33,10 @@ pub async fn create_label_handler( match result { Ok(label) => HttpResponse::Created().json(label), Err(e) => { - log::error!("{e:?}"); + match e { + PostError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } } diff --git a/backend/feature/post/src/framework/web/create_post_handler.rs b/backend/feature/post/src/framework/web/create_post_handler.rs index c26a6b9..a5c84c5 100644 --- a/backend/feature/post/src/framework/web/create_post_handler.rs +++ b/backend/feature/post/src/framework/web/create_post_handler.rs @@ -1,9 +1,14 @@ -use actix_web::{web, HttpResponse, Responder}; +use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; use auth::framework::web::auth_middleware::UserId; +use sentry::integrations::anyhow::capture_anyhow; -use crate::adapter::delivery::{ - create_post_request_dto::CreatePostRequestDto, post_controller::PostController, - post_response_dto::PostResponseDto, +use crate::{ + adapter::delivery::{ + create_post_request_dto::CreatePostRequestDto, post_controller::PostController, + post_response_dto::PostResponseDto, + }, + application::error::post_error::PostError, }; #[utoipa::path( @@ -28,7 +33,10 @@ pub async fn create_post_handler( match result { Ok(post) => HttpResponse::Created().json(post), Err(e) => { - log::error!("{e:?}"); + match e { + PostError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } } diff --git a/backend/feature/post/src/framework/web/get_all_labels_handler.rs b/backend/feature/post/src/framework/web/get_all_labels_handler.rs index 38edb70..bfc0934 100644 --- a/backend/feature/post/src/framework/web/get_all_labels_handler.rs +++ b/backend/feature/post/src/framework/web/get_all_labels_handler.rs @@ -1,7 +1,10 @@ use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; +use sentry::integrations::anyhow::capture_anyhow; -use crate::adapter::delivery::{ - label_response_dto::LabelResponseDto, post_controller::PostController, +use crate::{ + adapter::delivery::{label_response_dto::LabelResponseDto, post_controller::PostController}, + application::error::post_error::PostError, }; #[utoipa::path( @@ -21,7 +24,10 @@ pub async fn get_all_labels_handler( match result { Ok(labels) => HttpResponse::Ok().json(labels), Err(e) => { - log::error!("{e:?}"); + match e { + PostError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } } diff --git a/backend/feature/post/src/framework/web/get_all_post_info_handler.rs b/backend/feature/post/src/framework/web/get_all_post_info_handler.rs index a47669d..2f3dd19 100644 --- a/backend/feature/post/src/framework/web/get_all_post_info_handler.rs +++ b/backend/feature/post/src/framework/web/get_all_post_info_handler.rs @@ -1,8 +1,13 @@ use actix_web::{HttpResponse, Responder, web}; +use anyhow::anyhow; +use sentry::integrations::anyhow::capture_anyhow; -use crate::adapter::delivery::{ - post_controller::PostController, post_info_query_dto::PostQueryDto, - post_info_response_dto::PostInfoResponseDto, +use crate::{ + adapter::delivery::{ + post_controller::PostController, post_info_query_dto::PostQueryDto, + post_info_response_dto::PostInfoResponseDto, + }, + application::error::post_error::PostError, }; #[utoipa::path( @@ -26,7 +31,10 @@ pub async fn get_all_post_info_handler( match result { Ok(post_info_list) => HttpResponse::Ok().json(post_info_list), Err(e) => { - log::error!("{e:?}"); + match e { + PostError::Unexpected(e) => capture_anyhow(&e), + _ => capture_anyhow(&anyhow!(e)), + }; HttpResponse::InternalServerError().finish() } } diff --git a/backend/feature/post/src/framework/web/get_post_by_id_handler.rs b/backend/feature/post/src/framework/web/get_post_by_id_handler.rs index 03576c8..2057c5f 100644 --- a/backend/feature/post/src/framework/web/get_post_by_id_handler.rs +++ b/backend/feature/post/src/framework/web/get_post_by_id_handler.rs @@ -1,4 +1,5 @@ use actix_web::{HttpResponse, Responder, web}; +use sentry::integrations::anyhow::capture_anyhow; use crate::{ adapter::delivery::{post_controller::PostController, post_response_dto::PostResponseDto}, @@ -24,13 +25,12 @@ pub async fn get_post_by_id_handler( match result { Ok(post) => HttpResponse::Ok().json(post), - Err(e) => { - if e == PostError::NotFound { - HttpResponse::NotFound().finish() - } else { - log::error!("{e:?}"); + Err(e) => match e { + PostError::NotFound => HttpResponse::NotFound().finish(), + PostError::Unexpected(e) => { + capture_anyhow(&e); HttpResponse::InternalServerError().finish() } - } + }, } } diff --git a/backend/feature/post/src/framework/web/update_label_handler.rs b/backend/feature/post/src/framework/web/update_label_handler.rs index 2d19746..5c2b32f 100644 --- a/backend/feature/post/src/framework/web/update_label_handler.rs +++ b/backend/feature/post/src/framework/web/update_label_handler.rs @@ -1,5 +1,6 @@ use actix_web::{HttpResponse, Responder, web}; use auth::framework::web::auth_middleware::UserId; +use sentry::integrations::anyhow::capture_anyhow; use crate::{ adapter::delivery::{ @@ -37,8 +38,8 @@ pub async fn update_label_handler( Ok(label) => HttpResponse::Ok().json(label), Err(e) => match e { PostError::NotFound => HttpResponse::NotFound().finish(), - _ => { - log::error!("{e:?}"); + PostError::Unexpected(e) => { + capture_anyhow(&e); HttpResponse::InternalServerError().finish() } }, diff --git a/backend/feature/post/src/framework/web/update_post_handler.rs b/backend/feature/post/src/framework/web/update_post_handler.rs index d9cdb81..167b005 100644 --- a/backend/feature/post/src/framework/web/update_post_handler.rs +++ b/backend/feature/post/src/framework/web/update_post_handler.rs @@ -1,9 +1,13 @@ use actix_web::{HttpResponse, Responder, web}; use auth::framework::web::auth_middleware::UserId; +use sentry::integrations::anyhow::capture_anyhow; -use crate::adapter::delivery::{ - post_controller::PostController, post_response_dto::PostResponseDto, - update_post_request_dto::UpdatePostRequestDto, +use crate::{ + adapter::delivery::{ + post_controller::PostController, post_response_dto::PostResponseDto, + update_post_request_dto::UpdatePostRequestDto, + }, + application::error::post_error::PostError, }; #[utoipa::path( @@ -30,14 +34,12 @@ pub async fn update_post_handler( match result { Ok(post) => HttpResponse::Ok().json(post), - Err(e) => { - log::error!("{e:?}"); - match e { - crate::application::error::post_error::PostError::NotFound => { - HttpResponse::NotFound().finish() - } - _ => HttpResponse::InternalServerError().finish(), + Err(e) => match e { + PostError::NotFound => HttpResponse::NotFound().finish(), + PostError::Unexpected(e) => { + capture_anyhow(&e); + HttpResponse::InternalServerError().finish() } - } + }, } } diff --git a/backend/server/Cargo.toml b/backend/server/Cargo.toml index 8e5a7b1..03ed4c1 100644 --- a/backend/server/Cargo.toml +++ b/backend/server/Cargo.toml @@ -11,6 +11,7 @@ env_logger.workspace = true hex.workspace = true openidconnect.workspace = true percent-encoding.workspace = true +sentry.workspace = true sqlx.workspace = true utoipa.workspace = true utoipa-redoc.workspace = true diff --git a/backend/server/src/configuration.rs b/backend/server/src/configuration.rs index 3647d44..9959170 100644 --- a/backend/server/src/configuration.rs +++ b/backend/server/src/configuration.rs @@ -1,12 +1,13 @@ use openidconnect::reqwest; use crate::configuration::{ - db::DbConfiguration, oidc::OidcConfiguration, server::ServerConfiguration, - session::SessionConfiguration, storage::StorageConfiguration, + db::DbConfiguration, oidc::OidcConfiguration, sentry::SentryConfiguration, + server::ServerConfiguration, session::SessionConfiguration, storage::StorageConfiguration, }; pub mod db; pub mod oidc; +pub mod sentry; pub mod server; pub mod session; pub mod storage; @@ -15,6 +16,7 @@ pub mod storage; pub struct Configuration { pub db: DbConfiguration, pub oidc: OidcConfiguration, + pub sentry: SentryConfiguration, pub server: ServerConfiguration, pub session: SessionConfiguration, pub storage: StorageConfiguration, @@ -25,6 +27,7 @@ impl Configuration { Self { db: DbConfiguration::new(), oidc: OidcConfiguration::new(http_client).await, + sentry: SentryConfiguration::new(), server: ServerConfiguration::new(), session: SessionConfiguration::new(), storage: StorageConfiguration::new(), diff --git a/backend/server/src/configuration/sentry.rs b/backend/server/src/configuration/sentry.rs new file mode 100644 index 0000000..e9df39c --- /dev/null +++ b/backend/server/src/configuration/sentry.rs @@ -0,0 +1,22 @@ +#[derive(Clone)] +pub struct SentryConfiguration { + pub dsn: String, + pub options: sentry::ClientOptions, +} + +impl SentryConfiguration { + pub fn new() -> Self { + let dsn = std::env::var("SENTRY_DSN").unwrap_or("".to_string()); + + Self { + dsn: dsn, + options: sentry::ClientOptions { + release: sentry::release_name!(), + traces_sample_rate: 1.0, + send_default_pii: true, + max_request_body_size: sentry::MaxRequestBodySize::Always, + ..Default::default() + }, + } + } +} diff --git a/backend/server/src/main.rs b/backend/server/src/main.rs index cae50d6..0fd269e 100644 --- a/backend/server/src/main.rs +++ b/backend/server/src/main.rs @@ -5,17 +5,19 @@ use actix_web::{ App, Error, HttpServer, body::MessageBody, dev::{ServiceFactory, ServiceRequest, ServiceResponse}, + rt::Runtime, web, }; use auth::framework::web::auth_web_routes::configure_auth_routes; use image::framework::web::image_web_routes::configure_image_routes; use openidconnect::reqwest; use post::framework::web::post_web_routes::configure_post_routes; -use server::{api_doc::configure_api_doc_routes, configuration::Configuration, container::Container}; +use server::{ + api_doc::configure_api_doc_routes, configuration::Configuration, container::Container, +}; use sqlx::{Pool, Postgres}; -#[actix_web::main] -async fn main() -> std::io::Result<()> { +fn main() -> std::io::Result<()> { dotenv::dotenv().ok(); env_logger::init(); @@ -24,31 +26,45 @@ async fn main() -> std::io::Result<()> { .build() .expect("Failed to create HTTP client"); - let configuration = Configuration::new(http_client.clone()).await; + let rt = Runtime::new().unwrap(); + let configuration = rt.block_on(async { Configuration::new(http_client.clone()).await }); - let host = configuration.server.host.clone(); - let port = configuration.server.port; + let _guard = sentry::init(( + configuration.sentry.dsn.clone(), + configuration.sentry.options.clone(), + )); - let db_pool = configuration.db.create_connection().await; - let session_key = configuration.session.session_key.clone(); - let session_store = configuration.session.create_session_store().await; + actix_web::rt::System::new().block_on(async { + let host = configuration.server.host.clone(); + let port = configuration.server.port; - HttpServer::new(move || { - create_app( - db_pool.clone(), - http_client.clone(), - SessionMiddleware::builder(session_store.clone(), session_key.clone()), - configuration.clone(), - ) - }) - .bind((host, port))? - .run() - .await + let db_pool = configuration.db.create_connection().await; + let session_key = configuration.session.session_key.clone(); + let session_store = configuration.session.create_session_store().await; + + HttpServer::new(move || { + create_app( + db_pool.clone(), + http_client.clone(), + sentry::integrations::actix::Sentry::builder() + .capture_server_errors(true) + .start_transaction(true), + SessionMiddleware::builder(session_store.clone(), session_key.clone()), + configuration.clone(), + ) + }) + .bind((host, port))? + .run() + .await + })?; + + Ok(()) } fn create_app( db_pool: Pool, http_client: reqwest::Client, + sentry_builder: sentry::integrations::actix::SentryBuilder, session_middleware_builder: SessionMiddlewareBuilder, configuration: Configuration, ) -> App< @@ -64,6 +80,7 @@ fn create_app( App::new() // The middlewares are executed in opposite order as registration. + .wrap(sentry_builder.finish()) .wrap(session_middleware_builder.build()) .app_data(web::Data::from(container.auth_controller)) .app_data(web::Data::from(container.image_controller))