BLOG-90 Intergrate error tracking with Sentry #120

Merged
squid merged 4 commits from BLOG-90_sentry_intergated into main 2025-08-06 20:20:47 +08:00
45 changed files with 2405 additions and 143 deletions

488
backend/Cargo.lock generated
View File

@ -424,9 +424,11 @@ version = "0.2.0"
dependencies = [
"actix-session",
"actix-web",
"anyhow",
"async-trait",
"log",
"common",
"openidconnect",
"sentry",
"serde",
"sqlx",
"utoipa",
@ -617,6 +619,13 @@ dependencies = [
"tokio-util",
]
[[package]]
name = "common"
version = "0.2.0"
dependencies = [
"sqlx",
]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@ -656,6 +665,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 +823,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 +1110,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 +1155,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 +1428,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 +1532,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 +1747,12 @@ version = "0.2.0"
dependencies = [
"actix-multipart",
"actix-web",
"anyhow",
"async-trait",
"auth",
"common",
"futures",
"log",
"sentry",
"serde",
"sqlx",
"utoipa",
@ -1922,6 +2007,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 +2159,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 +2212,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 +2357,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 +2402,12 @@ name = "post"
version = "0.2.0"
dependencies = [
"actix-web",
"anyhow",
"async-trait",
"auth",
"chrono",
"log",
"common",
"sentry",
"serde",
"sqlx",
"utoipa",
@ -2273,6 +2446,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 +2687,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 +2702,7 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tower",
"tower-http",
@ -2621,6 +2806,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 +2847,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 +2900,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 +3181,7 @@ dependencies = [
"openidconnect",
"percent-encoding",
"post",
"sentry",
"sqlx",
"utoipa",
"utoipa-redoc",
@ -3302,6 +3662,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 +3792,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 +3816,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 +3874,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 +3916,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 +3977,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 +4127,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 +4173,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"

View File

@ -1,15 +1,26 @@
[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"] }
actix-web = "4.10.2"
anyhow = "1.0.98"
async-trait = "0.1.88"
chrono = "0.4.41"
dotenv = "0.15.0"
@ -22,6 +33,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",
@ -39,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"

View File

@ -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"]

View File

@ -6,9 +6,12 @@ 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
common.workspace = true

View File

@ -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),
}
}
}

View File

@ -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::DatabaseError(e.to_string()))?;
.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::DatabaseError(e.to_string()))?;
.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::DatabaseError(e.to_string()))?;
.map_err(|e| AuthError::Unexpected(DatabaseError(e).into()))?;
Ok(id)
}

View File

@ -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()))?
.unwrap()
.request_async(&self.http_client)
.await
.map_err(|_| AuthError::InvalidAuthCode)?;

View File

@ -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<Result<Self, Self::Error>>;
type Error = UnauthorizedError;
type Future = Ready<Result<Self, UnauthorizedError>>;
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let user_id_result = req.get_session().get::<i32>(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()
}
}

View File

@ -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()
}
}

View File

@ -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::<i32>(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()
}
},

View File

@ -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::<String>(SESSION_KEY_AUTH_STATE, auth_url.state) {
log::error!("{e:?}");
capture_anyhow(&e.into());
return HttpResponse::InternalServerError().finish();
}
if let Err(e) = session.insert::<String>(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()
}
}

View File

@ -0,0 +1,7 @@
[package]
name = "common"
version.workspace = true
edition.workspace = true
[dependencies]
sqlx.workspace = true

View File

@ -0,0 +1 @@
pub mod error;

View File

@ -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)
}
}

View File

@ -0,0 +1 @@
pub mod framework;

View File

@ -6,11 +6,13 @@ 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
auth.workspace = true
common.workspace = true

View File

@ -57,7 +57,7 @@ impl ImageController for ImageControllerImpl {
image: ImageRequestDto,
) -> Result<ImageInfoResponseDto, ImageError> {
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();

View File

@ -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),
}
}
}

View File

@ -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::DatabaseError(e.to_string())),
Err(e) => Err(ImageError::Unexpected(DatabaseError(e).into())),
}
}
@ -59,7 +60,7 @@ impl ImageDbService for ImageDbServiceImpl {
}),
None => Err(ImageError::NotFound),
},
Err(e) => Err(ImageError::DatabaseError(e.to_string())),
Err(e) => Err(ImageError::Unexpected(DatabaseError(e).into())),
}
}
}

View File

@ -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,20 +24,20 @@ 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(IOError(e).into()))?;
let file_path = format!("{}/{}", dir_path, id);
let mut file =
File::create(&file_path).map_err(|e| ImageError::StorageError(e.to_string()))?;
File::create(&file_path).map_err(|e| ImageError::Unexpected(IOError(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<Vec<u8>, 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(IOError(e).into()))?;
Ok(data)
}
}

View File

@ -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()
}
},

View File

@ -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()
}
},

View File

@ -5,11 +5,13 @@ 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
auth.workspace = true
common.workspace = true

View File

@ -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),
}
}
}

View File

@ -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(|err| PostError::DatabaseError(err.to_string()))?;
.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(|err| PostError::DatabaseError(err.to_string()))?
.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(|err| PostError::DatabaseError(err.to_string()))?;
.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(|err| PostError::DatabaseError(err.to_string()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
let mappers = records
.into_iter()

View File

@ -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::<PostInfoWithLabelRecord>()
.fetch_all(&self.db_pool)
.await
.map_err(|err| PostError::DatabaseError(err.to_string()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
let mut post_info_mappers_map = HashMap::<i32, PostInfoMapper>::new();
@ -136,7 +137,7 @@ impl PostDbService for PostDbServiceImpl {
.build_query_as::<PostWithLabelRecord>()
.fetch_all(&self.db_pool)
.await
.map_err(|err| PostError::DatabaseError(err.to_string()))?;
.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(|err| PostError::DatabaseError(err.to_string()))?;
.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(|err| PostError::DatabaseError(err.to_string()))?;
.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(|err| PostError::DatabaseError(err.to_string()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
}
tx.commit()
.await
.map_err(|err| PostError::DatabaseError(err.to_string()))?;
.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(|err| PostError::DatabaseError(err.to_string()))?;
.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(|err| PostError::DatabaseError(err.to_string()))?
.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(|err| PostError::DatabaseError(err.to_string()))?;
.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(|err| PostError::DatabaseError(err.to_string()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
}
tx.commit()
.await
.map_err(|err| PostError::DatabaseError(err.to_string()))?;
.map_err(|e| PostError::Unexpected(DatabaseError(e).into()))?;
Ok(())
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}

View File

@ -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()
}
}
},
}
}

View File

@ -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()
}
},

View File

@ -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()
}
}
},
}
}

View File

@ -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

View File

@ -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(),

View File

@ -0,0 +1,23 @@
#[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,
attach_stacktrace: true,
..Default::default()
},
}
}
}

View File

@ -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<Postgres>,
http_client: reqwest::Client,
sentry_builder: sentry::integrations::actix::SentryBuilder,
session_middleware_builder: SessionMiddlewareBuilder<RedisSessionStore>,
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))

3
frontend/.gitignore vendored
View File

@ -21,3 +21,6 @@ Thumbs.db
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
# Sentry Config File
.env.sentry-build-plugin

View File

@ -23,6 +23,8 @@ EXPOSE 3000
ENV NODE_ENV=production
ENV HOSTNAME=0.0.0.0
ENV PORT=3000
ENV SENTRY_AUTH_TOKEN=
ENV PUBLIC_SENTRY_DSN=
ENV PUBLIC_API_BASE_URL=http://127.0.0.1:8080/
ENV PUBLIC_GA_MEASUREMENT_ID=
CMD ["node", "build"]

View File

@ -47,5 +47,8 @@
"esbuild"
]
},
"packageManager": "pnpm@10.12.4"
"packageManager": "pnpm@10.12.4",
"dependencies": {
"@sentry/sveltekit": "^10.1.0"
}
}

1550
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
import { Environment } from '$lib/environment';
import { handleErrorWithSentry, replayIntegration } from '@sentry/sveltekit';
import * as Sentry from '@sentry/sveltekit';
Sentry.init({
dsn: Environment.SENTRY_DSN,
tracesSampleRate: 1.0,
// Enable logs to be sent to Sentry
enableLogs: true,
// This sets the sample rate to be 10%. You may want this to be 100% while
// in development and sample at a lower rate in production
replaysSessionSampleRate: 0.1,
// If the entire session is not sampled, use the below sample rate to sample
// sessions when an error occurs.
replaysOnErrorSampleRate: 1.0,
// If you don't want to use Session Replay, just remove the line below:
integrations: [replayIntegration()]
});
// If you have a custom error handler, pass it to `handleErrorWithSentry`
export const handleError = handleErrorWithSentry();

View File

@ -1,3 +1,5 @@
import { sequence } from '@sveltejs/kit/hooks';
import * as Sentry from '@sentry/sveltekit';
import { PostRepositoryImpl } from '$lib/post/adapter/gateway/postRepositoryImpl';
import { PostBloc } from '$lib/post/adapter/presenter/postBloc';
import { PostListBloc } from '$lib/post/adapter/presenter/postListBloc';
@ -5,8 +7,15 @@ import { GetAllPostsUseCase } from '$lib/post/application/useCase/getAllPostsUse
import { GetPostUseCase } from '$lib/post/application/useCase/getPostUseCase';
import { PostApiServiceImpl } from '$lib/post/framework/api/postApiServiceImpl';
import type { Handle } from '@sveltejs/kit';
import { Environment } from '$lib/environment';
export const handle: Handle = ({ event, resolve }) => {
Sentry.init({
dsn: Environment.SENTRY_DSN,
tracesSampleRate: 1,
enableLogs: true
});
export const handle: Handle = sequence(Sentry.sentryHandle(), ({ event, resolve }) => {
const postApiService = new PostApiServiceImpl(event.fetch);
const postRepository = new PostRepositoryImpl(postApiService);
const getAllPostsUseCase = new GetAllPostsUseCase(postRepository);
@ -16,4 +25,6 @@ export const handle: Handle = ({ event, resolve }) => {
event.locals.postBloc = new PostBloc(getPostUseCase);
return resolve(event);
};
});
export const handleError = Sentry.handleErrorWithSentry();

View File

@ -3,4 +3,5 @@ import { env } from '$env/dynamic/public';
export abstract class Environment {
static readonly API_BASE_URL = env.PUBLIC_API_BASE_URL ?? 'http://localhost:5173/api/';
static readonly GA_MEASUREMENT_ID = env.PUBLIC_GA_MEASUREMENT_ID ?? '';
static readonly SENTRY_DSN = env.PUBLIC_SENTRY_DSN ?? '';
}

View File

@ -1,3 +1,4 @@
import { sentrySvelteKit } from '@sentry/sveltekit';
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
@ -5,7 +6,16 @@ import { defineConfig } from 'vite';
import { version } from './package.json';
export default defineConfig({
plugins: [tailwindcss(), sveltekit()],
plugins: [
sentrySvelteKit({
sourceMapsUploadOptions: {
org: 'squidspirit',
project: 'blog-beta-frontend'
}
}),
tailwindcss(),
sveltekit()
],
define: {
'App.__VERSION__': JSON.stringify(version)
},