diff --git a/.forgejo/workflows/pipeline.yaml b/.forgejo/workflows/pipeline.yaml index 735c2ef..fdcd8f5 100644 --- a/.forgejo/workflows/pipeline.yaml +++ b/.forgejo/workflows/pipeline.yaml @@ -11,31 +11,6 @@ jobs: with: node-version: 24 - uses: actions/checkout@v6 - - name: Cache Node.js modules - uses: actions/cache@v4 - with: - path: frontend/node_modules - key: ${{ runner.os }}-node-${{ hashFiles('frontend/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - name: Cache Cargo registry - uses: actions/cache@v4 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo- - - name: Cache Cargo target - uses: actions/cache@v4 - with: - path: target/ - key: ${{ runner.os }}-cargo-target-${{ hashFiles('Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo-target- - run: | cd frontend npm install diff --git a/.gitignore b/.gitignore index 9bf488d..771536c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /target node_modules -/frontend/dist -.env \ No newline at end of file +/frontend/dist \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index b8d0c9c..8030dcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -93,29 +93,12 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "anyhow" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" - [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -178,10 +161,10 @@ checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core", - "base64 0.22.1", + "base64", "bytes", "futures-util", - "http 1.4.0", + "http", "http-body", "http-body-util", "hyper", @@ -216,7 +199,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.4.0", + "http", "http-body", "http-body-util", "mime", @@ -238,10 +221,10 @@ dependencies = [ "axum-core", "bytes", "cookie", - "fastrand 2.3.0", + "fastrand", "futures-util", "headers", - "http 1.4.0", + "http", "http-body", "http-body-util", "mime", @@ -259,18 +242,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -297,18 +268,6 @@ dependencies = [ "serde", ] -[[package]] -name = "binstring" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0669d5a35b64fdb5ab7fb19cae13148b6b5cbdf4b8247faf54ece47f699c8cef" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.10.0" @@ -368,12 +327,11 @@ version = "0.1.0" dependencies = [ "axum", "axum-extra", - "base64 0.22.1", + "base64", "chrono", "cookie", "dashmap", "dotenvy", - "isahc", "jsonwebtoken", "migration", "reqwest", @@ -388,7 +346,6 @@ dependencies = [ "tracing", "tracing-subscriber", "uuid", - "web-push", ] [[package]] @@ -431,12 +388,6 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - [[package]] name = "cc" version = "1.2.55" @@ -523,17 +474,6 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" -[[package]] -name = "coarsetime" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58eb270476aa4fc7843849f8a35063e8743b4dbcdf6dd0f8ea0886980c204c2" -dependencies = [ - "libc", - "wasix", - "wasm-bindgen", -] - [[package]] name = "colorchoice" version = "1.0.4" @@ -549,12 +489,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const-oid" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" - [[package]] name = "const-oid" version = "0.9.6" @@ -660,43 +594,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ct-codecs" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b10589d1a5e400d61f9f38f12f884cfd080ff345de8f17efda36fe0e4a02aa8" - -[[package]] -name = "curl" -version = "0.4.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79fc3b6dd0b87ba36e565715bf9a2ced221311db47bd18011676f24a6066edbc" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "windows-sys 0.59.0", -] - -[[package]] -name = "curl-sys" -version = "0.4.85+curl-8.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0efa6142b5ecc05f6d3eaa39e6af4888b9d3939273fb592c92b7088a8cf3fdb" -dependencies = [ - "cc", - "libc", - "libnghttp2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "windows-sys 0.59.0", -] - [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -779,50 +676,17 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" -[[package]] -name = "der" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" -dependencies = [ - "const-oid 0.6.2", - "der_derive", -] - -[[package]] -name = "der" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" -dependencies = [ - "const-oid 0.9.6", - "pem-rfc7468 0.6.0", - "zeroize", -] - [[package]] name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid 0.9.6", - "pem-rfc7468 0.7.0", + "const-oid", + "pem-rfc7468", "zeroize", ] -[[package]] -name = "der_derive" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aed3b3c608dc56cf36c45fe979d04eda51242e6703d8d0bb03426ef7c41db6a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "synstructure 0.12.6", -] - [[package]] name = "deranged" version = "0.5.5" @@ -893,7 +757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid 0.9.6", + "const-oid", "crypto-common", "subtle", ] @@ -921,30 +785,12 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der 0.7.10", + "der", "digest", "elliptic-curve", "rfc6979", - "signature 2.2.0", - "spki 0.7.3", -] - -[[package]] -name = "ece" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ea1d2f2cc974957a4e2575d8e5bb494549bab66338d6320c2789abcfff5746" -dependencies = [ - "base64 0.21.7", - "byteorder", - "hex", - "hkdf", - "lazy_static", - "once_cell", - "openssl", - "serde", - "sha2", - "thiserror 1.0.69", + "signature", + "spki", ] [[package]] @@ -953,18 +799,8 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8 0.10.2", - "signature 2.2.0", -] - -[[package]] -name = "ed25519-compact" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ce99a9e19c84beb4cc35ece85374335ccc398240712114c85038319ed709bd" -dependencies = [ - "ct-codecs", - "getrandom 0.3.4", + "pkcs8", + "signature", ] [[package]] @@ -1003,8 +839,8 @@ dependencies = [ "generic-array", "group", "hkdf", - "pem-rfc7468 0.7.0", - "pkcs8 0.10.2", + "pem-rfc7468", + "pkcs8", "rand_core", "sec1", "subtle", @@ -1047,12 +883,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "event-listener" version = "5.4.1" @@ -1064,15 +894,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.3.0" @@ -1212,34 +1033,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand 2.3.0", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-sink" version = "0.3.31" @@ -1297,11 +1090,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi", "wasip2", - "wasm-bindgen", ] [[package]] @@ -1332,7 +1123,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http", "indexmap", "slab", "tokio", @@ -1387,10 +1178,10 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "headers-core", - "http 1.4.0", + "http", "httpdate", "mime", "sha1", @@ -1402,7 +1193,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.4.0", + "http", ] [[package]] @@ -1441,30 +1232,6 @@ dependencies = [ "digest", ] -[[package]] -name = "hmac-sha1-compact" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b3ba31f6dc772cc8221ce81dbbbd64fa1e668255a6737d95eeace59b5a8823" - -[[package]] -name = "hmac-sha256" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f0ae375a85536cac3a243e3a9cda80a47910348abdea7e2c22f8ec556d586d" -dependencies = [ - "digest", -] - -[[package]] -name = "hmac-sha512" -version = "1.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5075d41b75a022af043a5bbf49b89abf17665d5aebf6f6ec64ffff207d87654d" -dependencies = [ - "digest", -] - [[package]] name = "home" version = "0.5.12" @@ -1474,17 +1241,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.4.0" @@ -1502,7 +1258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http", ] [[package]] @@ -1513,7 +1269,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http", "http-body", "pin-project-lite", ] @@ -1541,7 +1297,7 @@ dependencies = [ "futures-channel", "futures-core", "h2", - "http 1.4.0", + "http", "http-body", "httparse", "httpdate", @@ -1559,7 +1315,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.4.0", + "http", "hyper", "hyper-util", "rustls", @@ -1591,11 +1347,11 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", - "http 1.4.0", + "http", "http-body", "hyper", "ipnet", @@ -1763,15 +1519,6 @@ dependencies = [ "syn 2.0.114", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -1794,33 +1541,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "isahc" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" -dependencies = [ - "async-channel", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "event-listener 2.5.3", - "futures-lite 1.13.0", - "http 0.2.12", - "log", - "mime", - "once_cell", - "polling", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - [[package]] name = "itoa" version = "1.0.17" @@ -1843,63 +1563,23 @@ version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" dependencies = [ - "base64 0.22.1", + "base64", "ed25519-dalek", "getrandom 0.2.17", "hmac", "js-sys", "p256", "p384", - "pem 3.0.6", + "pem", "rand", - "rsa 0.9.10", + "rsa", "serde", "serde_json", "sha2", - "signature 2.2.0", + "signature", "simple_asn1", ] -[[package]] -name = "jwt-simple" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357892bb32159d763abdea50733fadcb9a8e1c319a9aa77592db8555d05af83e" -dependencies = [ - "anyhow", - "binstring", - "coarsetime", - "ct-codecs", - "ed25519-compact", - "hmac-sha1-compact", - "hmac-sha256", - "hmac-sha512", - "k256", - "p256", - "p384", - "rand", - "rsa 0.7.2", - "serde", - "serde_json", - "spki 0.6.0", - "thiserror 1.0.69", - "zeroize", -] - -[[package]] -name = "k256" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "once_cell", - "sha2", - "signature 2.2.0", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -1921,23 +1601,13 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libnghttp2-sys" -version = "0.1.11+1.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6c24e48a7167cffa7119da39d577fa482e66c688a4aac016bee862e1a713c4" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "libredox" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.10.0", + "bitflags", "libc", "redox_syscall 0.7.0", ] @@ -1952,18 +1622,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libz-sys" -version = "1.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -2055,7 +1713,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.4.0", + "http", "httparse", "memchr", "mime", @@ -2086,7 +1744,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2180,7 +1838,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.10.0", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -2304,36 +1962,16 @@ dependencies = [ "windows-link", ] -[[package]] -name = "pem" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" -dependencies = [ - "base64 0.13.1", - "once_cell", - "regex", -] - [[package]] name = "pem" version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64 0.22.1", + "base64", "serde_core", ] -[[package]] -name = "pem-rfc7468" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" -dependencies = [ - "base64ct", -] - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2376,26 +2014,6 @@ dependencies = [ "siphasher", ] -[[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 2.0.114", -] - [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2408,37 +2026,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs1" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" -dependencies = [ - "der 0.6.1", - "pkcs8 0.9.0", - "spki 0.6.0", - "zeroize", -] - [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der 0.7.10", - "pkcs8 0.10.2", - "spki 0.7.3", -] - -[[package]] -name = "pkcs8" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" -dependencies = [ - "der 0.6.1", - "spki 0.6.0", + "der", + "pkcs8", + "spki", ] [[package]] @@ -2447,8 +2043,8 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.10", - "spki 0.7.3", + "der", + "spki", ] [[package]] @@ -2457,22 +2053,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - [[package]] name = "potential_utf" version = "0.1.4" @@ -2636,7 +2216,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags", ] [[package]] @@ -2645,7 +2225,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ - "bitflags 2.10.0", + "bitflags", ] [[package]] @@ -2692,12 +2272,12 @@ version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", "h2", - "http 1.4.0", + "http", "http-body", "http-body-util", "hyper", @@ -2779,43 +2359,22 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "rsa" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c" -dependencies = [ - "byteorder", - "digest", - "num-bigint-dig", - "num-integer", - "num-iter", - "num-traits", - "pkcs1 0.4.1", - "pkcs8 0.9.0", - "rand_core", - "signature 1.6.4", - "smallvec", - "subtle", - "zeroize", -] - [[package]] name = "rsa" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid 0.9.6", + "const-oid", "digest", "num-bigint-dig", "num-integer", "num-traits", - "pkcs1 0.7.5", - "pkcs8 0.10.2", + "pkcs1", + "pkcs8", "rand_core", - "signature 2.2.0", - "spki 0.7.3", + "signature", + "spki", "subtle", "zeroize", ] @@ -2851,7 +2410,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -3091,31 +2650,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", - "der 0.7.10", + "der", "generic-array", - "pkcs8 0.10.2", + "pkcs8", "subtle", "zeroize", ] -[[package]] -name = "sec1_decode" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6326ddc956378a0739200b2c30892dccaf198992dfd7323274690b9e188af23" -dependencies = [ - "der 0.4.5", - "pem 0.8.3", - "thiserror 1.0.69", -] - [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.10.0", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -3251,16 +2799,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" -dependencies = [ - "digest", - "rand_core", -] - [[package]] name = "signature" version = "2.2.0" @@ -3301,17 +2839,6 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - [[package]] name = "smallvec" version = "1.15.1" @@ -3340,16 +2867,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" -dependencies = [ - "base64ct", - "der 0.6.1", -] - [[package]] name = "spki" version = "0.7.3" @@ -3357,7 +2874,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der 0.7.10", + "der", ] [[package]] @@ -3379,14 +2896,14 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64 0.22.1", + "base64", "bigdecimal", "bytes", "chrono", "crc", "crossbeam-queue", "either", - "event-listener 5.4.1", + "event-listener", "futures-core", "futures-intrusive", "futures-io", @@ -3459,9 +2976,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64 0.22.1", + "base64", "bigdecimal", - "bitflags 2.10.0", + "bitflags", "byteorder", "bytes", "chrono", @@ -3484,7 +3001,7 @@ dependencies = [ "once_cell", "percent-encoding", "rand", - "rsa 0.9.10", + "rsa", "rust_decimal", "serde", "sha1", @@ -3506,9 +3023,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64 0.22.1", + "base64", "bigdecimal", - "bitflags 2.10.0", + "bitflags", "byteorder", "chrono", "crc", @@ -3662,18 +3179,6 @@ dependencies = [ "futures-core", ] -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", -] - [[package]] name = "synstructure" version = "0.13.2" @@ -3691,7 +3196,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.10.0", + "bitflags", "core-foundation", "system-configuration-sys", ] @@ -3718,7 +3223,7 @@ version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ - "fastrand 2.3.0", + "fastrand", "getrandom 0.3.4", "once_cell", "rustix", @@ -3982,9 +3487,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bytes", - "http 1.4.0", + "http", "http-body", "http-body-util", "pin-project-lite", @@ -3998,10 +3503,10 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bytes", "futures-util", - "http 1.4.0", + "http", "http-body", "iri-string", "pin-project-lite", @@ -4055,16 +3560,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -4109,7 +3604,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.4.0", + "http", "httparse", "log", "rand", @@ -4223,12 +3718,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - [[package]] name = "want" version = "0.3.1" @@ -4259,15 +3748,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" -[[package]] -name = "wasix" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1757e0d1f8456693c7e5c6c629bdb54884e032aa0bb53c155f6a39f94440d332" -dependencies = [ - "wasi", -] - [[package]] name = "wasm-bindgen" version = "0.2.108" @@ -4327,28 +3807,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "web-push" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f2332e5400bb42c21bcab3ca2cd3400ab4b1d5ecbe276b533ce9acb59c56602" -dependencies = [ - "async-trait", - "base64 0.13.1", - "chrono", - "ece", - "futures-lite 2.6.1", - "http 0.2.12", - "isahc", - "jwt-simple", - "log", - "pem 3.0.6", - "sec1_decode", - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "web-sys" version = "0.3.85" @@ -4475,15 +3933,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" @@ -4744,7 +4193,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.114", - "synstructure 0.13.2", + "synstructure", ] [[package]] @@ -4785,7 +4234,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.114", - "synstructure 0.13.2", + "synstructure", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9878cd9..e7a13dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,6 @@ tokio-cron-scheduler = "0.15.1" dashmap = "6.1.0" jsonwebtoken = { version = "10.3.0", features = ["rsa", "rust_crypto"] } base64 = "0.22.1" -web-push = { version = "0.10.0", features = ["isahc-client"] } -isahc = "1.7" axum-extra = { version = "0.9", features = ["typed-header", "cookie"] } cookie = "0.18" thiserror = "2.0.18" diff --git a/frontend/index.html b/frontend/index.html index edcefbf..c9b08bb 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,13 +3,9 @@ - - - - - - + Antigravity Agent Dashboard + Re-run Task - diff --git a/frontend/public/apple-touch-icon.png b/frontend/public/apple-touch-icon.png deleted file mode 100644 index a5ea0e4..0000000 Binary files a/frontend/public/apple-touch-icon.png and /dev/null differ diff --git a/frontend/public/icon-192.png b/frontend/public/icon-192.png deleted file mode 100644 index b23b3c7..0000000 Binary files a/frontend/public/icon-192.png and /dev/null differ diff --git a/frontend/public/icon-512.png b/frontend/public/icon-512.png deleted file mode 100644 index 2905528..0000000 Binary files a/frontend/public/icon-512.png and /dev/null differ diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json deleted file mode 100644 index 5978a0c..0000000 --- a/frontend/public/manifest.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "Antigravity Agency Dashboard", - "short_name": "Agency", - "description": "Autonomous AI Agent Dashboard", - "start_url": "/", - "display": "standalone", - "background_color": "#0a0a0c", - "theme_color": "#5d5dff", - "icons": [ - { - "src": "icon-192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "icon-512.png", - "sizes": "512x512", - "type": "image/png" - } - ] -} \ No newline at end of file diff --git a/frontend/public/sw.js b/frontend/public/sw.js deleted file mode 100644 index e976c34..0000000 --- a/frontend/public/sw.js +++ /dev/null @@ -1,119 +0,0 @@ -const CACHE_NAME = 'agency-cache-v4'; -const ASSETS = [ - '/', - '/index.html', - '/manifest.json', - '/icon-192.png', - '/icon-512.png' -]; - -// Force immediate update to the latest SW -self.addEventListener('install', (event) => { - event.waitUntil( - caches.open(CACHE_NAME).then((cache) => { - return cache.addAll(ASSETS); - }).then(() => self.skipWaiting()) - ); -}); - -// Clean up old caches and take control of all clients immediately -self.addEventListener('activate', (event) => { - event.waitUntil( - caches.keys().then((cacheNames) => { - return Promise.all( - cacheNames.map((cacheName) => { - if (cacheName !== CACHE_NAME) { - console.log('Deleting old cache:', cacheName); - return caches.delete(cacheName); - } - }) - ); - }).then(() => self.clients.claim()) - ); -}); - -self.addEventListener('fetch', (event) => { - // Only intercept http/https requests - if (!event.request.url.startsWith('http')) return; - - event.respondWith( - caches.match(event.request).then((cachedResponse) => { - if (cachedResponse) { - return cachedResponse; - } - - return fetch(event.request).catch((error) => { - // If network fetch fails and it's a navigation request, return index.html - if (event.request.mode === 'navigate') { - return caches.match('/index.html'); - } - - // For assets, return a failure response instead of throwing. - // Re-throwing (or returning a rejected promise) causes the browser to show - // the "unexpected error" interception UI. - console.warn('Fetch failed for:', event.request.url, error); - - return new Response('Network error occurred', { - status: 503, - statusText: 'Service Unavailable', - headers: new Headers({ 'Content-Type': 'text/plain' }) - }); - }); - }) - ); -}); - -self.addEventListener('push', (event) => { - let data = { title: 'Notification', body: 'New update from Agency' }; - try { - if (event.data) { - data = event.data.json(); - } - } catch (e) { - console.error('Error parsing push data:', e); - } - - const options = { - body: data.body, - icon: '/icon-192.png', - badge: '/icon-192.png', - vibrate: [100, 50, 100], - data: { - dateOfArrival: Date.now(), - primaryKey: '1', - taskId: data.task_id, - runId: data.run_id - } - }; - - event.waitUntil( - self.registration.showNotification(data.title, options) - ); -}); - -self.addEventListener('notificationclick', (event) => { - event.notification.close(); - - const taskId = event.notification.data.taskId; - const runId = event.notification.data.runId; - - let url = '/'; - if (taskId && runId) { - url = `/?taskId=${taskId}&runId=${runId}`; - } - - event.waitUntil( - clients.matchAll({ type: 'window', includeUncontrolled: true }).then((windowClients) => { - // Check if there is already a window open and focus it, or open a new one - for (let client of windowClients) { - if ('focus' in client) { - // Navigate the existing client to the new URL if it's the same app - return client.navigate(url).then(c => c.focus()); - } - } - if (clients.openWindow) { - return clients.openWindow(url); - } - }) - ); -}); diff --git a/frontend/src/main.js b/frontend/src/main.js index ffae909..edf3959 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,6 +1,5 @@ import { marked } from 'marked'; import DOMPurify from 'dompurify'; -import './style.css'; const API_URL = '/api'; // These should ideally be environment-specific @@ -19,8 +18,7 @@ const state = { isEditing: false, isAuthenticated: false, chatMessages: [], - activeDashboardTab: 'chat', // 'chat' or 'activity' - swRegistration: null + activeDashboardTab: 'chat' // 'chat' or 'activity' }; // DOM elements const loginOverlay = document.getElementById('login-overlay'); @@ -159,16 +157,6 @@ async function fetchTasks() { const response = await fetchWithAuth(`${API_URL}/tasks`); const newTasks = await response.json(); - // Check for deep link in URL - const params = new URLSearchParams(window.location.search); - const urlTaskId = params.get('taskId'); - const urlRunId = params.get('runId'); - - if (urlTaskId && !state.selectedTaskId) { - state.selectedTaskId = urlTaskId; - state.selectedRunId = urlRunId; - } - // Check if we should follow the latest run let newSelectedRunId = state.selectedRunId; if (state.selectedTaskId) { @@ -189,12 +177,6 @@ async function fetchTasks() { tasks: newTasks, selectedRunId: newSelectedRunId }); - - // If we just loaded from a deep link, clear the params and select it - if (urlTaskId) { - window.history.replaceState({}, document.title, "/"); - selectTask(urlTaskId, urlRunId); - } } catch (error) { console.error('Error fetching tasks:', error); } @@ -262,19 +244,6 @@ function selectTask(id, runId = null) { renderRunHistory(task); showTaskView(task); - // Fetch and update subscription status - fetch(`${API_URL}/tasks/${id}/subscription`) - .then(res => res.json()) - .then(data => { - const btn = document.getElementById('notify-task-btn'); - if (data.isSubscribed) { - btn.classList.add('notified'); - } else { - btn.classList.remove('notified'); - } - }) - .catch(err => console.error('Failed to fetch subscription status', err)); - // Close sidebar on mobile after selection if (window.innerWidth <= 768) { closeMobileMenu(); @@ -759,14 +728,7 @@ async function handleCallback() { const response = await fetch(`${API_URL}/auth/callback?code=${code}&redirect_uri=${encodeURIComponent(AUTH_CONFIG.redirectUri)}`, { credentials: 'include' }); - const contentType = response.headers.get("content-type"); - let data; - if (contentType && contentType.includes("application/json")) { - data = await response.json(); - } else { - const text = await response.text(); - throw new Error(`Expected JSON but got ${contentType}. Body: ${text.substring(0, 100)}`); - } + const data = await response.json(); if (response.ok) { state.isAuthenticated = true; @@ -822,16 +784,6 @@ async function initializeApp() { if (hasSession) { appEl.classList.remove('hidden'); loginOverlay.classList.add('hidden'); - - // Handle deep links from notifications - const params = new URLSearchParams(window.location.search); - const taskId = params.get('taskId'); - const runId = params.get('runId'); - if (taskId) { - state.selectedTaskId = taskId; - state.selectedRunId = runId; - } - await fetchTasks(); connectWebSocket(); } else { @@ -845,116 +797,3 @@ if (window.location.pathname === '/callback' || window.location.search.includes( } else { initializeApp(); } - -// Register Service Worker for PWA -if ('serviceWorker' in navigator) { - window.addEventListener('load', () => { - navigator.serviceWorker.register('/sw.js') - .then(reg => { - console.log('SW registered', reg); - state.swRegistration = reg; - }) - .catch(err => { - console.error('SW registration failed:', err); - if (window.isSecureContext === false) { - console.error('Context is NOT secure. Service Workers require HTTPS or localhost.'); - } - }); - }); -} - -async function setupPush() { - if (!state.swRegistration) { - console.warn('SW registration not available'); - return false; - } - - try { - const vapidResponse = await fetch(`${API_URL}/notifications/vapid-key`); - const { publicKey } = await vapidResponse.json(); - - // Always clear existing subscription to ensure we use latest VAPID key - const existingSub = await state.swRegistration.pushManager.getSubscription(); - if (existingSub) { - await existingSub.unsubscribe(); - console.log('Unsubscribed existing push subscription'); - } - - const subscription = await state.swRegistration.pushManager.subscribe({ - userVisibleOnly: true, - applicationServerKey: urlBase64ToUint8Array(publicKey) - }); - - await fetch(`${API_URL}/notifications/register`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - endpoint: subscription.endpoint, - p256dh: b64(subscription.getKey('p256dh')), - auth: b64(subscription.getKey('auth')) - }) - }); - console.log('Push registered'); - return true; - } catch (err) { - console.warn('Push registration failed:', err); - return false; - } -} - -function b64(buffer) { - const binary = String.fromCharCode.apply(null, new Uint8Array(buffer)); - return btoa(binary) - .replace(/\+/g, '-') - .replace(/\//g, '_') - .replace(/=/g, ''); -} - -function urlBase64ToUint8Array(base64String) { - const padding = '='.repeat((4 - base64String.length % 4) % 4); - const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/'); - const rawData = window.atob(base64); - const outputArray = new Uint8Array(rawData.length); - for (let i = 0; i < rawData.length; ++i) { - outputArray[i] = rawData.charCodeAt(i); - } - return outputArray; -} - -async function toggleTaskSubscription(taskId) { - const btn = document.getElementById('notify-task-btn'); - const isNotified = btn.classList.contains('notified'); - const method = isNotified ? 'DELETE' : 'POST'; - - // If trying to enable but no push subscription, try setting it up first (user gesture here) - if (!isNotified && 'Notification' in window) { - if (Notification.permission !== 'granted') { - const permission = await Notification.requestPermission(); - if (permission !== 'granted') { - showToast('Notification permission denied', 'error'); - return; - } - } - - const sub = await state.swRegistration.pushManager.getSubscription(); - if (!sub) { - const success = await setupPush(); - if (!success) { - showToast('Failed to initialize push notifications', 'error'); - return; - } - } - } - - try { - await fetch(`${API_URL}/tasks/${taskId}/subscribe`, { method }); - btn.classList.toggle('notified'); - showToast(isNotified ? 'Notifications disabled' : 'Notifications enabled'); - } catch (err) { - showToast('Failed to update notifications'); - } -} - -document.getElementById('notify-task-btn').addEventListener('click', () => { - if (state.selectedTaskId) toggleTaskSubscription(state.selectedTaskId); -}); diff --git a/frontend/src/style.css b/frontend/src/style.css index c2c08b6..048ad46 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -25,7 +25,6 @@ body { color: var(--text-main); line-height: 1.5; height: 100vh; - height: 100dvh; overflow: hidden; } @@ -1050,8 +1049,8 @@ textarea:focus { .menu-toggle { display: none; position: fixed; - top: calc(16px + env(safe-area-inset-top)); - right: calc(16px + env(safe-area-inset-right)); + top: 16px; + right: 16px; z-index: 1100; background: var(--primary); border: none; @@ -1101,37 +1100,6 @@ textarea:focus { } /* Responsive Styles */ -#notify-task-btn.notified { - color: var(--primary); - background: var(--primary-glow); -} - -#notify-task-btn.notified .notify-icon { - animation: ring 0.5s ease; -} - -@keyframes ring { - 0% { - transform: rotate(0); - } - - 25% { - transform: rotate(15deg); - } - - 50% { - transform: rotate(-15deg); - } - - 75% { - transform: rotate(10deg); - } - - 100% { - transform: rotate(0); - } -} - @media (max-width: 768px) { .menu-toggle { display: flex; @@ -1145,27 +1113,18 @@ textarea:focus { z-index: 1050; transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 10px 0 30px rgba(0, 0, 0, 0.5); - padding-top: env(safe-area-inset-top); - padding-bottom: env(safe-area-inset-bottom); } .sidebar.open { left: 0; } - .dashboard-view, - .task-view { + .dashboard-view, .task-view { padding: 20px; - padding-top: calc(80px + env(safe-area-inset-top)); - padding-left: max(20px, env(safe-area-inset-left)); - padding-right: max(20px, env(safe-area-inset-right)); - padding-bottom: max(20px, env(safe-area-inset-bottom)); + padding-top: 80px; /* Space for menu button */ height: auto; min-height: 100%; overflow-y: auto; - overflow-x: hidden; - width: 100%; - max-width: 100vw; } .dashboard-header h2 { @@ -1176,15 +1135,12 @@ textarea:focus { flex-direction: column; height: auto; overflow: visible; - width: 100%; - gap: 16px; } .run-history { width: 100%; max-height: 200px; flex-shrink: 0; - margin-bottom: 8px; } .run-details { @@ -1192,14 +1148,6 @@ textarea:focus { height: auto; overflow: visible; gap: 16px; - width: 100%; - } - - .answer-container { - width: 100%; - margin-bottom: 8px; - border-left: none; - border-top: 4px solid var(--primary); } .logs-container { @@ -1214,35 +1162,20 @@ textarea:focus { gap: 16px; } - .header-main { - flex-direction: column; - align-items: flex-start; - gap: 12px; - width: 100%; - } - - .header-main h2 { - font-size: 20px; - line-height: 1.3; - } - .header-actions { width: 100%; - display: flex; - flex-wrap: wrap; + display: grid; + grid-template-columns: 1fr 1fr; gap: 8px; } .header-actions .btn { - flex: 1 1 auto; justify-content: center; } .modal { width: 95%; padding: 20px; - max-height: 90%; - overflow-y: auto; } .preset-group { @@ -1252,34 +1185,4 @@ textarea:focus { .chat-container { height: 500px; } - - .chat-form input, - textarea { - font-size: 16px !important; - } - - /* Force word break for long text in containers */ - .answer-output, - .activity-table td { - word-break: break-word; - } - - .activity-table td { - padding: 12px 10px; - } - - /* Hide Date on very small mobile to prevent table overflow */ - @media (max-width: 480px) { - - .activity-table th:last-child, - .activity-table td:last-child { - display: none; - } - } - - /* Prevent horizontal scroll on the entire app */ - #app { - width: 100vw; - overflow-x: hidden; - } } \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js index a06ef36..a71c6f4 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -4,7 +4,7 @@ export default defineConfig({ server: { proxy: { '/api': { - target: 'http://localhost:3001', + target: 'http://localhost:3000', changeOrigin: true, ws: true, } diff --git a/migration/src/lib.rs b/migration/src/lib.rs index cad08e0..e206140 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -4,7 +4,6 @@ mod m20220101_000001_create_table; mod m20260210_000002_add_answer_column; mod m20260210_000003_separate_runs; mod m20260210_000004_add_cron_column; -mod m20260212_000005_notifications; pub struct Migrator; @@ -16,7 +15,6 @@ impl MigratorTrait for Migrator { Box::new(m20260210_000002_add_answer_column::Migration), Box::new(m20260210_000003_separate_runs::Migration), Box::new(m20260210_000004_add_cron_column::Migration), - Box::new(m20260212_000005_notifications::Migration), ] } } diff --git a/migration/src/m20260212_000005_notifications.rs b/migration/src/m20260212_000005_notifications.rs deleted file mode 100644 index fdfdaac..0000000 --- a/migration/src/m20260212_000005_notifications.rs +++ /dev/null @@ -1,115 +0,0 @@ -use sea_orm_migration::prelude::*; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - // Push Subscriptions table - manager - .create_table( - Table::create() - .table(PushSubscriptions::Table) - .if_not_exists() - .col( - ColumnDef::new(PushSubscriptions::Id) - .uuid() - .not_null() - .primary_key(), - ) - .col( - ColumnDef::new(PushSubscriptions::UserSub) - .string() - .not_null(), - ) - .col( - ColumnDef::new(PushSubscriptions::Endpoint) - .string() - .not_null(), - ) - .col( - ColumnDef::new(PushSubscriptions::P256dh) - .string() - .not_null(), - ) - .col(ColumnDef::new(PushSubscriptions::Auth).string().not_null()) - .col( - ColumnDef::new(PushSubscriptions::CreatedAt) - .timestamp_with_time_zone() - .not_null(), - ) - .to_owned(), - ) - .await?; - - // Task Subscriptions table - manager - .create_table( - Table::create() - .table(TaskSubscriptions::Table) - .if_not_exists() - .col( - ColumnDef::new(TaskSubscriptions::Id) - .uuid() - .not_null() - .primary_key(), - ) - .col( - ColumnDef::new(TaskSubscriptions::UserSub) - .string() - .not_null(), - ) - .col(ColumnDef::new(TaskSubscriptions::TaskId).uuid().not_null()) - .col( - ColumnDef::new(TaskSubscriptions::CreatedAt) - .timestamp_with_time_zone() - .not_null(), - ) - .foreign_key( - ForeignKey::create() - .name("fk-task-subscription-task-id") - .from(TaskSubscriptions::Table, TaskSubscriptions::TaskId) - .to(Tasks::Table, Tasks::Id) - .on_delete(ForeignKeyAction::Cascade), - ) - .to_owned(), - ) - .await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(TaskSubscriptions::Table).to_owned()) - .await?; - manager - .drop_table(Table::drop().table(PushSubscriptions::Table).to_owned()) - .await - } -} - -#[derive(DeriveIden)] -enum PushSubscriptions { - Table, - Id, - UserSub, - Endpoint, - P256dh, - Auth, - CreatedAt, -} - -#[derive(DeriveIden)] -enum TaskSubscriptions { - Table, - Id, - UserSub, - TaskId, - CreatedAt, -} - -#[derive(DeriveIden)] -enum Tasks { - Table, - Id, -} diff --git a/openapi/calendar/openapi.json b/openapi/calendar/openapi.json deleted file mode 100644 index 8c8908c..0000000 --- a/openapi/calendar/openapi.json +++ /dev/null @@ -1,593 +0,0 @@ -{ - "openapi": "3.1.0", - "info": { - "title": "calendar", - "description": "", - "license": { - "name": "" - }, - "version": "0.1.0" - }, - "paths": { - "/auth/me": { - "get": { - "tags": [ - "crate::handlers::auth" - ], - "operationId": "me", - "responses": { - "200": { - "description": "Current user profile", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CurrentUser" - } - } - } - }, - "401": { - "description": "Unauthorized" - } - }, - "security": [ - { - "oidc": [] - } - ] - } - }, - "/events": { - "get": { - "tags": [ - "crate::handlers::event" - ], - "operationId": "list_events", - "parameters": [ - { - "name": "upcoming", - "in": "query", - "required": false, - "schema": { - "type": [ - "boolean", - "null" - ] - } - } - ], - "responses": { - "200": { - "description": "List of events", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Model" - } - } - } - } - }, - "401": { - "description": "Unauthorized" - } - }, - "security": [ - { - "oidc": [] - } - ] - }, - "post": { - "tags": [ - "crate::handlers::event" - ], - "operationId": "create_event", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateEventRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Event created successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Model" - } - } - } - }, - "400": { - "description": "Invalid request payload" - }, - "401": { - "description": "Unauthorized" - } - }, - "security": [ - { - "oidc": [] - } - ] - } - }, - "/events/{id}": { - "get": { - "tags": [ - "crate::handlers::event" - ], - "operationId": "get_event", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Event database id", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Event details", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Model" - } - } - } - }, - "401": { - "description": "Unauthorized" - }, - "404": { - "description": "Event not found" - } - }, - "security": [ - { - "oidc": [] - } - ] - }, - "put": { - "tags": [ - "crate::handlers::event" - ], - "operationId": "update_event", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Event database id", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateEventRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Event updated successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Model" - } - } - } - }, - "400": { - "description": "Invalid request payload" - }, - "401": { - "description": "Unauthorized" - }, - "404": { - "description": "Event not found" - } - }, - "security": [ - { - "oidc": [] - } - ] - }, - "delete": { - "tags": [ - "crate::handlers::event" - ], - "operationId": "delete_event", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Event database id", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "204": { - "description": "Event deleted successfully" - }, - "401": { - "description": "Unauthorized" - }, - "404": { - "description": "Event not found" - } - }, - "security": [ - { - "oidc": [] - } - ] - } - }, - "/service/v1/events": { - "get": { - "tags": [ - "crate::handlers::service" - ], - "operationId": "service_list_events", - "parameters": [ - { - "name": "user_id", - "in": "query", - "required": false, - "schema": { - "type": [ - "integer", - "null" - ], - "format": "int32" - } - }, - { - "name": "upcoming", - "in": "query", - "required": false, - "schema": { - "type": [ - "boolean", - "null" - ] - } - } - ], - "responses": { - "200": { - "description": "List of events", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Model" - } - } - } - } - }, - "401": { - "description": "Unauthorized" - } - }, - "security": [ - { - "oidc": [] - } - ] - }, - "post": { - "tags": [ - "crate::handlers::service" - ], - "operationId": "service_create_event", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ServiceCreateEventRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Event created successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Model" - } - } - } - }, - "400": { - "description": "Invalid request payload" - }, - "401": { - "description": "Unauthorized" - } - }, - "security": [ - { - "oidc": [] - } - ] - } - }, - "/service/v1/events/{id}": { - "get": { - "tags": [ - "crate::handlers::service" - ], - "operationId": "service_get_event", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Event database id", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "200": { - "description": "Event details", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Model" - } - } - } - }, - "401": { - "description": "Unauthorized" - }, - "404": { - "description": "Event not found" - } - }, - "security": [ - { - "oidc": [] - } - ] - }, - "put": { - "tags": [ - "crate::handlers::service" - ], - "operationId": "service_update_event", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Event database id", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ServiceCreateEventRequest" - } - } - }, - "required": true - }, - "responses": { - "200": { - "description": "Event updated successfully", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Model" - } - } - } - }, - "400": { - "description": "Invalid request payload" - }, - "401": { - "description": "Unauthorized" - }, - "404": { - "description": "Event not found" - } - }, - "security": [ - { - "oidc": [] - } - ] - }, - "delete": { - "tags": [ - "crate::handlers::service" - ], - "operationId": "service_delete_event", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "Event database id", - "required": true, - "schema": { - "type": "integer", - "format": "int32" - } - } - ], - "responses": { - "204": { - "description": "Event deleted successfully" - }, - "401": { - "description": "Unauthorized" - }, - "404": { - "description": "Event not found" - } - }, - "security": [ - { - "oidc": [] - } - ] - } - } - }, - "components": { - "schemas": { - "CreateEventRequest": { - "type": "object", - "required": [ - "name", - "from", - "to" - ], - "properties": { - "from": { - "type": "string" - }, - "name": { - "type": "string" - }, - "to": { - "type": "string" - } - } - }, - "CurrentUser": { - "type": "object", - "required": [ - "id", - "sub", - "email", - "name" - ], - "properties": { - "email": { - "type": "string" - }, - "id": { - "type": "integer", - "format": "int32" - }, - "name": { - "type": "string" - }, - "sub": { - "type": "string" - } - } - }, - "Model": { - "type": "object", - "required": [ - "id", - "name", - "from", - "to" - ], - "properties": { - "from": { - "type": "string", - "format": "date-time" - }, - "id": { - "type": "integer", - "format": "int64" - }, - "name": { - "type": "string" - }, - "to": { - "type": "string", - "format": "date-time" - }, - "user_id": { - "type": [ - "integer", - "null" - ], - "format": "int32" - } - } - }, - "ServiceCreateEventRequest": { - "type": "object", - "required": [ - "name", - "from", - "to" - ], - "properties": { - "from": { - "type": "string" - }, - "name": { - "type": "string" - }, - "to": { - "type": "string" - }, - "user_id": { - "type": [ - "integer", - "null" - ], - "format": "int32" - } - } - } - } - }, - "tags": [ - { - "name": "calendar", - "description": "Calendar Management API" - } - ] -} \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index b81a727..c7a714e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,8 +14,6 @@ pub struct Config { pub cookie_secure: bool, pub agent_max_turns: u32, pub agent_max_duration_secs: u64, - pub vapid_private_key: String, - pub calendar_api_url: String, } impl Config { @@ -56,12 +54,6 @@ impl Config { .and_then(|s| s.parse().ok()) .unwrap_or(120); - let vapid_private_key = env::var("VAPID_PRIVATE_KEY") - .map_err(|_| AppError::Config("VAPID_PRIVATE_KEY must be set".into()))?; - - let calendar_api_url = - env::var("CALENDAR_API_URL").unwrap_or_else(|_| "http://localhost:8000".to_string()); - Ok(Config { database_url, port, @@ -74,8 +66,6 @@ impl Config { cookie_secure, agent_max_turns, agent_max_duration_secs, - vapid_private_key, - calendar_api_url, }) } } diff --git a/src/domain/agent/api.rs b/src/domain/agent/api.rs index cbd93a8..f886c34 100644 --- a/src/domain/agent/api.rs +++ b/src/domain/agent/api.rs @@ -75,8 +75,6 @@ pub async fn perform_search( tracing::info!(query = %query, "Performing Tavily web search"); let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) - .connect_timeout(std::time::Duration::from_secs(10)) - .pool_idle_timeout(std::time::Duration::from_secs(60)) .build()?; let response = client .post("https://api.tavily.com/search") diff --git a/src/domain/agent/mod.rs b/src/domain/agent/mod.rs index 8f738cc..1b6dde8 100644 --- a/src/domain/agent/mod.rs +++ b/src/domain/agent/mod.rs @@ -3,7 +3,6 @@ pub mod tools; use chrono::Utc; use sea_orm::DatabaseConnection; -use std::sync::Arc; use std::time::{Duration, Instant}; use self::api::{ChatRequest, ChatResponse, Message, Tool}; @@ -14,8 +13,6 @@ pub struct Agent { url: String, zen_api_key: Option, tavily_api_key: Option, - calendar_client: Arc, - pub user_sub: Option, pub messages: Vec, tools: Option>, logs: String, @@ -29,8 +26,6 @@ impl Agent { db: DatabaseConnection, zen_api_key: Option, tavily_api_key: Option, - calendar_client: Arc, - user_sub: Option, initial_message: String, ) -> AppResult { let intro = format!( @@ -55,31 +50,19 @@ impl Agent { }, ]; - Self::with_messages( - db, - zen_api_key, - tavily_api_key, - calendar_client, - user_sub, - messages, - ) + Self::with_messages(db, zen_api_key, tavily_api_key, messages) } pub fn with_messages( db: DatabaseConnection, zen_api_key: Option, tavily_api_key: Option, - calendar_client: Arc, - user_sub: Option, messages: Vec, ) -> AppResult { let tools = Some(tools::get_tools()); let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(120)) - .connect_timeout(std::time::Duration::from_secs(10)) - .tcp_keepalive(std::time::Duration::from_secs(30)) - .pool_idle_timeout(std::time::Duration::from_secs(60)) .build() .map_err(|e| AppError::Internal(format!("Failed to build HTTP client: {}", e)))?; @@ -89,8 +72,6 @@ impl Agent { url: "https://opencode.ai/zen/v1/chat/completions".to_string(), zen_api_key, tavily_api_key, - calendar_client, - user_sub, messages, tools, logs: String::new(), @@ -122,8 +103,6 @@ impl Agent { return Err(AppError::Internal("Agent run exceeded max turns".into())); } - tracing::info!("Turn {}", turns); - turns += 1; let current_role = self .messages @@ -135,39 +114,17 @@ impl Agent { turns, current_role )); - let assistant_message = - match tokio::time::timeout(Duration::from_secs(180), self.execute_turn()).await { - Ok(res) => res?, - Err(_) => { - tracing::error!("Agent execution turn timed out after 180s"); - return Err(AppError::Internal("Agent execution turn timed out".into())); - } - }; + let assistant_message = self.execute_turn().await?; if let Some(tool_calls) = &assistant_message.tool_calls { - tracing::info!("Assistant tool calls: {:#?}", tool_calls); if tool_calls.iter().any(|tc| tc.function.name == "answer") { finished = true; } } if self.answer.is_some() { - tracing::info!("Answer: {}", self.answer.as_ref().unwrap()); finished = true; } - - if !finished { - self.messages.push(Message { - role: "system".to_string(), - content: Some( - "continue, use the finish tool to submit your final answer".to_string(), - ), - tool_calls: None, - tool_call_id: None, - }); - } - - tracing::info!("Finished turn"); } self.log("\n--- Execution Finished ---"); @@ -175,12 +132,10 @@ impl Agent { } pub async fn execute_turn(&mut self) -> AppResult { - let max_sub_turns = 20; + let max_sub_turns = 10; let mut sub_turns = 0; loop { - tracing::info!("Sub turn {}", sub_turns); - sub_turns += 1; if sub_turns > max_sub_turns { return Err(AppError::Internal( @@ -196,34 +151,27 @@ impl Agent { .message .clone(); - tracing::info!("Assistant message: {:#?}", assistant_message); - self.messages.push(assistant_message.clone()); if let Some(content) = &assistant_message.content { - tracing::info!("Assistant content: {}", content); if !content.is_empty() { self.log(&format!("\nAssistant: {}", content)); } } if let Some(tool_calls) = &assistant_message.tool_calls { - tracing::info!("Assistant tool calls: {:#?}", tool_calls); let mut is_final_cycle = false; let mut final_answer = None; for tool_call in tool_calls { self.log(&format!("Calling tool: {}", tool_call.function.name)); - let (tool_message, is_final, tool_answer) = tools::handle_tool_call( - tool_call, - &self.tavily_api_key, - &self.db, - &self.calendar_client, - self.user_sub.as_deref(), - ) - .await - .map_err(|e| AppError::Internal(format!("Tool execution failed: {}", e)))?; + let (tool_message, is_final, tool_answer) = + tools::handle_tool_call(tool_call, &self.tavily_api_key, &self.db) + .await + .map_err(|e| { + AppError::Internal(format!("Tool execution failed: {}", e)) + })?; if let Some(ans) = tool_answer { self.answer = Some(ans.clone()); @@ -242,7 +190,6 @@ impl Agent { } if is_final_cycle { - tracing::info!("Final answer: {}", final_answer.as_ref().unwrap()); return Ok(Message { role: "assistant".to_string(), content: final_answer.or(assistant_message.content), @@ -265,11 +212,7 @@ impl Agent { tools: self.tools.clone(), }; - let mut request_builder = self - .client - .post(&self.url) - .json(&request) - .timeout(Duration::from_secs(60)); + let mut request_builder = self.client.post(&self.url).json(&request); if let Some(key) = &self.zen_api_key { request_builder = request_builder.header("Authorization", format!("Bearer {}", key)); @@ -279,12 +222,10 @@ impl Agent { let response = request_builder.send().await.map_err(|e| { let duration = start.elapsed(); let is_timeout = e.is_timeout(); - let is_connect = e.is_connect(); tracing::error!( - "Network error after {:?} during LLM call (Timeout: {}, Connect: {}): {:?}", + "Network error after {:?} during LLM call (Timeout: {}): {:?}", duration, is_timeout, - is_connect, e ); AppError::Network(e) diff --git a/src/domain/agent/tools.rs b/src/domain/agent/tools.rs index 2b257c0..bbdff4c 100644 --- a/src/domain/agent/tools.rs +++ b/src/domain/agent/tools.rs @@ -3,7 +3,6 @@ use sea_orm::{ ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter, QueryOrder, QuerySelect, }; use serde::Deserialize; -use std::sync::Arc; use uuid::Uuid; #[derive(Deserialize)] @@ -97,47 +96,6 @@ pub fn get_tools() -> Vec { }), }, }, - Tool { - tool_type: "function".to_string(), - function: FunctionDefinition { - name: "calendar_list_events".to_string(), - description: "List calendar events".to_string(), - parameters: serde_json::json!({ - "type": "object", - "properties": { - "upcoming": { - "type": "boolean", - "description": "If true, only upcoming events will be listed" - } - } - }), - }, - }, - Tool { - tool_type: "function".to_string(), - function: FunctionDefinition { - name: "calendar_create_event".to_string(), - description: "Create a new calendar event".to_string(), - parameters: serde_json::json!({ - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the event" - }, - "from": { - "type": "string", - "description": "Start time in ISO 8601 format (e.g., 2023-10-27T10:00:00Z)" - }, - "to": { - "type": "string", - "description": "End time in ISO 8601 format (e.g., 2023-10-27T11:00:00Z)" - } - }, - "required": ["name", "from", "to"] - }), - }, - }, ] } @@ -145,8 +103,6 @@ pub async fn handle_tool_call( tool_call: &ToolCall, tavily_api_key: &Option, db: &DatabaseConnection, - calendar: &Arc, - user_sub: Option<&str>, ) -> Result<(Message, bool, Option), Box> { let mut answer = None; let name = &tool_call.function.name; @@ -242,34 +198,6 @@ pub async fn handle_tool_call( )); } (out, false) - } else if name == "calendar_list_events" { - let args: serde_json::Value = serde_json::from_str(&tool_call.function.arguments)?; - let upcoming = args["upcoming"].as_bool(); - match calendar - .list_events(user_sub.map(|s| s.to_string()), upcoming) - .await - { - Ok(events) => { - tracing::info!("{:#?}", events); - (serde_json::to_string(&events)?, false) - } - Err(e) => (format!("Error listing events: {}", e), false), - } - } else if name == "calendar_create_event" { - let args: serde_json::Value = serde_json::from_str(&tool_call.function.arguments)?; - let name_val = args["name"].as_str().unwrap_or_default(); - let from_val = args["from"].as_str().unwrap_or_default(); - let to_val = args["to"].as_str().unwrap_or_default(); - match calendar - .create_event(user_sub.map(|s| s.to_string()), name_val, from_val, to_val) - .await - { - Ok(event) => ( - format!("Event created: {}", serde_json::to_string(&event)?), - false, - ), - Err(e) => (format!("Error creating event: {}", e), false), - } } else { (format!("Error: Unknown tool {}", name), false) }; diff --git a/src/domain/auth.rs b/src/domain/auth.rs index a180185..c70e0c7 100644 --- a/src/domain/auth.rs +++ b/src/domain/auth.rs @@ -46,10 +46,7 @@ pub struct JwksVerifier { impl JwksVerifier { pub async fn new(issuer: String, audience: String) -> Result> { - let client = Client::builder() - .timeout(std::time::Duration::from_secs(30)) - .connect_timeout(std::time::Duration::from_secs(10)) - .build()?; + let client = Client::new(); let discovery_url = format!( "{}/.well-known/openid-configuration", issuer.trim_end_matches('/') @@ -126,10 +123,7 @@ impl Authenticator { client_id: String, client_secret: String, ) -> Result> { - let client = Client::builder() - .timeout(std::time::Duration::from_secs(30)) - .connect_timeout(std::time::Duration::from_secs(10)) - .build()?; + let client = Client::new(); let discovery_url = format!( "{}/.well-known/openid-configuration", issuer.trim_end_matches('/') @@ -196,27 +190,4 @@ impl Authenticator { Ok(res) } - - pub async fn client_credentials( - &self, - scope: &str, - ) -> Result> { - let params = [ - ("grant_type", "client_credentials"), - ("client_id", &self.client_id), - ("client_secret", &self.client_secret), - ("scope", scope), - ]; - - let res = self - .client - .post(&self.token_url) - .form(¶ms) - .send() - .await? - .json() - .await?; - - Ok(res) - } } diff --git a/src/domain/calendar/mod.rs b/src/domain/calendar/mod.rs deleted file mode 100644 index c6f49ae..0000000 --- a/src/domain/calendar/mod.rs +++ /dev/null @@ -1,298 +0,0 @@ -use crate::domain::auth::Authenticator; -use crate::error::{AppError, AppResult}; -use chrono::{DateTime, Duration, Utc}; -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use tokio::sync::RwLock; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CalendarEvent { - pub id: Option, - pub name: String, - pub from: DateTime, - pub to: DateTime, - pub user_sub: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CreateEventRequest { - pub name: String, - pub from: String, - pub to: String, - pub user_sub: Option, -} -struct TokenState { - access_token: String, - expires_at: DateTime, -} - -pub struct CalendarClient { - base_url: String, - client: Client, - authenticator: Arc, - token_state: RwLock>, -} - -impl CalendarClient { - pub fn new(base_url: String, authenticator: Arc) -> Self { - let client = Client::builder() - .timeout(std::time::Duration::from_secs(30)) - .connect_timeout(std::time::Duration::from_secs(10)) - .build() - .unwrap_or_else(|_| Client::new()); - - Self { - base_url: base_url.trim_end_matches('/').to_string(), - client, - authenticator, - token_state: RwLock::new(None), - } - } - - async fn get_token(&self) -> AppResult { - { - let state = self.token_state.read().await; - if let Some(token) = &*state { - if token.expires_at > Utc::now() + Duration::seconds(30) { - tracing::debug!("Using cached Calendar API token"); - return Ok(token.access_token.clone()); - } - } - } - - let mut state = self.token_state.write().await; - // Double check after acquiring write lock - if let Some(token) = &*state { - if token.expires_at > Utc::now() + Duration::seconds(30) { - return Ok(token.access_token.clone()); - } - } - - tracing::info!("Refreshing Calendar API token via Client Credentials flow"); - let token_data = self - .authenticator - .client_credentials("profile") - .await - .map_err(|e| AppError::Internal(format!("Failed to get client credentials: {}", e)))?; - - let access_token = token_data["access_token"] - .as_str() - .ok_or_else(|| AppError::Internal("Missing access_token in response".into()))? - .to_string(); - - let expires_in = token_data["expires_in"].as_i64().unwrap_or(3600); - - let expires_at = Utc::now() + Duration::seconds(expires_in); - - *state = Some(TokenState { - access_token: access_token.clone(), - expires_at, - }); - - Ok(access_token) - } - - pub async fn list_events( - &self, - user_sub: Option, - upcoming: Option, - ) -> AppResult> { - let token = self.get_token().await?; - let mut url = format!("{}/service/v1/events", self.base_url); - let mut params = Vec::new(); - if let Some(uid) = &user_sub { - params.push(format!("user_sub={}", uid)); - } - if let Some(u) = upcoming { - params.push(format!("upcoming={}", u)); - } - - if !params.is_empty() { - url.push_str("?"); - url.push_str(¶ms.join("&")); - } - - tracing::info!(method = "GET", %url, "Sending Calendar API request"); - - let res = self - .client - .get(&url) - .bearer_auth(token) - .send() - .await - .map_err(AppError::Network)?; - - let status = res.status(); - tracing::info!(%status, %url, "Received Calendar API response"); - - if !status.is_success() { - let error_body = res.text().await.unwrap_or_default(); - tracing::error!(%status, %url, body = %error_body, "Calendar API request failed"); - return Err(AppError::Internal(format!( - "Failed to list events: {} - {}", - status, error_body - ))); - } - - let body = res - .json() - .await - .map_err(|e| AppError::Internal(e.to_string())); - tracing::info!(%status, %url, body = ?body, "Received Calendar API response"); - body - } - - pub async fn create_event( - &self, - user_sub: Option, - name: &str, - from: &str, - to: &str, - ) -> AppResult { - let token = self.get_token().await?; - let url = format!("{}/service/v1/events", self.base_url); - - let request = CreateEventRequest { - name: name.to_string(), - from: from.to_string(), - to: to.to_string(), - user_sub, - }; - - tracing::info!(method = "POST", %url, "Sending Calendar API request"); - - let res = self - .client - .post(&url) - .bearer_auth(token) - .json(&request) - .send() - .await - .map_err(AppError::Network)?; - - let status = res.status(); - tracing::info!(%status, %url, "Received Calendar API response"); - - if !status.is_success() { - let error_body = res.text().await.unwrap_or_default(); - tracing::error!(%status, %url, body = %error_body, "Calendar API request failed"); - return Err(AppError::Internal(format!( - "Failed to create event: {} - {}", - status, error_body - ))); - } - - res.json() - .await - .map_err(|e| AppError::Internal(e.to_string())) - } - - #[allow(dead_code)] - pub async fn get_event(&self, id: i32) -> AppResult { - let token = self.get_token().await?; - let url = format!("{}/service/v1/events/{}", self.base_url, id); - tracing::info!(method = "GET", %url, "Sending Calendar API request"); - - let res = self - .client - .get(&url) - .bearer_auth(token) - .send() - .await - .map_err(AppError::Network)?; - - let status = res.status(); - tracing::info!(%status, %url, "Received Calendar API response"); - - if !status.is_success() { - let error_body = res.text().await.unwrap_or_default(); - tracing::error!(%status, %url, body = %error_body, "Calendar API request failed"); - return Err(AppError::Internal(format!( - "Failed to get event: {} - {}", - status, error_body - ))); - } - - res.json() - .await - .map_err(|e| AppError::Internal(e.to_string())) - } - - #[allow(dead_code)] - pub async fn update_event( - &self, - id: i32, - user_sub: Option, - name: &str, - from: &str, - to: &str, - ) -> AppResult { - let token = self.get_token().await?; - let url = format!("{}/service/v1/events/{}", self.base_url, id); - - let request = CreateEventRequest { - name: name.to_string(), - from: from.to_string(), - to: to.to_string(), - user_sub, - }; - - tracing::info!(method = "PUT", %url, "Sending Calendar API request"); - - let res = self - .client - .put(&url) - .bearer_auth(token) - .json(&request) - .send() - .await - .map_err(AppError::Network)?; - - let status = res.status(); - tracing::info!(%status, %url, "Received Calendar API response"); - - if !status.is_success() { - let error_body = res.text().await.unwrap_or_default(); - tracing::error!(%status, %url, body = %error_body, "Calendar API request failed"); - return Err(AppError::Internal(format!( - "Failed to update event: {} - {}", - status, error_body - ))); - } - - res.json() - .await - .map_err(|e| AppError::Internal(e.to_string())) - } - - #[allow(dead_code)] - pub async fn delete_event(&self, id: i32) -> AppResult<()> { - let token = self.get_token().await?; - let url = format!("{}/service/v1/events/{}", self.base_url, id); - - tracing::info!(method = "DELETE", %url, "Sending Calendar API request"); - - let res = self - .client - .delete(&url) - .bearer_auth(token) - .send() - .await - .map_err(AppError::Network)?; - - let status = res.status(); - tracing::info!(%status, %url, "Received Calendar API response"); - - if !status.is_success() { - let error_body = res.text().await.unwrap_or_default(); - tracing::error!(%status, %url, body = %error_body, "Calendar API request failed"); - return Err(AppError::Internal(format!( - "Failed to delete event: {} - {}", - status, error_body - ))); - } - - Ok(()) - } -} diff --git a/src/domain/mod.rs b/src/domain/mod.rs index 4a21816..7982060 100644 --- a/src/domain/mod.rs +++ b/src/domain/mod.rs @@ -1,5 +1,3 @@ pub mod agent; pub mod auth; -pub mod calendar; -pub mod notifications; pub mod tasks; diff --git a/src/domain/notifications/mod.rs b/src/domain/notifications/mod.rs deleted file mode 100644 index ec19098..0000000 --- a/src/domain/notifications/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod push; diff --git a/src/domain/notifications/push.rs b/src/domain/notifications/push.rs deleted file mode 100644 index 478fe2a..0000000 --- a/src/domain/notifications/push.rs +++ /dev/null @@ -1,113 +0,0 @@ -use crate::error::AppResult; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; -use web_push::*; - -pub struct PushSender { - pub private_key: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct PushSubscription { - pub endpoint: String, - pub p256dh: String, - pub auth: String, -} - -fn ensure_pem(input: &str) -> String { - let input = input.trim(); - if input.contains("-----BEGIN") { - return input.to_string(); - } - - if input.starts_with("MHc") { - format!( - "-----BEGIN EC PRIVATE KEY-----\n{}\n-----END EC PRIVATE KEY-----", - input - ) - } else { - format!( - "-----BEGIN PRIVATE KEY-----\n{}\n-----END PRIVATE KEY-----", - input - ) - } -} - -impl PushSender { - pub fn new(private_key_pem: &str) -> AppResult { - let pem = ensure_pem(private_key_pem); - // Validate key immediately to catch config errors early - let _ = - VapidSignatureBuilder::from_pem_no_sub(std::io::Cursor::new(&pem)).map_err(|e| { - crate::error::AppError::Internal(format!("Invalid VAPID private key: {}", e)) - })?; - - Ok(Self { private_key: pem }) - } - - pub fn get_public_key(&self) -> AppResult> { - let builder = - VapidSignatureBuilder::from_pem_no_sub(std::io::Cursor::new(&self.private_key)) - .map_err(|e| crate::error::AppError::Internal(e.to_string()))?; - Ok(builder.get_public_key()) - } - - pub async fn send_notification( - &self, - subscription: &PushSubscription, - title: &str, - body: &str, - task_id: Option, - run_id: Option, - ) -> AppResult<()> { - let subscription_info = SubscriptionInfo::new( - subscription.endpoint.clone(), - subscription.p256dh.clone(), - subscription.auth.clone(), - ); - - let builder = - VapidSignatureBuilder::from_pem_no_sub(std::io::Cursor::new(&self.private_key)) - .map_err(|e| crate::error::AppError::Internal(e.to_string()))?; - - let mut builder = builder.add_sub_info(&subscription_info); - builder.add_claim("sub", "mailto:pavel@flegr.me"); - - let vapid_signature = builder - .build() - .map_err(|e| crate::error::AppError::Internal(e.to_string()))?; - - let mut builder = WebPushMessageBuilder::new(&subscription_info); - - builder.set_vapid_signature(vapid_signature); - - let payload = serde_json::to_vec(&serde_json::json!({ - "title": title, - "body": body, - "task_id": task_id, - "run_id": run_id, - })) - .map_err(|e| crate::error::AppError::Internal(e.to_string()))?; - - builder.set_payload(ContentEncoding::Aes128Gcm, &payload); - - let message = builder - .build() - .map_err(|e| crate::error::AppError::Internal(e.to_string()))?; - - let client = IsahcWebPushClient::new() - .map_err(|e| crate::error::AppError::Internal(e.to_string()))?; - - client.send(message).await.map_err(|e| { - tracing::error!("Failed to send push notification: {}", e); - crate::error::AppError::Internal(e.to_string()) - })?; - - tracing::info!( - "Push notification sent successfully to {}", - subscription.endpoint - ); - - Ok(()) - } -} diff --git a/src/domain/tasks.rs b/src/domain/tasks.rs index bd518ef..6e7962d 100644 --- a/src/domain/tasks.rs +++ b/src/domain/tasks.rs @@ -1,5 +1,5 @@ use chrono::Utc; -use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, Set}; +use sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, Set}; use serde::{Deserialize, Serialize}; use std::sync::Arc; use uuid::Uuid; @@ -8,7 +8,6 @@ use crate::config::Config; use crate::domain::agent::Agent; use crate::entities::task::Entity as Task; use crate::entities::task_run::{self, Entity as TaskRun}; -use crate::entities::{push_subscription, task_subscription}; use crate::scheduler::Scheduler; #[derive(Deserialize)] @@ -56,12 +55,11 @@ pub async fn execute_agent_run( db: &DatabaseConnection, _scheduler: &Arc, config: &Arc, - calendar_client: Arc, task_id: Uuid, goal: String, ) -> AppResult { let run_id = Uuid::new_v4(); - tracing::info!(%task_id, %run_id, "Starting background agent execution run"); + tracing::info!(%task_id, %run_id, "Starting agent execution run"); let new_run = task_run::ActiveModel { id: Set(run_id), @@ -93,92 +91,48 @@ pub async fn execute_agent_run( db.clone(), config.zen_api_key.clone(), config.tavily_api_key.clone(), - calendar_client.clone(), - None, goal.clone(), )?; - let db_bg = db.clone(); - let config_bg = config.clone(); - let scheduler_bg = _scheduler.clone(); - let task_id_bg = task_id; - - tokio::spawn(async move { - let (logs, answer, status) = match agent.run(&config_bg).await { - Ok((logs, answer)) => { - tracing::info!(task_id = %task_id_bg, run_id = %run_id, "Agent execution completed successfully"); - (logs, answer, "completed".to_string()) - } - Err(e) => { - tracing::error!(task_id = %task_id_bg, run_id = %run_id, error = %e, "Agent execution failed"); - ( - format!("Execution failed: {}", e), - None, - "failed".to_string(), - ) - } - }; - - let run_update = task_run::ActiveModel { - id: Set(run_id), - logs: Set(logs), - answer: Set(answer), - status: Set(status.clone()), - ..Default::default() - }; - - if let Err(e) = run_update.update(&db_bg).await { - tracing::error!(task_id = %task_id_bg, run_id = %run_id, error = %e, "Failed to update run record"); + let (logs, answer, status) = match agent.run(config).await { + Ok((logs, answer)) => { + tracing::info!(%task_id, %run_id, "Agent execution completed successfully"); + (logs, answer, "completed".to_string()) } - - if let Ok(task_response) = get_task_inner(task_id_bg, &db_bg).await { - let _ = scheduler_bg - .tx - .send(crate::server::notifications::WsEvent::RunFinished( - task_response.clone(), - )); - - // Send Push Notifications to subscribers - if let Ok(subscriptions) = task_subscription::Entity::find() - .filter(task_subscription::Column::TaskId.eq(task_id_bg)) - .all(&db_bg) - .await - { - for sub in subscriptions { - if let Ok(push_subs) = push_subscription::Entity::find() - .filter(push_subscription::Column::UserSub.eq(sub.user_sub.clone())) - .all(&db_bg) - .await - { - for push_sub in push_subs { - let sender = scheduler_bg.push_sender.clone(); - let goal = task_response.goal.clone(); - let status_bg = status.clone(); - let sub_data = crate::domain::notifications::push::PushSubscription { - endpoint: push_sub.endpoint, - p256dh: push_sub.p256dh, - auth: push_sub.auth, - }; - - tokio::spawn(async move { - let _ = sender - .send_notification( - &sub_data, - &format!("Task Completed: {}", status_bg), - &goal, - Some(task_id_bg), - Some(run_id), - ) - .await; - }); - } - } - } - } + Err(e) => { + tracing::error!(%task_id, %run_id, error = %e, "Agent execution failed"); + ( + format!("Execution failed: {}", e), + None, + "failed".to_string(), + ) } - }); + }; - get_task_inner(task_id, db).await + let run: task_run::ActiveModel = TaskRun::find_by_id(run_id) + .one(db) + .await + .map_err(crate::error::AppError::Database)? + .ok_or_else(|| crate::error::AppError::NotFound("Run not found after insert".into()))? + .into(); + + let mut run = run; + run.logs = Set(logs.clone()); + run.answer = Set(answer.clone()); + run.status = Set(status); + + run.update(db) + .await + .map_err(crate::error::AppError::Database)?; + + let task_response = get_task_inner(task_id, db).await?; + let _ = _scheduler + .tx + .send(crate::server::notifications::WsEvent::RunFinished( + task_response.clone(), + )); + + Ok(task_response) } pub async fn get_task_inner(id: Uuid, db: &DatabaseConnection) -> AppResult { diff --git a/src/entities/mod.rs b/src/entities/mod.rs index 304ddb0..7ff42bf 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -1,4 +1,2 @@ -pub mod push_subscription; pub mod task; pub mod task_run; -pub mod task_subscription; diff --git a/src/entities/push_subscription.rs b/src/entities/push_subscription.rs deleted file mode 100644 index c306114..0000000 --- a/src/entities/push_subscription.rs +++ /dev/null @@ -1,19 +0,0 @@ -use sea_orm::entity::prelude::*; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] -#[sea_orm(table_name = "push_subscriptions")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: Uuid, - pub user_sub: String, - pub endpoint: String, - pub p256dh: String, - pub auth: String, - pub created_at: DateTimeWithTimeZone, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/src/entities/task_subscription.rs b/src/entities/task_subscription.rs deleted file mode 100644 index b7011ee..0000000 --- a/src/entities/task_subscription.rs +++ /dev/null @@ -1,32 +0,0 @@ -use sea_orm::entity::prelude::*; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] -#[sea_orm(table_name = "task_subscriptions")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: Uuid, - pub user_sub: String, - pub task_id: Uuid, - pub created_at: DateTimeWithTimeZone, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::task::Entity", - from = "Column::TaskId", - to = "super::task::Column::Id", - on_update = "NoAction", - on_delete = "Cascade" - )] - Task, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Task.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/src/error.rs b/src/error.rs index 81194ce..01943d5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -32,20 +32,16 @@ pub enum AppError { impl IntoResponse for AppError { fn into_response(self) -> Response { - let (status, error_message) = match &self { + let (status, error_message) = match self { AppError::Database(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()), - AppError::Config(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.clone()), - AppError::NotFound(err) => (StatusCode::NOT_FOUND, err.clone()), - AppError::Unauthorized(err) => (StatusCode::UNAUTHORIZED, err.clone()), - AppError::Internal(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.clone()), + AppError::Config(err) => (StatusCode::INTERNAL_SERVER_ERROR, err), + AppError::NotFound(err) => (StatusCode::NOT_FOUND, err), + AppError::Unauthorized(err) => (StatusCode::UNAUTHORIZED, err), + AppError::Internal(err) => (StatusCode::INTERNAL_SERVER_ERROR, err), AppError::Network(err) => (StatusCode::BAD_GATEWAY, err.to_string()), - AppError::InvalidRequest(err) => (StatusCode::BAD_REQUEST, err.clone()), + AppError::InvalidRequest(err) => (StatusCode::BAD_REQUEST, err), }; - if status.is_server_error() || status.is_client_error() { - tracing::error!(%status, error = %self, "AppError converted to response"); - } - let body = Json(json!({ "error": error_message, })); diff --git a/src/scheduler.rs b/src/scheduler.rs index c98debd..9aad91b 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -13,18 +13,14 @@ pub struct Scheduler { db: DatabaseConnection, tasks_to_jobs: DashMap, config: Arc, - pub calendar_client: Arc, pub tx: tokio::sync::broadcast::Sender, - pub push_sender: Arc, } impl Scheduler { pub async fn new( db: DatabaseConnection, config: Arc, - calendar_client: Arc, tx: tokio::sync::broadcast::Sender, - push_sender: Arc, ) -> AppResult { let scheduler = JobScheduler::new() .await @@ -38,9 +34,7 @@ impl Scheduler { db, tasks_to_jobs: DashMap::new(), config, - calendar_client, tx, - push_sender, }) } @@ -53,19 +47,13 @@ impl Scheduler { let db = self.db.clone(); let config = self.config.clone(); let tx = self.tx.clone(); - let push_sender = self.push_sender.clone(); - let calendar_client = self.calendar_client.clone(); let job = Job::new_async(cron_expr, move |_uuid, _l| { let db = db.clone(); let config = config.clone(); let tx = tx.clone(); - let push_sender = push_sender.clone(); - let calendar_client = calendar_client.clone(); Box::pin(async move { - if let Err(e) = - Self::run_task(db, config, calendar_client, tx, push_sender, task_id).await - { + if let Err(e) = Self::run_task(db, config, tx, task_id).await { tracing::error!("Error in scheduled task {}: {}", task_id, e); } }) @@ -98,9 +86,7 @@ impl Scheduler { async fn run_task( db: DatabaseConnection, config: Arc, - calendar_client: Arc, tx: tokio::sync::broadcast::Sender, - push_sender: Arc, task_id: Uuid, ) -> AppResult<()> { let task = Task::find_by_id(task_id) @@ -140,8 +126,6 @@ impl Scheduler { db.clone(), config.zen_api_key.clone(), config.tavily_api_key.clone(), - calendar_client.clone(), - None, task.goal.clone(), )?; @@ -163,7 +147,7 @@ impl Scheduler { let run_complete = task_run::ActiveModel { id: Set(run_id), - status: Set(status.clone()), + status: Set(status), logs: Set(logs), answer: Set(answer), ..Default::default() @@ -178,48 +162,8 @@ impl Scheduler { if let Ok(task_response) = crate::domain::tasks::get_task_inner(task_id, &db).await { let _ = tx.send(crate::server::notifications::WsEvent::RunFinished( - task_response.clone(), + task_response, )); - - // Push notifications for scheduled runs - use crate::entities::{push_subscription, task_subscription}; - use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; - if let Ok(subscriptions) = task_subscription::Entity::find() - .filter(task_subscription::Column::TaskId.eq(task_id)) - .all(&db) - .await - { - for sub in subscriptions { - if let Ok(push_subs) = push_subscription::Entity::find() - .filter(push_subscription::Column::UserSub.eq(sub.user_sub)) - .all(&db) - .await - { - for push_sub in push_subs { - let sender = push_sender.clone(); - let sub_data = - crate::domain::notifications::push::PushSubscription { - endpoint: push_sub.endpoint, - p256dh: push_sub.p256dh, - auth: push_sub.auth, - }; - let goal = task_response.goal.clone(); - let status = status.clone(); - tokio::spawn(async move { - let _ = sender - .send_notification( - &sub_data, - &format!("Scheduled Task Completed: {}", status), - &goal, - Some(task_id), - Some(run_id), - ) - .await; - }); - } - } - } - } } }); diff --git a/src/server/chat.rs b/src/server/chat.rs index 6e24d4b..5163275 100644 --- a/src/server/chat.rs +++ b/src/server/chat.rs @@ -17,7 +17,6 @@ pub struct ChatResult { pub async fn chat_handler( State(state): State>, - user: crate::server::auth::AuthenticatedUser, Json(payload): Json, ) -> Result, AppError> { let msg_count = payload.messages.len(); @@ -46,21 +45,11 @@ pub async fn chat_handler( state.db.clone(), state.config.zen_api_key.clone(), state.config.tavily_api_key.clone(), - state.calendar_client.clone(), - Some(user.0.sub), messages, )?; tracing::info!("Starting interactive agent turn"); - let assistant_message = - match tokio::time::timeout(std::time::Duration::from_secs(180), agent.execute_turn()).await - { - Ok(res) => res?, - Err(_) => { - tracing::error!("Interactive chat agent turn timed out after 180s"); - return Err(AppError::Internal("Agent turn timed out".into())); - } - }; + let assistant_message = agent.execute_turn().await?; Ok(Json(ChatResult { message: assistant_message, diff --git a/src/server/mod.rs b/src/server/mod.rs index 5160b4e..6979d6e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -13,6 +13,7 @@ use sea_orm::{Database, DatabaseConnection, EntityTrait}; use std::sync::Arc; use tower_http::cors::{AllowOrigin, CorsLayer}; +use crate::entities::task::Entity as Task; use crate::scheduler::Scheduler; use crate::error::AppResult; @@ -24,7 +25,6 @@ pub struct AppState { pub config: Arc, pub verifier: Arc, pub authenticator: Arc, - pub calendar_client: Arc, pub tx: tokio::sync::broadcast::Sender, } @@ -35,30 +35,13 @@ pub async fn start(config: crate::config::Config) -> AppResult<()> { let (tx, _) = tokio::sync::broadcast::channel(100); - let push_sender = Arc::new(crate::domain::notifications::push::PushSender::new( - &config.vapid_private_key.clone(), - )?); - - let (verifier, authenticator) = setup_auth(&config).await?; - let calendar_client = Arc::new(crate::domain::calendar::CalendarClient::new( - config.calendar_api_url.clone(), - authenticator.clone(), - )); - let scheduler = Arc::new( - Scheduler::new( - db.clone(), - config.clone(), - calendar_client.clone(), - tx.clone(), - push_sender.clone(), - ) - .await - .map_err(|e| crate::error::AppError::Internal(e.to_string()))?, + Scheduler::new(db.clone(), config.clone(), tx.clone()) + .await + .map_err(|e| crate::error::AppError::Internal(e.to_string()))?, ); // Load existing scheduled tasks - use crate::entities::task::Entity as Task; let existing_tasks = Task::find() .all(&db) .await @@ -69,13 +52,14 @@ pub async fn start(config: crate::config::Config) -> AppResult<()> { } } + let (verifier, authenticator) = setup_auth(&config).await?; + let state = Arc::new(AppState { db, scheduler, config: config.clone(), verifier, authenticator, - calendar_client, tx, }); @@ -144,11 +128,6 @@ fn build_app(state: Arc, config: &crate::config::Config) -> Router { .route("/api/auth/logout", post(auth::auth_logout)) .route("/api/chat", post(chat::chat_handler)) .route("/api/ws", get(notifications::ws_handler)) - .route("/api/notifications/register", post(notifications::push_handlers::register_push)) - .route("/api/notifications/vapid-key", get(notifications::push_handlers::get_vapid_key)) - .route("/api/tasks/:id/subscription", get(notifications::push_handlers::get_subscription_status)) - .route("/api/tasks/:id/subscribe", post(notifications::push_handlers::subscribe_task).delete(notifications::push_handlers::unsubscribe_task)) - .layer(axum::middleware::from_fn(log_error_responses)) .layer(cors) .layer(tower_http::set_header::SetResponseHeaderLayer::overriding( axum::http::header::CONTENT_SECURITY_POLICY, @@ -166,22 +145,6 @@ fn build_app(state: Arc, config: &crate::config::Config) -> Router { .with_state(state) } -async fn log_error_responses( - req: axum::extract::Request, - next: axum::middleware::Next, -) -> axum::response::Response { - let method = req.method().clone(); - let uri = req.uri().clone(); - let res = next.run(req).await; - let status = res.status(); - - if status.is_client_error() || status.is_server_error() { - tracing::error!(%method, %uri, %status, "Response error"); - } - - res -} - fn build_cors_layer(config: &crate::config::Config) -> CorsLayer { let allow_origin = if let Some(origins) = &config.cors_allowed_origins { let values: Vec = origins diff --git a/src/server/notifications.rs b/src/server/notifications.rs index 0786d0f..dcc62a3 100644 --- a/src/server/notifications.rs +++ b/src/server/notifications.rs @@ -1,5 +1,3 @@ -pub mod push_handlers; - use crate::domain::tasks::{RecentRunResponse, TaskResponse}; use crate::server::AppState; use axum::{ diff --git a/src/server/notifications/push_handlers.rs b/src/server/notifications/push_handlers.rs deleted file mode 100644 index cc38e19..0000000 --- a/src/server/notifications/push_handlers.rs +++ /dev/null @@ -1,133 +0,0 @@ -use axum::{ - Json, - extract::{Path, State}, -}; -use chrono::Utc; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; -use std::sync::Arc; -use uuid::Uuid; - -use crate::entities::{push_subscription, task_subscription}; -use crate::error::{AppError, AppResult}; -use crate::server::AppState; -use crate::server::auth::AuthenticatedUser; -use base64::Engine; - -#[derive(serde::Deserialize)] -pub struct RegisterPushRequest { - pub endpoint: String, - pub p256dh: String, - pub auth: String, -} - -pub async fn register_push( - State(state): State>, - user: AuthenticatedUser, - Json(payload): Json, -) -> AppResult> { - let user_sub = user.0.sub; - tracing::info!( - "Registering push subscription for user: {} with endpoint: {}", - user_sub, - payload.endpoint - ); - - // Check if subscription exists - let existing = push_subscription::Entity::find() - .filter(push_subscription::Column::UserSub.eq(user_sub.clone())) - .filter(push_subscription::Column::Endpoint.eq(payload.endpoint.clone())) - .one(&state.db) - .await - .map_err(AppError::Database)?; - - if existing.is_none() { - let new_sub = push_subscription::ActiveModel { - id: Set(Uuid::new_v4()), - user_sub: Set(user_sub), - endpoint: Set(payload.endpoint), - p256dh: Set(payload.p256dh), - auth: Set(payload.auth), - created_at: Set(Utc::now().into()), - }; - new_sub - .insert(&state.db) - .await - .map_err(AppError::Database)?; - } - - Ok(Json(serde_json::json!({ "status": "registered" }))) -} - -pub async fn subscribe_task( - State(state): State>, - user: AuthenticatedUser, - Path(task_id): Path, -) -> AppResult> { - let user_sub = user.0.sub; - - let existing = task_subscription::Entity::find() - .filter(task_subscription::Column::UserSub.eq(user_sub.clone())) - .filter(task_subscription::Column::TaskId.eq(task_id)) - .one(&state.db) - .await - .map_err(AppError::Database)?; - - if existing.is_none() { - let new_sub = task_subscription::ActiveModel { - id: Set(Uuid::new_v4()), - user_sub: Set(user_sub), - task_id: Set(task_id), - created_at: Set(Utc::now().into()), - }; - new_sub - .insert(&state.db) - .await - .map_err(AppError::Database)?; - } - - Ok(Json(serde_json::json!({ "status": "subscribed" }))) -} - -pub async fn unsubscribe_task( - State(state): State>, - user: AuthenticatedUser, - Path(task_id): Path, -) -> AppResult> { - let user_sub = user.0.sub; - - task_subscription::Entity::delete_many() - .filter(task_subscription::Column::UserSub.eq(user_sub)) - .filter(task_subscription::Column::TaskId.eq(task_id)) - .exec(&state.db) - .await - .map_err(AppError::Database)?; - - Ok(Json(serde_json::json!({ "status": "unsubscribed" }))) -} - -pub async fn get_vapid_key( - State(state): State>, -) -> AppResult> { - let public_key = state.scheduler.push_sender.get_public_key()?; - let encoded = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(public_key); - Ok(Json(serde_json::json!({ "publicKey": encoded }))) -} - -pub async fn get_subscription_status( - State(state): State>, - user: AuthenticatedUser, - Path(task_id): Path, -) -> AppResult> { - let user_sub = user.0.sub; - - let existing = task_subscription::Entity::find() - .filter(task_subscription::Column::UserSub.eq(user_sub)) - .filter(task_subscription::Column::TaskId.eq(task_id)) - .one(&state.db) - .await - .map_err(AppError::Database)?; - - Ok(Json( - serde_json::json!({ "isSubscribed": existing.is_some() }), - )) -} diff --git a/src/server/tasks.rs b/src/server/tasks.rs index fddc842..5c5da2b 100644 --- a/src/server/tasks.rs +++ b/src/server/tasks.rs @@ -109,7 +109,6 @@ pub async fn rerun_task( &state.db, &state.scheduler, &state.config, - state.calendar_client.clone(), task.id, task.goal, )