From 68d9a0a62663fd2e094192561c15d3b0cf240a66 Mon Sep 17 00:00:00 2001 From: lucalise Date: Thu, 15 Jan 2026 20:12:54 -0800 Subject: [PATCH] feat: implement repo fetching, add sentry --- api/Cargo.lock | 665 ++++++++++++++++++++++++++++++++- api/Cargo.toml | 4 + api/src/endpoints/get_repos.rs | 16 +- api/src/error.rs | 2 +- api/src/main.rs | 83 +++- bun.lock | 2 +- package.json | 4 +- src/lib/Repos.svelte | 27 +- src/lib/api-client.ts | 50 +++ src/lib/auth-client.ts | 4 +- src/lib/types/repo.ts | 8 +- vite.config.ts | 8 +- 12 files changed, 822 insertions(+), 51 deletions(-) create mode 100644 src/lib/api-client.ts diff --git a/api/Cargo.lock b/api/Cargo.lock index 814796c..899f816 100644 --- a/api/Cargo.lock +++ b/api/Cargo.lock @@ -185,6 +185,15 @@ dependencies = [ "syn", ] +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -237,11 +246,15 @@ dependencies = [ "actix-web", "chrono", "jsonwebtoken", - "reqwest", + "reqwest 0.13.1", + "sentry", "serde", "sqlx", "thiserror 2.0.17", "tokio", + "tracing", + "tracing-actix-web", + "tracing-subscriber", ] [[package]] @@ -287,6 +300,21 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + [[package]] name = "base16ct" version = "0.2.0" @@ -323,6 +351,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "brotli" version = "8.0.2" @@ -592,6 +629,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" @@ -647,6 +694,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -813,6 +870,18 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +[[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.8" @@ -986,6 +1055,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "group" version = "0.13.0" @@ -1100,6 +1175,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link", +] + [[package]] name = "http" version = "0.2.12" @@ -1194,6 +1280,22 @@ dependencies = [ "tower-service", ] +[[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.19" @@ -1547,6 +1649,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "md-5" version = "0.10.6" @@ -1591,6 +1702,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mutually_exclusive_features" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577" + [[package]] name = "native-tls" version = "0.2.14" @@ -1608,6 +1725,27 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -1670,6 +1808,174 @@ dependencies = [ "libm", ] +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1726,6 +2032,22 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_info" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" +dependencies = [ + "android_system_properties", + "log", + "nix", + "objc2", + "objc2-foundation", + "objc2-ui-kit", + "serde", + "windows-sys 0.61.2", +] + [[package]] name = "p256" version = "0.13.2" @@ -1804,6 +2126,26 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2068,6 +2410,44 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "reqwest" version = "0.13.1" @@ -2152,6 +2532,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "2.1.1" @@ -2347,6 +2733,127 @@ version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +[[package]] +name = "sentry" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f925d575b468e88b079faf590a8dd0c9c99e2ec29e9bab663ceb8b45056312f" +dependencies = [ + "httpdate", + "native-tls", + "reqwest 0.12.28", + "sentry-actix", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-debug-images", + "sentry-panic", + "sentry-tracing", + "tokio", + "ureq", +] + +[[package]] +name = "sentry-actix" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18bac0f6b8621fa0f85e298901e51161205788322e1a995e3764329020368058" +dependencies = [ + "actix-http", + "actix-web", + "bytes", + "futures-util", + "sentry-core", +] + +[[package]] +name = "sentry-backtrace" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb1ef7534f583af20452b1b1bf610a60ed9c8dd2d8485e7bd064efc556a78fb" +dependencies = [ + "backtrace", + "regex", + "sentry-core", +] + +[[package]] +name = "sentry-contexts" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd6be899d9938390b6d1ec71e2f53bd9e57b6a9d8b1d5b049e5c364e7da9078" +dependencies = [ + "hostname", + "libc", + "os_info", + "rustc_version", + "sentry-core", + "uname", +] + +[[package]] +name = "sentry-core" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ab054c34b87f96c3e4701bea1888317cde30cc7e4a6136d2c48454ab96661c" +dependencies = [ + "rand 0.9.2", + "sentry-types", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "sentry-debug-images" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5637ec550dc6f8c49a711537950722d3fc4baa6fd433c371912104eaff31e2a5" +dependencies = [ + "findshlibs", + "sentry-core", +] + +[[package]] +name = "sentry-panic" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f02c7162f7b69b8de872b439d4696dc1d65f80b13ddd3c3831723def4756b63" +dependencies = [ + "sentry-backtrace", + "sentry-core", +] + +[[package]] +name = "sentry-tracing" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1dd47df349a80025819f3d25c3d2f751df705d49c65a4cdc0f130f700972a48" +dependencies = [ + "bitflags", + "sentry-backtrace", + "sentry-core", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sentry-types" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eecbd63e9d15a26a40675ed180d376fcb434635d2e33de1c24003f61e3e2230d" +dependencies = [ + "debugid", + "hex", + "rand 0.9.2", + "serde", + "serde_json", + "thiserror 2.0.17", + "time", + "url", + "uuid", +] + [[package]] name = "serde" version = "1.0.228" @@ -2424,6 +2931,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2839,6 +3355,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.45" @@ -2911,6 +3436,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[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-rustls" version = "0.26.4" @@ -3002,6 +3537,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-actix-web" +version = "0.7.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca6b15407f9bfcb35f82d0e79e603e1629ece4e91cc6d9e58f890c184dd20af" +dependencies = [ + "actix-web", + "mutually_exclusive_features", + "pin-project", + "tracing", + "uuid", +] + [[package]] name = "tracing-attributes" version = "0.1.31" @@ -3020,6 +3568,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -3034,6 +3612,15 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[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" @@ -3079,6 +3666,35 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" +dependencies = [ + "base64", + "der", + "log", + "native-tls", + "percent-encoding", + "rustls-pki-types", + "ureq-proto", + "utf-8", + "webpki-root-certs", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64", + "http 1.4.0", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -3089,14 +3705,39 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde_core", + "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" @@ -3246,6 +3887,22 @@ 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-util" version = "0.1.11" @@ -3255,6 +3912,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[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.62.2" diff --git a/api/Cargo.toml b/api/Cargo.toml index 96d8dec..ad6f5ec 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -8,7 +8,11 @@ actix-web = "4.12.1" chrono = { version = "0.4.43", features = ["serde"] } jsonwebtoken = { version = "10.2.0", features = ["rust_crypto"] } reqwest = { version = "0.13.1", features = ["json"] } +sentry = { version = "0.46.1", features = ["actix", "tracing"] } serde = "1.0.228" sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-native-tls"] } thiserror = "2.0.17" tokio = "1.49.0" +tracing = "0.1.44" +tracing-actix-web = "0.7.21" +tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } diff --git a/api/src/endpoints/get_repos.rs b/api/src/endpoints/get_repos.rs index b961102..fd5c61e 100644 --- a/api/src/endpoints/get_repos.rs +++ b/api/src/endpoints/get_repos.rs @@ -4,13 +4,15 @@ use serde::{Deserialize, Serialize}; use crate::{AppState, account::AccountRepository, error::Result}; -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] struct Repository { + id: u64, name: String, full_name: String, - description: String, - language: String, - stars: usize, + description: Option, + language: Option, + #[serde(alias = "stargazers_count")] + stars: Option, updated_at: DateTime, private: bool, } @@ -25,11 +27,13 @@ pub async fn get_repos( let response = app_state .reqwest_client - .get("https://api.github.com/user/repos") + .get("https://api.github.com/user/repos?affiliation=owner") .bearer_auth(token) .send() .await?; response.error_for_status_ref()?; + let data = response.json::>().await?; + tracing::debug!(github_response = ?data.iter().filter(|r| r.private == true).collect::>(), "received repos"); - Ok(HttpResponse::Ok().json(response.json::>().await?)) + Ok(HttpResponse::Ok().json(data)) } diff --git a/api/src/error.rs b/api/src/error.rs index 3912d34..b775c2f 100644 --- a/api/src/error.rs +++ b/api/src/error.rs @@ -28,7 +28,7 @@ struct ErrorResponse { impl ResponseError for Error { fn error_response(&self) -> actix_web::HttpResponse { match self { - Error::AccessToken => HttpResponse::Unauthorized().finish(), + Error::AccessToken => HttpResponse::BadRequest().finish(), Error::Unauthorized => HttpResponse::Unauthorized().finish(), Error::TokenExpired => HttpResponse::Unauthorized().json(ErrorResponse { error: "token expired".to_string(), diff --git a/api/src/main.rs b/api/src/main.rs index 485e0c6..2ca0091 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -6,8 +6,12 @@ mod middleware; use std::env; -use actix_web::{App, HttpServer, middleware::from_fn, web}; +use actix_web::{App, HttpServer, middleware::from_fn, rt::System, web}; use sqlx::PgPool; +use tracing::level_filters::LevelFilter; +use tracing_subscriber::{ + EnvFilter, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt, +}; use crate::auth::{Auth, JWT}; @@ -17,9 +21,15 @@ struct AppState { auth: Auth, } -#[actix_web::main] -async fn main() -> std::io::Result<()> { - let reqwest_client = reqwest::Client::new(); +async fn run() -> std::io::Result<()> { + let reqwest_client = reqwest::Client::builder() + .user_agent(concat!( + env!("CARGO_PKG_NAME"), + "/", + env!("CARGO_PKG_VERSION") + )) + .build() + .expect("failed to create reqwest client"); let app_data = web::Data::new(AppState { reqwest_client: reqwest_client.clone(), pool: PgPool::connect( @@ -33,21 +43,74 @@ async fn main() -> std::io::Result<()> { HttpServer::new(move || { App::new() .app_data(app_data.clone()) + .wrap( + sentry::integrations::actix::Sentry::builder() + .capture_server_errors(true) + .start_transaction(true) + .finish(), + ) + .wrap(tracing_actix_web::TracingLogger::default()) .route( "/", web::get() .to(async || concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"))), ) .service( - web::scope("/api") - .route( - "/{user_id}/repos", - web::get().to(endpoints::get_repos::get_repos), - ) - .wrap(from_fn(middleware::protected)), + web::scope("/api").service( + web::scope("/v0").service( + web::scope("/user") + .route( + "/{user_id}/repos", + web::get().to(endpoints::get_repos::get_repos), + ) + .wrap(from_fn(middleware::protected)), + ), + ), ) }) .bind(("127.0.0.1", 8080))? .run() .await } + +// cant use #[actix_web::main] because of Sentry +// Note: "Macros like #[tokio::main] and #[actix_web::main] are not supported. The Sentry client must be initialized before the async runtime is started." +// https://docs.sentry.io/platforms/rust/guides/actix-web/ +fn main() -> std::io::Result<()> { + let tracing_env_filter = EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy() + .add_directive("reqwest=info".parse().unwrap()) + .add_directive("hyper=info".parse().unwrap()) + .add_directive("h2=info".parse().unwrap()) + .add_directive("rustls=info".parse().unwrap()); + + tracing_subscriber::registry() + .with(tracing_env_filter) + .with( + tracing_subscriber::fmt::layer() + .compact() + .with_span_events(FmtSpan::CLOSE), + ) + .with(sentry::integrations::tracing::layer()) + .init(); + + let guard = sentry::init(( + env::var("SENTRY_DSN").ok(), + sentry::ClientOptions { + release: sentry::release_name!(), + traces_sample_rate: 1.0, + session_mode: sentry::SessionMode::Request, + debug: true, + ..Default::default() + }, + )); + + if guard.is_enabled() { + tracing::info!("sentry initialized"); + } else { + tracing::info!("sentry **NOT** initialized") + }; + + System::new().block_on(run()) +} diff --git a/bun.lock b/bun.lock index dfd6de8..9014c74 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,7 @@ "bits-ui": "^2.15.4", "dotenv": "^17.2.3", "drizzle-orm": "^0.45.1", + "jose": "^6.1.3", }, "devDependencies": { "@sveltejs/adapter-auto": "^7.0.0", @@ -21,7 +22,6 @@ "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/vite": "^4.1.17", "drizzle-kit": "^0.31.8", - "prettier": "^3.7.4", "prettier-plugin-svelte": "^3.4.0", "prettier-plugin-tailwindcss": "^0.7.2", "rolldown-vite": "^7.3.1", diff --git a/package.json b/package.json index de24934..7ab20ef 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/vite": "^4.1.17", "drizzle-kit": "^0.31.8", - "prettier": "^3.7.4", "prettier-plugin-svelte": "^3.4.0", "prettier-plugin-tailwindcss": "^0.7.2", "rolldown-vite": "^7.3.1", @@ -38,6 +37,7 @@ "better-auth": "^1.4.12", "bits-ui": "^2.15.4", "dotenv": "^17.2.3", - "drizzle-orm": "^0.45.1" + "drizzle-orm": "^0.45.1", + "jose": "^6.1.3" } } diff --git a/src/lib/Repos.svelte b/src/lib/Repos.svelte index 2fb3ec2..73fa80e 100644 --- a/src/lib/Repos.svelte +++ b/src/lib/Repos.svelte @@ -4,37 +4,24 @@ import type { Repository } from './types/repo'; import { createQuery } from '@tanstack/svelte-query'; import { authClient } from './auth-client'; + import { apiClient } from './api-client'; const session = authClient.useSession(); const query = createQuery(() => ({ queryKey: ['github-repositories'], queryFn: async () => { - const response = await fetch(`/api/${$session.data?.user.id}/repos`); - return await response.json(); - } + return await apiClient.request(`/api/v0/user/${$session.data?.user.id}/repos`); + }, + enabled: !!$session.data?.user.id, + staleTime: 30000 })); - $inspect(query.data); - - const mockRepositories: Repository[] = [ - { - id: 1, - name: 'Mock', - fullName: 'Mock', - description: 'Mock Data', - language: 'GDScript', - stars: 42, - updatedAt: new Date().toISOString(), - isPrivate: false - } - ]; - let searchQuery = $state(''); let adding = $state(null); const filteredRepositories = $derived( - mockRepositories.filter( + (query.data ?? []).filter( (repo) => repo.name.toLowerCase().includes(searchQuery.toLowerCase()) || repo.description?.toLowerCase().includes(searchQuery.toLowerCase()) @@ -110,7 +97,7 @@ {/if} - {repo.stars} + {repo.stars ?? 0} diff --git a/src/lib/api-client.ts b/src/lib/api-client.ts new file mode 100644 index 0000000..29d68a8 --- /dev/null +++ b/src/lib/api-client.ts @@ -0,0 +1,50 @@ +import { decodeJwt } from 'jose'; +import { authClient } from './auth-client'; + +export class ApiClient { + private token: string | null; + + constructor() { + this.token = null; + } + + private tokenValid() { + if (!this.token) { + return false; + } + + const jwt = decodeJwt(this.token); + if (!jwt.exp) { + return false; + } + + return jwt.exp > Date.now() / 1000 + 10; + } + + private async getToken() { + if (this.tokenValid()) { + return this.token; + } + + const token = (await authClient.token().then((t) => t.data?.token)) ?? null; + this.token = token; + + return token; + } + + async request(url: string, options: RequestInit = {}): Promise { + const token = await this.getToken(); + + const response = await fetch(url, { + ...options, + headers: { + Authorization: `Bearer ${token}` + } + }); + + const data = await response.json(); + return data; + } +} + +export const apiClient = new ApiClient(); diff --git a/src/lib/auth-client.ts b/src/lib/auth-client.ts index 5b0cdb0..db6f1d4 100644 --- a/src/lib/auth-client.ts +++ b/src/lib/auth-client.ts @@ -1,7 +1,7 @@ -import { jwt } from 'better-auth/plugins'; +import { jwtClient } from 'better-auth/client/plugins'; import { createAuthClient } from 'better-auth/svelte'; export const authClient = createAuthClient({ baseURL: 'http://localhost:5173', - plugins: [jwt()] + plugins: [jwtClient()] }); diff --git a/src/lib/types/repo.ts b/src/lib/types/repo.ts index d2aa14d..769170c 100644 --- a/src/lib/types/repo.ts +++ b/src/lib/types/repo.ts @@ -2,9 +2,9 @@ export type Repository = { id: number; name: string; fullName: string; - description: string | null; - language: string | null; - stars: number; + description?: string | null; + language?: string | null; + stars?: number; updatedAt: string; - isPrivate: boolean; + private: boolean; }; diff --git a/vite.config.ts b/vite.config.ts index 02563d2..623d376 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,10 +6,10 @@ export default defineConfig({ plugins: [tailwindcss(), sveltekit()], server: { proxy: { - // '/api': { - // target: 'http://localhost:8080', - // changeOrigin: true - // } + '/api/v0': { + target: 'http://localhost:8080', + changeOrigin: true + } } } });