feat: implement repo fetching, add sentry

This commit is contained in:
2026-01-15 20:12:54 -08:00
parent 6f2f64fd73
commit 68d9a0a626
12 changed files with 822 additions and 51 deletions

665
api/Cargo.lock generated
View File

@@ -185,6 +185,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "addr2line"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
dependencies = [
"gimli",
]
[[package]] [[package]]
name = "adler2" name = "adler2"
version = "2.0.1" version = "2.0.1"
@@ -237,11 +246,15 @@ dependencies = [
"actix-web", "actix-web",
"chrono", "chrono",
"jsonwebtoken", "jsonwebtoken",
"reqwest", "reqwest 0.13.1",
"sentry",
"serde", "serde",
"sqlx", "sqlx",
"thiserror 2.0.17", "thiserror 2.0.17",
"tokio", "tokio",
"tracing",
"tracing-actix-web",
"tracing-subscriber",
] ]
[[package]] [[package]]
@@ -287,6 +300,21 @@ dependencies = [
"fs_extra", "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]] [[package]]
name = "base16ct" name = "base16ct"
version = "0.2.0" version = "0.2.0"
@@ -323,6 +351,15 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "block2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
dependencies = [
"objc2",
]
[[package]] [[package]]
name = "brotli" name = "brotli"
version = "8.0.2" version = "8.0.2"
@@ -592,6 +629,16 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "debugid"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d"
dependencies = [
"serde",
"uuid",
]
[[package]] [[package]]
name = "der" name = "der"
version = "0.7.10" version = "0.7.10"
@@ -647,6 +694,16 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "dispatch2"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
dependencies = [
"bitflags",
"objc2",
]
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.5" version = "0.2.5"
@@ -813,6 +870,18 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" 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]] [[package]]
name = "flate2" name = "flate2"
version = "1.1.8" version = "1.1.8"
@@ -986,6 +1055,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "gimli"
version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]] [[package]]
name = "group" name = "group"
version = "0.13.0" version = "0.13.0"
@@ -1100,6 +1175,17 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "http" name = "http"
version = "0.2.12" version = "0.2.12"
@@ -1194,6 +1280,22 @@ dependencies = [
"tower-service", "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]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.19" version = "0.1.19"
@@ -1547,6 +1649,15 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 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]] [[package]]
name = "md-5" name = "md-5"
version = "0.10.6" version = "0.10.6"
@@ -1591,6 +1702,12 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.14" version = "0.2.14"
@@ -1608,6 +1725,27 @@ dependencies = [
"tempfile", "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]] [[package]]
name = "num-bigint" name = "num-bigint"
version = "0.4.6" version = "0.4.6"
@@ -1670,6 +1808,174 @@ dependencies = [
"libm", "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]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.21.3" version = "1.21.3"
@@ -1726,6 +2032,22 @@ dependencies = [
"vcpkg", "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]] [[package]]
name = "p256" name = "p256"
version = "0.13.2" version = "0.13.2"
@@ -1804,6 +2126,26 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 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]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.16" version = "0.2.16"
@@ -2068,6 +2410,44 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 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]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.13.1" version = "0.13.1"
@@ -2152,6 +2532,12 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rustc-demangle"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "2.1.1" version = "2.1.1"
@@ -2347,6 +2733,127 @@ version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 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]] [[package]]
name = "serde" name = "serde"
version = "1.0.228" version = "1.0.228"
@@ -2424,6 +2931,15 @@ dependencies = [
"digest", "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]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@@ -2839,6 +3355,15 @@ dependencies = [
"syn", "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]] [[package]]
name = "time" name = "time"
version = "0.3.45" version = "0.3.45"
@@ -2911,6 +3436,16 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.26.4" version = "0.26.4"
@@ -3002,6 +3537,19 @@ dependencies = [
"tracing-core", "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]] [[package]]
name = "tracing-attributes" name = "tracing-attributes"
version = "0.1.31" version = "0.1.31"
@@ -3020,6 +3568,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [ dependencies = [
"once_cell", "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]] [[package]]
@@ -3034,6 +3612,15 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "uname"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b72f89f0ca32e4db1c04e2a72f5345d59796d4866a1ee0609084569f73683dc8"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.18" version = "0.3.18"
@@ -3079,6 +3666,35 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 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]] [[package]]
name = "url" name = "url"
version = "2.5.8" version = "2.5.8"
@@ -3089,14 +3705,39 @@ dependencies = [
"idna", "idna",
"percent-encoding", "percent-encoding",
"serde", "serde",
"serde_derive",
] ]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "utf8_iter" name = "utf8_iter"
version = "1.0.4" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 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]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@@ -3246,6 +3887,22 @@ dependencies = [
"wasite", "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]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.11" version = "0.1.11"
@@ -3255,6 +3912,12 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "windows-core" name = "windows-core"
version = "0.62.2" version = "0.62.2"

View File

@@ -8,7 +8,11 @@ actix-web = "4.12.1"
chrono = { version = "0.4.43", features = ["serde"] } chrono = { version = "0.4.43", features = ["serde"] }
jsonwebtoken = { version = "10.2.0", features = ["rust_crypto"] } jsonwebtoken = { version = "10.2.0", features = ["rust_crypto"] }
reqwest = { version = "0.13.1", features = ["json"] } reqwest = { version = "0.13.1", features = ["json"] }
sentry = { version = "0.46.1", features = ["actix", "tracing"] }
serde = "1.0.228" serde = "1.0.228"
sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-native-tls"] } sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-native-tls"] }
thiserror = "2.0.17" thiserror = "2.0.17"
tokio = "1.49.0" tokio = "1.49.0"
tracing = "0.1.44"
tracing-actix-web = "0.7.21"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }

View File

@@ -4,13 +4,15 @@ use serde::{Deserialize, Serialize};
use crate::{AppState, account::AccountRepository, error::Result}; use crate::{AppState, account::AccountRepository, error::Result};
#[derive(Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct Repository { struct Repository {
id: u64,
name: String, name: String,
full_name: String, full_name: String,
description: String, description: Option<String>,
language: String, language: Option<String>,
stars: usize, #[serde(alias = "stargazers_count")]
stars: Option<usize>,
updated_at: DateTime<Utc>, updated_at: DateTime<Utc>,
private: bool, private: bool,
} }
@@ -25,11 +27,13 @@ pub async fn get_repos(
let response = app_state let response = app_state
.reqwest_client .reqwest_client
.get("https://api.github.com/user/repos") .get("https://api.github.com/user/repos?affiliation=owner")
.bearer_auth(token) .bearer_auth(token)
.send() .send()
.await?; .await?;
response.error_for_status_ref()?; response.error_for_status_ref()?;
let data = response.json::<Vec<Repository>>().await?;
tracing::debug!(github_response = ?data.iter().filter(|r| r.private == true).collect::<Vec<&Repository>>(), "received repos");
Ok(HttpResponse::Ok().json(response.json::<Vec<Repository>>().await?)) Ok(HttpResponse::Ok().json(data))
} }

View File

@@ -28,7 +28,7 @@ struct ErrorResponse {
impl ResponseError for Error { impl ResponseError for Error {
fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> { fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
match self { match self {
Error::AccessToken => HttpResponse::Unauthorized().finish(), Error::AccessToken => HttpResponse::BadRequest().finish(),
Error::Unauthorized => HttpResponse::Unauthorized().finish(), Error::Unauthorized => HttpResponse::Unauthorized().finish(),
Error::TokenExpired => HttpResponse::Unauthorized().json(ErrorResponse { Error::TokenExpired => HttpResponse::Unauthorized().json(ErrorResponse {
error: "token expired".to_string(), error: "token expired".to_string(),

View File

@@ -6,8 +6,12 @@ mod middleware;
use std::env; 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 sqlx::PgPool;
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{
EnvFilter, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt,
};
use crate::auth::{Auth, JWT}; use crate::auth::{Auth, JWT};
@@ -17,9 +21,15 @@ struct AppState {
auth: Auth, auth: Auth,
} }
#[actix_web::main] async fn run() -> std::io::Result<()> {
async fn main() -> std::io::Result<()> { let reqwest_client = reqwest::Client::builder()
let reqwest_client = reqwest::Client::new(); .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 { let app_data = web::Data::new(AppState {
reqwest_client: reqwest_client.clone(), reqwest_client: reqwest_client.clone(),
pool: PgPool::connect( pool: PgPool::connect(
@@ -33,21 +43,74 @@ async fn main() -> std::io::Result<()> {
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.app_data(app_data.clone()) .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( .route(
"/", "/",
web::get() web::get()
.to(async || concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"))), .to(async || concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"))),
) )
.service( .service(
web::scope("/api") web::scope("/api").service(
web::scope("/v0").service(
web::scope("/user")
.route( .route(
"/{user_id}/repos", "/{user_id}/repos",
web::get().to(endpoints::get_repos::get_repos), web::get().to(endpoints::get_repos::get_repos),
) )
.wrap(from_fn(middleware::protected)), .wrap(from_fn(middleware::protected)),
),
),
) )
}) })
.bind(("127.0.0.1", 8080))? .bind(("127.0.0.1", 8080))?
.run() .run()
.await .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())
}

View File

@@ -13,6 +13,7 @@
"bits-ui": "^2.15.4", "bits-ui": "^2.15.4",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"drizzle-orm": "^0.45.1", "drizzle-orm": "^0.45.1",
"jose": "^6.1.3",
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/adapter-auto": "^7.0.0",
@@ -21,7 +22,6 @@
"@sveltejs/vite-plugin-svelte": "^6.2.1", "@sveltejs/vite-plugin-svelte": "^6.2.1",
"@tailwindcss/vite": "^4.1.17", "@tailwindcss/vite": "^4.1.17",
"drizzle-kit": "^0.31.8", "drizzle-kit": "^0.31.8",
"prettier": "^3.7.4",
"prettier-plugin-svelte": "^3.4.0", "prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.7.2", "prettier-plugin-tailwindcss": "^0.7.2",
"rolldown-vite": "^7.3.1", "rolldown-vite": "^7.3.1",

View File

@@ -8,7 +8,6 @@
"@sveltejs/vite-plugin-svelte": "^6.2.1", "@sveltejs/vite-plugin-svelte": "^6.2.1",
"@tailwindcss/vite": "^4.1.17", "@tailwindcss/vite": "^4.1.17",
"drizzle-kit": "^0.31.8", "drizzle-kit": "^0.31.8",
"prettier": "^3.7.4",
"prettier-plugin-svelte": "^3.4.0", "prettier-plugin-svelte": "^3.4.0",
"prettier-plugin-tailwindcss": "^0.7.2", "prettier-plugin-tailwindcss": "^0.7.2",
"rolldown-vite": "^7.3.1", "rolldown-vite": "^7.3.1",
@@ -38,6 +37,7 @@
"better-auth": "^1.4.12", "better-auth": "^1.4.12",
"bits-ui": "^2.15.4", "bits-ui": "^2.15.4",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"drizzle-orm": "^0.45.1" "drizzle-orm": "^0.45.1",
"jose": "^6.1.3"
} }
} }

View File

@@ -4,37 +4,24 @@
import type { Repository } from './types/repo'; import type { Repository } from './types/repo';
import { createQuery } from '@tanstack/svelte-query'; import { createQuery } from '@tanstack/svelte-query';
import { authClient } from './auth-client'; import { authClient } from './auth-client';
import { apiClient } from './api-client';
const session = authClient.useSession(); const session = authClient.useSession();
const query = createQuery(() => ({ const query = createQuery(() => ({
queryKey: ['github-repositories'], queryKey: ['github-repositories'],
queryFn: async () => { queryFn: async () => {
const response = await fetch(`/api/${$session.data?.user.id}/repos`); return await apiClient.request<Repository[]>(`/api/v0/user/${$session.data?.user.id}/repos`);
return await response.json(); },
} 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 searchQuery = $state('');
let adding = $state<number | null>(null); let adding = $state<number | null>(null);
const filteredRepositories = $derived( const filteredRepositories = $derived(
mockRepositories.filter( (query.data ?? []).filter(
(repo) => (repo) =>
repo.name.toLowerCase().includes(searchQuery.toLowerCase()) || repo.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
repo.description?.toLowerCase().includes(searchQuery.toLowerCase()) repo.description?.toLowerCase().includes(searchQuery.toLowerCase())
@@ -110,7 +97,7 @@
{/if} {/if}
<span class="flex items-center gap-1"> <span class="flex items-center gap-1">
<Star class="h-3.5 w-3.5" /> <Star class="h-3.5 w-3.5" />
{repo.stars} {repo.stars ?? 0}
</span> </span>
<span class="flex items-center gap-1"> <span class="flex items-center gap-1">
<Clock class="h-3.5 w-3.5" /> <Clock class="h-3.5 w-3.5" />

50
src/lib/api-client.ts Normal file
View File

@@ -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<T>(url: string, options: RequestInit = {}): Promise<T> {
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();

View File

@@ -1,7 +1,7 @@
import { jwt } from 'better-auth/plugins'; import { jwtClient } from 'better-auth/client/plugins';
import { createAuthClient } from 'better-auth/svelte'; import { createAuthClient } from 'better-auth/svelte';
export const authClient = createAuthClient({ export const authClient = createAuthClient({
baseURL: 'http://localhost:5173', baseURL: 'http://localhost:5173',
plugins: [jwt()] plugins: [jwtClient()]
}); });

View File

@@ -2,9 +2,9 @@ export type Repository = {
id: number; id: number;
name: string; name: string;
fullName: string; fullName: string;
description: string | null; description?: string | null;
language: string | null; language?: string | null;
stars: number; stars?: number;
updatedAt: string; updatedAt: string;
isPrivate: boolean; private: boolean;
}; };

View File

@@ -6,10 +6,10 @@ export default defineConfig({
plugins: [tailwindcss(), sveltekit()], plugins: [tailwindcss(), sveltekit()],
server: { server: {
proxy: { proxy: {
// '/api': { '/api/v0': {
// target: 'http://localhost:8080', target: 'http://localhost:8080',
// changeOrigin: true changeOrigin: true
// } }
} }
} }
}); });