Add API for propagating cursor locations #61
15 changed files with 85 additions and 562 deletions
Remove aide
commit
4ca55768c5
286
backend/Cargo.lock
generated
286
backend/Cargo.lock
generated
|
|
@ -17,20 +17,6 @@ version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ahash"
|
|
||||||
version = "0.8.11"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"getrandom 0.2.15",
|
|
||||||
"once_cell",
|
|
||||||
"serde",
|
|
||||||
"version_check",
|
|
||||||
"zerocopy 0.7.35",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
|
|
@ -40,41 +26,6 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aide"
|
|
||||||
version = "0.13.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5678d2978845ddb4bd736a026f467dd652d831e9e6254b0e41b07f7ee7523309"
|
|
||||||
dependencies = [
|
|
||||||
"axum",
|
|
||||||
"axum-extra",
|
|
||||||
"bytes",
|
|
||||||
"cfg-if",
|
|
||||||
"http",
|
|
||||||
"indexmap 2.7.0",
|
|
||||||
"schemars",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_qs",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
"tower-layer",
|
|
||||||
"tower-service",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aide-axum-typed-multipart"
|
|
||||||
version = "0.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9b8f5c830a08754addfa31fa09e6c183bac8d2ae7bd007131f9eb84fcb87a40e"
|
|
||||||
dependencies = [
|
|
||||||
"aide",
|
|
||||||
"axum",
|
|
||||||
"axum_typed_multipart",
|
|
||||||
"indexmap 2.7.0",
|
|
||||||
"schemars",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "allocator-api2"
|
name = "allocator-api2"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
|
|
@ -265,26 +216,6 @@ dependencies = [
|
||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "axum-jsonschema"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fcffe29ca1b60172349fea781ec34441d598809bd227ccbb5bf5dc2879cd9c78"
|
|
||||||
dependencies = [
|
|
||||||
"aide",
|
|
||||||
"async-trait",
|
|
||||||
"axum",
|
|
||||||
"http",
|
|
||||||
"http-body",
|
|
||||||
"itertools",
|
|
||||||
"jsonschema",
|
|
||||||
"schemars",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"serde_path_to_error",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-macros"
|
name = "axum-macros"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
|
@ -368,21 +299,6 @@ version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7"
|
checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bit-set"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
|
||||||
dependencies = [
|
|
||||||
"bit-vec",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bit-vec"
|
|
||||||
version = "0.6.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.6.0"
|
version = "2.6.0"
|
||||||
|
|
@ -407,12 +323,6 @@ version = "3.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bytecount"
|
|
||||||
version = "0.6.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
|
@ -700,12 +610,6 @@ version = "0.15.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dyn-clone"
|
|
||||||
version = "1.0.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
|
|
@ -768,16 +672,6 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fancy-regex"
|
|
||||||
version = "0.11.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
|
|
||||||
dependencies = [
|
|
||||||
"bit-set",
|
|
||||||
"regex",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
|
@ -816,16 +710,6 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fraction"
|
|
||||||
version = "0.13.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3027ae1df8d41b4bed2241c8fdad4acc1e7af60c8e17743534b545e77182d678"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static",
|
|
||||||
"num",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
|
|
@ -943,10 +827,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1346,24 +1228,6 @@ version = "1.70.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iso8601"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153"
|
|
||||||
dependencies = [
|
|
||||||
"nom",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.12.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.14"
|
version = "1.0.14"
|
||||||
|
|
@ -1380,34 +1244,6 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "jsonschema"
|
|
||||||
version = "0.17.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2a071f4f7efc9a9118dfb627a0a94ef247986e1ab8606a4c806ae2b3aa3b6978"
|
|
||||||
dependencies = [
|
|
||||||
"ahash",
|
|
||||||
"anyhow",
|
|
||||||
"base64 0.21.7",
|
|
||||||
"bytecount",
|
|
||||||
"fancy-regex",
|
|
||||||
"fraction",
|
|
||||||
"getrandom 0.2.15",
|
|
||||||
"iso8601",
|
|
||||||
"itoa",
|
|
||||||
"memchr",
|
|
||||||
"num-cmp",
|
|
||||||
"once_cell",
|
|
||||||
"parking_lot",
|
|
||||||
"percent-encoding",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"time",
|
|
||||||
"url",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
|
@ -1521,12 +1357,6 @@ dependencies = [
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "minimal-lexical"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|
@ -1564,16 +1394,6 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "7.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
"minimal-lexical",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
|
|
@ -1584,30 +1404,6 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num"
|
|
||||||
version = "0.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
|
|
||||||
dependencies = [
|
|
||||||
"num-bigint",
|
|
||||||
"num-complex",
|
|
||||||
"num-integer",
|
|
||||||
"num-iter",
|
|
||||||
"num-rational",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-bigint"
|
|
||||||
version = "0.4.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
|
||||||
dependencies = [
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint-dig"
|
name = "num-bigint-dig"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
|
@ -1625,21 +1421,6 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-cmp"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-complex"
|
|
||||||
version = "0.4.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -1666,17 +1447,6 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-rational"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
|
||||||
dependencies = [
|
|
||||||
"num-bigint",
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
|
|
@ -2077,34 +1847,6 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "schemars"
|
|
||||||
version = "0.8.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"chrono",
|
|
||||||
"dyn-clone",
|
|
||||||
"indexmap 2.7.0",
|
|
||||||
"schemars_derive",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "schemars_derive"
|
|
||||||
version = "0.8.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"serde_derive_internals",
|
|
||||||
"syn 2.0.90",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scoped-tls"
|
name = "scoped-tls"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|
@ -2137,17 +1879,6 @@ dependencies = [
|
||||||
"syn 2.0.90",
|
"syn 2.0.90",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_derive_internals"
|
|
||||||
version = "0.29.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.90",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.140"
|
version = "1.0.140"
|
||||||
|
|
@ -2170,19 +1901,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_qs"
|
|
||||||
version = "0.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6"
|
|
||||||
dependencies = [
|
|
||||||
"axum",
|
|
||||||
"futures",
|
|
||||||
"percent-encoding",
|
|
||||||
"serde",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
|
|
@ -2610,12 +2328,9 @@ dependencies = [
|
||||||
name = "sync_server"
|
name = "sync_server"
|
||||||
version = "0.3.15"
|
version = "0.3.15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aide",
|
|
||||||
"aide-axum-typed-multipart",
|
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
"axum-jsonschema",
|
|
||||||
"axum_typed_multipart",
|
"axum_typed_multipart",
|
||||||
"bimap",
|
"bimap",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|
@ -2626,7 +2341,6 @@ dependencies = [
|
||||||
"rand 0.9.0",
|
"rand 0.9.0",
|
||||||
"regex",
|
"regex",
|
||||||
"sanitize-filename",
|
"sanitize-filename",
|
||||||
"schemars",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
|
|
||||||
|
|
@ -18,22 +18,18 @@ log = { version = "0.4.27" }
|
||||||
anyhow = { version = "1.0.98", features = ["backtrace"] }
|
anyhow = { version = "1.0.98", features = ["backtrace"] }
|
||||||
axum = { version = "0.7.4", features = ["ws", "macros", "tracing", "multipart"]}
|
axum = { version = "0.7.4", features = ["ws", "macros", "tracing", "multipart"]}
|
||||||
axum-extra = { version = "0.9.6", features = ["typed-header"] }
|
axum-extra = { version = "0.9.6", features = ["typed-header"] }
|
||||||
aide-axum-typed-multipart = "0.13.0"
|
|
||||||
axum_typed_multipart = "0.11.0"
|
axum_typed_multipart = "0.11.0"
|
||||||
tower-http = { version = "0.6.1", features = ["cors", "trace", "limit", "timeout"] }
|
tower-http = { version = "0.6.1", features = ["cors", "trace", "limit", "timeout"] }
|
||||||
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["fmt", "env-filter"]}
|
tracing-subscriber = { version = "0.3.19", features = ["fmt", "env-filter"]}
|
||||||
serde_yaml = "0.9.34"
|
|
||||||
sqlx = { version = "0.8.6", features = ["sqlite", "runtime-tokio", "uuid", "chrono"] }
|
sqlx = { version = "0.8.6", features = ["sqlite", "runtime-tokio", "uuid", "chrono"] }
|
||||||
chrono = { version = "0.4.41", features = ["serde"] }
|
chrono = { version = "0.4.41", features = ["serde"] }
|
||||||
aide = { version = "0.13.5", features = ["axum", "axum-ws", "scalar", "axum-headers"] }
|
|
||||||
schemars = { version = "0.8.22", features = ["chrono", "uuid1", "bytes"] }
|
|
||||||
tracing = "0.1.41"
|
|
||||||
rand = "0.9.0"
|
rand = "0.9.0"
|
||||||
sanitize-filename = "0.6.0"
|
sanitize-filename = "0.6.0"
|
||||||
axum-jsonschema = { version = "0.8.0", features = ["aide"] }
|
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
clap = { version = "4.5.38", features = ["derive"] }
|
clap = { version = "4.5.38", features = ["derive"] }
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
|
serde_yaml = "0.9.34"
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
clap-verbosity-flag = "3.0.3"
|
clap-verbosity-flag = "3.0.3"
|
||||||
bimap = "0.6.3"
|
bimap = "0.6.3"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sync_lib::bytes_to_base64;
|
use sync_lib::bytes_to_base64;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
@ -27,7 +26,7 @@ impl PartialEq<Self> for StoredDocumentVersion {
|
||||||
fn eq(&self, other: &Self) -> bool { self.vault_update_id == other.vault_update_id }
|
fn eq(&self, other: &Self) -> bool { self.vault_update_id == other.vault_update_id }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(TS, Debug, Clone, Serialize, JsonSchema)]
|
#[derive(TS, Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DocumentVersionWithoutContent {
|
pub struct DocumentVersionWithoutContent {
|
||||||
#[ts(as = "i32")]
|
#[ts(as = "i32")]
|
||||||
|
|
@ -59,7 +58,7 @@ impl From<StoredDocumentVersion> for DocumentVersionWithoutContent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
#[derive(TS, Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DocumentVersion {
|
pub struct DocumentVersion {
|
||||||
pub vault_update_id: VaultUpdateId,
|
pub vault_update_id: VaultUpdateId,
|
||||||
|
|
|
||||||
|
|
@ -12,29 +12,20 @@ mod responses;
|
||||||
mod update_document;
|
mod update_document;
|
||||||
mod websocket;
|
mod websocket;
|
||||||
|
|
||||||
use std::{ffi::OsString, sync::Arc, time::Duration};
|
use std::{ffi::OsString, time::Duration};
|
||||||
|
|
||||||
use aide::{
|
|
||||||
axum::{
|
|
||||||
ApiRouter,
|
|
||||||
routing::{delete, get, post, put},
|
|
||||||
},
|
|
||||||
openapi::{Info, OpenApi},
|
|
||||||
scalar::Scalar,
|
|
||||||
transform::TransformOpenApi,
|
|
||||||
};
|
|
||||||
use anyhow::{Context as _, Result, anyhow};
|
use anyhow::{Context as _, Result, anyhow};
|
||||||
use auth::auth_middleware;
|
use auth::auth_middleware;
|
||||||
use axum::{
|
use axum::{
|
||||||
Extension, Json,
|
Router,
|
||||||
extract::{DefaultBodyLimit, Request},
|
extract::{DefaultBodyLimit, Request},
|
||||||
http::{self, HeaderValue, Method},
|
http::{self, HeaderValue, Method},
|
||||||
middleware,
|
middleware,
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::IntoMakeService,
|
routing::{IntoMakeService, delete, get, post, put},
|
||||||
};
|
};
|
||||||
use device_id_header::DEVICE_ID_HEADER_NAME;
|
use device_id_header::DEVICE_ID_HEADER_NAME;
|
||||||
use log::{error, info};
|
use log::info;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
use tower_http::{
|
use tower_http::{
|
||||||
LatencyUnit,
|
LatencyUnit,
|
||||||
|
|
@ -51,26 +42,20 @@ use tracing::{Level, info_span};
|
||||||
use crate::{
|
use crate::{
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
config::server_config::ServerConfig,
|
config::server_config::ServerConfig,
|
||||||
errors::{SerializedError, client_error, not_found_error},
|
errors::{client_error, not_found_error},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn create_server(config_path: Option<OsString>) -> Result<()> {
|
pub async fn create_server(config_path: Option<OsString>) -> Result<()> {
|
||||||
aide::r#gen::on_error(|err| error!("{err}"));
|
|
||||||
aide::r#gen::extract_schemas(true);
|
|
||||||
|
|
||||||
let app_state = AppState::try_new(config_path)
|
let app_state = AppState::try_new(config_path)
|
||||||
.await
|
.await
|
||||||
.context("Failed to initialise app state")?;
|
.context("Failed to initialise app state")?;
|
||||||
|
|
||||||
let server_config = app_state.config.server.clone();
|
let server_config = app_state.config.server.clone();
|
||||||
|
|
||||||
let mut api = create_open_api();
|
let app = Router::new()
|
||||||
let app = ApiRouter::new()
|
|
||||||
.nest("/", get_authed_routes(app_state.clone()))
|
.nest("/", get_authed_routes(app_state.clone()))
|
||||||
.api_route("/vaults/:vault_id/ping", get(ping::ping))
|
.route("/vaults/:vault_id/ping", get(ping::ping))
|
||||||
.route("/vaults/:vault_id/ws", get(websocket::websocket_handler))
|
.route("/vaults/:vault_id/ws", get(websocket::websocket_handler))
|
||||||
.route("/", Scalar::new("/api.json").axum_route())
|
|
||||||
.route("/api.json", axum::routing::get(serve_api))
|
|
||||||
.layer(DefaultBodyLimit::disable())
|
.layer(DefaultBodyLimit::disable())
|
||||||
.layer(RequestBodyLimitLayer::new(
|
.layer(RequestBodyLimitLayer::new(
|
||||||
app_state.config.server.max_body_size_mb * 1024 * 1024,
|
app_state.config.server.max_body_size_mb * 1024 * 1024,
|
||||||
|
|
@ -108,8 +93,6 @@ pub async fn create_server(config_path: Option<OsString>) -> Result<()> {
|
||||||
.on_failure(DefaultOnFailure::new().level(Level::ERROR)),
|
.on_failure(DefaultOnFailure::new().level(Level::ERROR)),
|
||||||
)
|
)
|
||||||
.with_state(app_state)
|
.with_state(app_state)
|
||||||
.finish_api_with(&mut api, add_api_docs_error_example)
|
|
||||||
.layer(Extension(Arc::new(api))) // https://github.com/tamasfe/aide/blob/507f4a8822bc0c13cbda0f589da1e0f4cbcdb812/examples/example-axum/src/main.rs#L39
|
|
||||||
.fallback(handle_404)
|
.fallback(handle_404)
|
||||||
.fallback(handle_405)
|
.fallback(handle_405)
|
||||||
.into_make_service();
|
.into_make_service();
|
||||||
|
|
@ -117,67 +100,33 @@ pub async fn create_server(config_path: Option<OsString>) -> Result<()> {
|
||||||
start_server(app, &server_config).await
|
start_server(app, &server_config).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn serve_api(Extension(api): Extension<Arc<OpenApi>>) -> impl IntoResponse { Json(api) }
|
fn get_authed_routes(app_state: AppState) -> Router<AppState> {
|
||||||
|
Router::new()
|
||||||
fn create_open_api() -> OpenApi {
|
.route(
|
||||||
OpenApi {
|
|
||||||
info: Info {
|
|
||||||
title: "VaultLink sync server".to_owned(),
|
|
||||||
summary: Some(
|
|
||||||
"Simple API for syncing documents between concurrent clients.".to_owned(),
|
|
||||||
),
|
|
||||||
description: Some(include_str!("../README.md").to_owned()),
|
|
||||||
version: env!("CARGO_PKG_VERSION").to_owned(),
|
|
||||||
..Info::default()
|
|
||||||
},
|
|
||||||
..OpenApi::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_api_docs_error_example(api: TransformOpenApi<'_>) -> TransformOpenApi<'_> {
|
|
||||||
api.default_response_with::<Json<SerializedError>, _>(|res| {
|
|
||||||
res.example(SerializedError {
|
|
||||||
message: "An error has occurred".to_owned(),
|
|
||||||
causes: vec![],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_authed_routes(app_state: AppState) -> ApiRouter<AppState> {
|
|
||||||
ApiRouter::new()
|
|
||||||
.api_route(
|
|
||||||
"/vaults/:vault_id/documents",
|
"/vaults/:vault_id/documents",
|
||||||
get(fetch_latest_documents::fetch_latest_documents),
|
get(fetch_latest_documents::fetch_latest_documents),
|
||||||
)
|
)
|
||||||
.api_route(
|
.route(
|
||||||
"/vaults/:vault_id/documents",
|
"/vaults/:vault_id/documents",
|
||||||
post(create_document::create_document_multipart),
|
post(create_document::create_document),
|
||||||
)
|
)
|
||||||
.api_route(
|
.route(
|
||||||
"/vaults/:vault_id/documents/json",
|
|
||||||
post(create_document::create_document_json),
|
|
||||||
)
|
|
||||||
.api_route(
|
|
||||||
"/vaults/:vault_id/documents/:document_id",
|
"/vaults/:vault_id/documents/:document_id",
|
||||||
get(fetch_latest_document_version::fetch_latest_document_version),
|
get(fetch_latest_document_version::fetch_latest_document_version),
|
||||||
)
|
)
|
||||||
.api_route(
|
.route(
|
||||||
"/vaults/:vault_id/documents/:document_id",
|
"/vaults/:vault_id/documents/:document_id",
|
||||||
put(update_document::update_document_multipart),
|
put(update_document::update_document),
|
||||||
)
|
)
|
||||||
.api_route(
|
.route(
|
||||||
"/vaults/:vault_id/documents/:document_id/json",
|
|
||||||
put(update_document::update_document_json),
|
|
||||||
)
|
|
||||||
.api_route(
|
|
||||||
"/vaults/:vault_id/documents/:document_id/versions/:version_id",
|
"/vaults/:vault_id/documents/:document_id/versions/:version_id",
|
||||||
put(fetch_document_version::fetch_document_version),
|
put(fetch_document_version::fetch_document_version),
|
||||||
)
|
)
|
||||||
.api_route(
|
.route(
|
||||||
"/vaults/:vault_id/documents/:document_id/versions/:version_id/content",
|
"/vaults/:vault_id/documents/:document_id/versions/:version_id/content",
|
||||||
put(fetch_document_version_content::fetch_document_version_content),
|
put(fetch_document_version_content::fetch_document_version_content),
|
||||||
)
|
)
|
||||||
.api_route(
|
.route(
|
||||||
"/vaults/:vault_id/documents/:document_id",
|
"/vaults/:vault_id/documents/:document_id",
|
||||||
delete(delete_document::delete_document),
|
delete(delete_document::delete_document),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,24 @@
|
||||||
use aide_axum_typed_multipart::TypedMultipart;
|
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use axum::{
|
use axum::{
|
||||||
Extension,
|
Extension, Json,
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
};
|
};
|
||||||
use axum_extra::TypedHeader;
|
use axum_extra::TypedHeader;
|
||||||
use axum_jsonschema::Json;
|
use axum_typed_multipart::TypedMultipart;
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sync_lib::base64_to_bytes;
|
|
||||||
|
|
||||||
use super::{
|
use super::{device_id_header::DeviceIdHeader, requests::CreateDocumentVersion};
|
||||||
device_id_header::DeviceIdHeader,
|
|
||||||
requests::{CreateDocumentVersion, CreateDocumentVersionMultipart},
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_state::{
|
app_state::{
|
||||||
AppState,
|
AppState,
|
||||||
database::models::{
|
database::models::{DocumentVersionWithoutContent, StoredDocumentVersion, VaultId},
|
||||||
DocumentId, DocumentVersionWithoutContent, StoredDocumentVersion, VaultId,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
config::user_config::User,
|
config::user_config::User,
|
||||||
errors::{SyncServerError, client_error, server_error},
|
errors::{SyncServerError, client_error, server_error},
|
||||||
utils::{normalize::normalize, sanitize_path::sanitize_path},
|
utils::{normalize::normalize, sanitize_path::sanitize_path},
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is required for aide to infer the path parameter types and names
|
#[derive(Deserialize)]
|
||||||
#[derive(Deserialize, JsonSchema)]
|
|
||||||
pub struct CreateDocumentPathParams {
|
pub struct CreateDocumentPathParams {
|
||||||
#[serde(deserialize_with = "normalize")]
|
#[serde(deserialize_with = "normalize")]
|
||||||
vault_id: VaultId,
|
vault_id: VaultId,
|
||||||
|
|
@ -37,63 +28,12 @@ pub struct CreateDocumentPathParams {
|
||||||
/// already. If a document with the same path exists, a new version is created
|
/// already. If a document with the same path exists, a new version is created
|
||||||
/// with their content merged.
|
/// with their content merged.
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn create_document_multipart(
|
pub async fn create_document(
|
||||||
Path(CreateDocumentPathParams { vault_id }): Path<CreateDocumentPathParams>,
|
Path(CreateDocumentPathParams { vault_id }): Path<CreateDocumentPathParams>,
|
||||||
Extension(user): Extension<User>,
|
Extension(user): Extension<User>,
|
||||||
TypedHeader(device_id): TypedHeader<DeviceIdHeader>,
|
TypedHeader(device_id): TypedHeader<DeviceIdHeader>,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
TypedMultipart(axum_typed_multipart::TypedMultipart(request)): TypedMultipart<
|
TypedMultipart(request): TypedMultipart<CreateDocumentVersion>,
|
||||||
CreateDocumentVersionMultipart,
|
|
||||||
>,
|
|
||||||
) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> {
|
|
||||||
internal_create_document(
|
|
||||||
user,
|
|
||||||
device_id,
|
|
||||||
state,
|
|
||||||
vault_id,
|
|
||||||
request.document_id,
|
|
||||||
request.relative_path,
|
|
||||||
request.content.contents.to_vec(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new document in case a document with the same doesn't exist
|
|
||||||
/// already. If a document with the same path exists, a new version is created
|
|
||||||
/// with their content merged.
|
|
||||||
#[axum::debug_handler]
|
|
||||||
pub async fn create_document_json(
|
|
||||||
Path(CreateDocumentPathParams { vault_id }): Path<CreateDocumentPathParams>,
|
|
||||||
Extension(user): Extension<User>,
|
|
||||||
TypedHeader(device_id): TypedHeader<DeviceIdHeader>,
|
|
||||||
State(state): State<AppState>,
|
|
||||||
Json(request): Json<CreateDocumentVersion>,
|
|
||||||
) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> {
|
|
||||||
let content_bytes = base64_to_bytes(&request.content_base64)
|
|
||||||
.context("Failed to decode base64 content in request")
|
|
||||||
.map_err(client_error)?;
|
|
||||||
|
|
||||||
internal_create_document(
|
|
||||||
user,
|
|
||||||
device_id,
|
|
||||||
state,
|
|
||||||
vault_id,
|
|
||||||
request.document_id,
|
|
||||||
request.relative_path,
|
|
||||||
content_bytes,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
async fn internal_create_document(
|
|
||||||
user: User,
|
|
||||||
device_id: DeviceIdHeader,
|
|
||||||
state: AppState,
|
|
||||||
vault_id: VaultId,
|
|
||||||
document_id: Option<DocumentId>,
|
|
||||||
relative_path: String,
|
|
||||||
content: Vec<u8>,
|
|
||||||
) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> {
|
) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> {
|
||||||
let mut transaction = state
|
let mut transaction = state
|
||||||
.database
|
.database
|
||||||
|
|
@ -101,7 +41,7 @@ async fn internal_create_document(
|
||||||
.await
|
.await
|
||||||
.map_err(server_error)?;
|
.map_err(server_error)?;
|
||||||
|
|
||||||
let document_id = match document_id {
|
let document_id = match request.document_id {
|
||||||
Some(document_id) => {
|
Some(document_id) => {
|
||||||
let existing_version = state
|
let existing_version = state
|
||||||
.database
|
.database
|
||||||
|
|
@ -126,13 +66,13 @@ async fn internal_create_document(
|
||||||
.await
|
.await
|
||||||
.map_err(server_error)?;
|
.map_err(server_error)?;
|
||||||
|
|
||||||
let sanitized_relative_path = sanitize_path(&relative_path);
|
let sanitized_relative_path = sanitize_path(&request.relative_path);
|
||||||
|
|
||||||
let new_version = StoredDocumentVersion {
|
let new_version = StoredDocumentVersion {
|
||||||
vault_update_id: last_update_id + 1,
|
vault_update_id: last_update_id + 1,
|
||||||
document_id,
|
document_id,
|
||||||
relative_path: sanitized_relative_path,
|
relative_path: sanitized_relative_path,
|
||||||
content,
|
content: request.content.contents.to_vec(),
|
||||||
updated_date: chrono::Utc::now(),
|
updated_date: chrono::Utc::now(),
|
||||||
is_deleted: false,
|
is_deleted: false,
|
||||||
user_id: user.name,
|
user_id: user.name,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use axum::{
|
use axum::{
|
||||||
Extension,
|
Extension, Json,
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
};
|
};
|
||||||
use axum_extra::TypedHeader;
|
use axum_extra::TypedHeader;
|
||||||
use axum_jsonschema::Json;
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::{device_id_header::DeviceIdHeader, requests::DeleteDocumentVersion};
|
use super::{device_id_header::DeviceIdHeader, requests::DeleteDocumentVersion};
|
||||||
|
|
@ -21,8 +19,7 @@ use crate::{
|
||||||
utils::{normalize::normalize, sanitize_path::sanitize_path},
|
utils::{normalize::normalize, sanitize_path::sanitize_path},
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is required for aide to infer the path parameter types and names
|
#[derive(Deserialize)]
|
||||||
#[derive(Deserialize, JsonSchema)]
|
|
||||||
pub struct DeleteDocumentPathParams {
|
pub struct DeleteDocumentPathParams {
|
||||||
#[serde(deserialize_with = "normalize")]
|
#[serde(deserialize_with = "normalize")]
|
||||||
vault_id: VaultId,
|
vault_id: VaultId,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use axum::extract::{Path, State};
|
use axum::{
|
||||||
use axum_jsonschema::Json;
|
Json,
|
||||||
use schemars::JsonSchema;
|
extract::{Path, State},
|
||||||
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -13,8 +14,7 @@ use crate::{
|
||||||
utils::normalize::normalize,
|
utils::normalize::normalize,
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is required for aide to infer the path parameter types and names
|
#[derive(Deserialize)]
|
||||||
#[derive(Deserialize, JsonSchema)]
|
|
||||||
pub struct FetchDocumentVersionPathParams {
|
pub struct FetchDocumentVersionPathParams {
|
||||||
#[serde(deserialize_with = "normalize")]
|
#[serde(deserialize_with = "normalize")]
|
||||||
vault_id: VaultId,
|
vault_id: VaultId,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use axum::{
|
||||||
body::Bytes,
|
body::Bytes,
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
};
|
};
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -15,8 +14,7 @@ use crate::{
|
||||||
utils::normalize::normalize,
|
utils::normalize::normalize,
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is required for aide to infer the path parameter types and names
|
#[derive(Deserialize)]
|
||||||
#[derive(Deserialize, JsonSchema)]
|
|
||||||
pub struct FetchDocumentVersionContentPathParams {
|
pub struct FetchDocumentVersionContentPathParams {
|
||||||
#[serde(deserialize_with = "normalize")]
|
#[serde(deserialize_with = "normalize")]
|
||||||
vault_id: VaultId,
|
vault_id: VaultId,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use axum::extract::{Path, State};
|
use axum::{
|
||||||
use axum_jsonschema::Json;
|
Json,
|
||||||
use schemars::JsonSchema;
|
extract::{Path, State},
|
||||||
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -13,8 +14,7 @@ use crate::{
|
||||||
utils::normalize::normalize,
|
utils::normalize::normalize,
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is required for aide to infer the path parameter types and names
|
#[derive(Deserialize)]
|
||||||
#[derive(Deserialize, JsonSchema)]
|
|
||||||
pub struct FetchLatestDocumentVersionPathParams {
|
pub struct FetchLatestDocumentVersionPathParams {
|
||||||
#[serde(deserialize_with = "normalize")]
|
#[serde(deserialize_with = "normalize")]
|
||||||
vault_id: VaultId,
|
vault_id: VaultId,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use axum::extract::{Path, Query, State};
|
use axum::{
|
||||||
use axum_jsonschema::Json;
|
Json,
|
||||||
use schemars::JsonSchema;
|
extract::{Path, Query, State},
|
||||||
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::responses::FetchLatestDocumentsResponse;
|
use super::responses::FetchLatestDocumentsResponse;
|
||||||
|
|
@ -13,15 +14,13 @@ use crate::{
|
||||||
utils::normalize::normalize,
|
utils::normalize::normalize,
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is required for aide to infer the path parameter types and names
|
#[derive(Deserialize)]
|
||||||
#[derive(Deserialize, JsonSchema)]
|
|
||||||
pub struct FetchLatestDocumentsPathParams {
|
pub struct FetchLatestDocumentsPathParams {
|
||||||
#[serde(deserialize_with = "normalize")]
|
#[serde(deserialize_with = "normalize")]
|
||||||
vault_id: VaultId,
|
vault_id: VaultId,
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is required for aide to infer the path parameter types and names
|
#[derive(Deserialize)]
|
||||||
#[derive(Deserialize, JsonSchema)]
|
|
||||||
pub struct QueryParams {
|
pub struct QueryParams {
|
||||||
since_update_id: Option<VaultUpdateId>,
|
since_update_id: Option<VaultUpdateId>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use axum_extra::{
|
||||||
TypedHeader,
|
TypedHeader,
|
||||||
headers::{Authorization, authorization::Bearer},
|
headers::{Authorization, authorization::Bearer},
|
||||||
};
|
};
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::{auth::auth, responses::PingResponse};
|
use super::{auth::auth, responses::PingResponse};
|
||||||
|
|
@ -16,8 +15,7 @@ use crate::{
|
||||||
utils::normalize::normalize,
|
utils::normalize::normalize,
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is required for aide to infer the path parameter types and names
|
#[derive(Deserialize)]
|
||||||
#[derive(Deserialize, JsonSchema)]
|
|
||||||
pub struct PingPathParams {
|
pub struct PingPathParams {
|
||||||
#[serde(deserialize_with = "normalize")]
|
#[serde(deserialize_with = "normalize")]
|
||||||
vault_id: VaultId,
|
vault_id: VaultId,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use aide_axum_typed_multipart::FieldData;
|
|
||||||
use axum::body::Bytes;
|
use axum::body::Bytes;
|
||||||
use axum_typed_multipart::TryFromMultipart;
|
use axum_typed_multipart::{FieldData, TryFromMultipart};
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{self, Deserialize};
|
use serde::{self, Deserialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::app_state::database::models::{DocumentId, VaultUpdateId};
|
use crate::app_state::database::models::{DocumentId, VaultUpdateId};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, JsonSchema)]
|
#[derive(TS, Debug, TryFromMultipart)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[ts(export)]
|
||||||
pub struct CreateDocumentVersion {
|
pub struct CreateDocumentVersion {
|
||||||
/// The client can decide the document id (if it wishes to) in order
|
/// The client can decide the document id (if it wishes to) in order
|
||||||
/// to help with syncing. If the client does not provide a document id,
|
/// to help with syncing. If the client does not provide a document id,
|
||||||
|
|
@ -15,36 +14,26 @@ pub struct CreateDocumentVersion {
|
||||||
/// it must not already exist in the database.
|
/// it must not already exist in the database.
|
||||||
pub document_id: Option<DocumentId>,
|
pub document_id: Option<DocumentId>,
|
||||||
pub relative_path: String,
|
pub relative_path: String,
|
||||||
pub content_base64: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, TryFromMultipart, JsonSchema)]
|
#[ts(as = "Vec<u8>")]
|
||||||
pub struct CreateDocumentVersionMultipart {
|
|
||||||
pub document_id: Option<DocumentId>,
|
|
||||||
pub relative_path: String,
|
|
||||||
#[form_data(limit = "unlimited")]
|
#[form_data(limit = "unlimited")]
|
||||||
pub content: FieldData<Bytes>,
|
pub content: FieldData<Bytes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, JsonSchema)]
|
#[derive(TS, Debug, TryFromMultipart)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[ts(export)]
|
||||||
pub struct UpdateDocumentVersion {
|
pub struct UpdateDocumentVersion {
|
||||||
pub parent_version_id: VaultUpdateId,
|
pub parent_version_id: VaultUpdateId,
|
||||||
pub relative_path: String,
|
pub relative_path: String,
|
||||||
pub content_base64: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, TryFromMultipart, JsonSchema)]
|
#[ts(as = "Vec<u8>")]
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct UpdateDocumentVersionMultipart {
|
|
||||||
pub parent_version_id: VaultUpdateId,
|
|
||||||
pub relative_path: String,
|
|
||||||
#[form_data(limit = "unlimited")]
|
#[form_data(limit = "unlimited")]
|
||||||
pub content: FieldData<Bytes>,
|
pub content: FieldData<Bytes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, JsonSchema)]
|
#[derive(TS, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export)]
|
||||||
pub struct DeleteDocumentVersion {
|
pub struct DeleteDocumentVersion {
|
||||||
pub relative_path: String,
|
pub relative_path: String,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{self, Serialize};
|
use serde::{self, Serialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::app_state::database::models::{
|
use crate::app_state::database::models::{
|
||||||
DocumentVersion, DocumentVersionWithoutContent, VaultUpdateId,
|
DocumentVersion, DocumentVersionWithoutContent, VaultUpdateId,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Response to a ping request.
|
/// Response to a ping request.
|
||||||
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
#[derive(TS, Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export)]
|
||||||
pub struct PingResponse {
|
pub struct PingResponse {
|
||||||
/// Semantic version of the server.
|
/// Semantic version of the server.
|
||||||
pub server_version: String,
|
pub server_version: String,
|
||||||
|
|
@ -18,8 +19,9 @@ pub struct PingResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Response to a fetch latest documents request.
|
/// Response to a fetch latest documents request.
|
||||||
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
#[derive(TS, Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[ts(export)]
|
||||||
pub struct FetchLatestDocumentsResponse {
|
pub struct FetchLatestDocumentsResponse {
|
||||||
pub latest_documents: Vec<DocumentVersionWithoutContent>,
|
pub latest_documents: Vec<DocumentVersionWithoutContent>,
|
||||||
|
|
||||||
|
|
@ -28,8 +30,9 @@ pub struct FetchLatestDocumentsResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Response to an update document request.
|
/// Response to an update document request.
|
||||||
#[derive(Debug, Clone, Serialize, JsonSchema)]
|
#[derive(TS, Debug, Clone, Serialize)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
|
#[ts(export)]
|
||||||
pub enum DocumentUpdateResponse {
|
pub enum DocumentUpdateResponse {
|
||||||
/// Returned when the created/updated document's content is the same as was
|
/// Returned when the created/updated document's content is the same as was
|
||||||
/// sent in the create/update request and thus the response doesn't contain
|
/// sent in the create/update request and thus the response doesn't contain
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,29 @@
|
||||||
use aide_axum_typed_multipart::TypedMultipart;
|
|
||||||
use anyhow::{Context as _, anyhow};
|
use anyhow::{Context as _, anyhow};
|
||||||
use axum::{
|
use axum::{
|
||||||
Extension,
|
Extension, Json,
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
};
|
};
|
||||||
use axum_extra::TypedHeader;
|
use axum_extra::TypedHeader;
|
||||||
use axum_jsonschema::Json;
|
use axum_typed_multipart::TypedMultipart;
|
||||||
use log::info;
|
use log::info;
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sync_lib::{base64_to_bytes, is_file_type_mergable, merge};
|
use sync_lib::{is_file_type_mergable, merge};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
device_id_header::DeviceIdHeader,
|
device_id_header::DeviceIdHeader, requests::UpdateDocumentVersion,
|
||||||
requests::{UpdateDocumentVersion, UpdateDocumentVersionMultipart},
|
|
||||||
responses::DocumentUpdateResponse,
|
responses::DocumentUpdateResponse,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
app_state::{
|
app_state::{
|
||||||
AppState,
|
AppState,
|
||||||
database::models::{DocumentId, StoredDocumentVersion, VaultId, VaultUpdateId},
|
database::models::{DocumentId, StoredDocumentVersion, VaultId},
|
||||||
},
|
},
|
||||||
config::user_config::User,
|
config::user_config::User,
|
||||||
errors::{SyncServerError, client_error, not_found_error, server_error},
|
errors::{SyncServerError, not_found_error, server_error},
|
||||||
utils::{dedup_paths::dedup_paths, normalize::normalize, sanitize_path::sanitize_path},
|
utils::{dedup_paths::dedup_paths, normalize::normalize, sanitize_path::sanitize_path},
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is required for aide to infer the path parameter types and names
|
#[derive(Deserialize)]
|
||||||
#[derive(Deserialize, JsonSchema)]
|
|
||||||
pub struct UpdateDocumentPathParams {
|
pub struct UpdateDocumentPathParams {
|
||||||
#[serde(deserialize_with = "normalize")]
|
#[serde(deserialize_with = "normalize")]
|
||||||
vault_id: VaultId,
|
vault_id: VaultId,
|
||||||
|
|
@ -36,7 +32,8 @@ pub struct UpdateDocumentPathParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
pub async fn update_document_multipart(
|
#[allow(clippy::too_many_lines)]
|
||||||
|
pub async fn update_document(
|
||||||
Path(UpdateDocumentPathParams {
|
Path(UpdateDocumentPathParams {
|
||||||
vault_id,
|
vault_id,
|
||||||
document_id,
|
document_id,
|
||||||
|
|
@ -44,79 +41,25 @@ pub async fn update_document_multipart(
|
||||||
Extension(user): Extension<User>,
|
Extension(user): Extension<User>,
|
||||||
TypedHeader(device_id): TypedHeader<DeviceIdHeader>,
|
TypedHeader(device_id): TypedHeader<DeviceIdHeader>,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
TypedMultipart(axum_typed_multipart::TypedMultipart(request)): TypedMultipart<
|
TypedMultipart(request): TypedMultipart<UpdateDocumentVersion>,
|
||||||
UpdateDocumentVersionMultipart,
|
|
||||||
>,
|
|
||||||
) -> Result<Json<DocumentUpdateResponse>, SyncServerError> {
|
|
||||||
internal_update_document(
|
|
||||||
user,
|
|
||||||
device_id,
|
|
||||||
state,
|
|
||||||
vault_id,
|
|
||||||
document_id,
|
|
||||||
request.parent_version_id,
|
|
||||||
request.relative_path,
|
|
||||||
request.content.contents.to_vec(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
|
||||||
pub async fn update_document_json(
|
|
||||||
Path(UpdateDocumentPathParams {
|
|
||||||
vault_id,
|
|
||||||
document_id,
|
|
||||||
}): Path<UpdateDocumentPathParams>,
|
|
||||||
Extension(user): Extension<User>,
|
|
||||||
TypedHeader(device_id): TypedHeader<DeviceIdHeader>,
|
|
||||||
State(state): State<AppState>,
|
|
||||||
Json(request): Json<UpdateDocumentVersion>,
|
|
||||||
) -> Result<Json<DocumentUpdateResponse>, SyncServerError> {
|
|
||||||
let content_bytes = base64_to_bytes(&request.content_base64)
|
|
||||||
.context("Failed to decode base64 content in request")
|
|
||||||
.map_err(client_error)?;
|
|
||||||
|
|
||||||
internal_update_document(
|
|
||||||
user,
|
|
||||||
device_id,
|
|
||||||
state,
|
|
||||||
vault_id,
|
|
||||||
document_id,
|
|
||||||
request.parent_version_id,
|
|
||||||
request.relative_path,
|
|
||||||
content_bytes,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
|
|
||||||
async fn internal_update_document(
|
|
||||||
user: User,
|
|
||||||
device_id: DeviceIdHeader,
|
|
||||||
state: AppState,
|
|
||||||
vault_id: VaultId,
|
|
||||||
document_id: DocumentId,
|
|
||||||
parent_version_id: VaultUpdateId,
|
|
||||||
relative_path: String,
|
|
||||||
content: Vec<u8>,
|
|
||||||
) -> Result<Json<DocumentUpdateResponse>, SyncServerError> {
|
) -> Result<Json<DocumentUpdateResponse>, SyncServerError> {
|
||||||
// No need for a transaction as document versions are immutable
|
// No need for a transaction as document versions are immutable
|
||||||
let parent_document = state
|
let parent_document = state
|
||||||
.database
|
.database
|
||||||
.get_document_version(&vault_id, parent_version_id, None)
|
.get_document_version(&vault_id, request.parent_version_id, None)
|
||||||
.await
|
.await
|
||||||
.map_err(server_error)?
|
.map_err(server_error)?
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
|| {
|
|| {
|
||||||
Err(not_found_error(anyhow!(
|
Err(not_found_error(anyhow!(
|
||||||
"Parent version with id `{}` not found",
|
"Parent version with id `{}` not found",
|
||||||
parent_version_id
|
request.parent_version_id
|
||||||
)))
|
)))
|
||||||
},
|
},
|
||||||
Ok,
|
Ok,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let sanitized_relative_path = sanitize_path(&relative_path);
|
let sanitized_relative_path = sanitize_path(&request.relative_path);
|
||||||
|
|
||||||
let mut transaction = state
|
let mut transaction = state
|
||||||
.database
|
.database
|
||||||
|
|
@ -156,6 +99,8 @@ async fn internal_update_document(
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let content = request.content.contents.to_vec();
|
||||||
|
|
||||||
// Return the latest version if the content and path are the same as the latest
|
// Return the latest version if the content and path are the same as the latest
|
||||||
// version
|
// version
|
||||||
if content == latest_version.content && sanitized_relative_path == latest_version.relative_path
|
if content == latest_version.content && sanitized_relative_path == latest_version.relative_path
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,9 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
rm -rf backend/sync_server/bindings
|
rm -rf backend/sync_server/bindings
|
||||||
|
|
||||||
cd backend
|
cd backend
|
||||||
cargo test export_bindings
|
cargo test export_bindings
|
||||||
cd -
|
cd -
|
||||||
|
|
||||||
cp -r backend/sync_server/bindings/* frontend/sync-client/src/services/types/
|
cp -r backend/sync_server/bindings/* frontend/sync-client/src/services/types/
|
||||||
|
|
||||||
./scripts/utils/wait-for-server.sh
|
|
||||||
|
|
||||||
npm install -g openapi-typescript
|
|
||||||
openapi-typescript http://localhost:3000/api.json --output frontend/sync-client/src/services/types/http-api.ts
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue