diff --git a/Cargo.lock b/Cargo.lock index 6a8599e..a29675a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "adler2" @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -103,15 +103,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -281,7 +281,7 @@ version = "56.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3aa9e59c611ebc291c28582077ef25c97f1975383f1479b12f3b9ffee2ffabe" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -323,7 +323,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -453,7 +453,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -516,7 +516,7 @@ checksum = "523ab528ce3a7ada6597f8ccf5bd8d85ebe26d5edf311cad4d1d3cfb2d357ac6" dependencies = [ "base64", "blowfish", - "getrandom 0.4.1", + "getrandom 0.4.2", "subtle", "zeroize", ] @@ -529,9 +529,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bitvec" @@ -590,14 +590,14 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytecheck" @@ -650,9 +650,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.55" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "jobserver", @@ -696,9 +696,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -706,9 +706,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -718,21 +718,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cocoa" @@ -766,9 +766,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "comfy-table" @@ -928,15 +928,15 @@ dependencies = [ [[package]] name = "deflate64" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" +checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", "serde_core", @@ -950,7 +950,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -972,7 +972,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1001,9 +1001,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embed-resource" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" +checksum = "47ec73ddcf6b7f23173d5c3c5a32b5507dc0a734de7730aa14abc5d5e296bb5f" dependencies = [ "cc", "memchr", @@ -1035,7 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1120,7 +1120,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1146,9 +1146,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1160,9 +1160,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1170,44 +1170,44 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-core", "futures-io", @@ -1216,7 +1216,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -1261,21 +1260,21 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", "wasm-bindgen", @@ -1656,9 +1655,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" @@ -1703,9 +1702,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -1788,9 +1787,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libduckdb-sys" @@ -1817,13 +1816,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", - "redox_syscall 0.7.0", + "plain", + "redox_syscall 0.7.3", ] [[package]] @@ -2008,7 +2008,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.59.0", ] [[package]] @@ -2117,7 +2117,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -2140,9 +2140,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -2222,9 +2222,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -2238,6 +2238,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "polyval" version = "0.6.2" @@ -2322,14 +2328,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "proc-macro-crate" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ "toml_edit", ] @@ -2363,7 +2369,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2408,9 +2414,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "getrandom 0.3.4", @@ -2438,14 +2444,14 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -2456,6 +2462,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "radium" version = "0.7.0" @@ -2527,16 +2539,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] name = "redox_syscall" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -2564,9 +2576,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rend" @@ -2676,7 +2688,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "fallible-iterator 0.3.0", "fallible-streaming-iterator", "hashlink 0.11.0", @@ -2722,18 +2734,18 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "once_cell", "ring", @@ -2772,9 +2784,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "scopeguard" @@ -2821,7 +2833,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2961,12 +2973,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3035,7 +3047,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3047,7 +3059,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3069,9 +3081,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -3095,7 +3107,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3123,15 +3135,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.1", + "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3151,7 +3163,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3216,9 +3228,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -3248,13 +3260,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3315,7 +3327,7 @@ dependencies = [ "indexmap", "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow", @@ -3330,14 +3342,23 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" -version = "0.23.10+spec-1.0.0" +version = "0.25.4+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", "winnow", ] @@ -3395,7 +3416,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "bytes", "futures-core", "futures-util", @@ -3501,7 +3522,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3527,9 +3548,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.22" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", @@ -3591,9 +3612,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -3674,7 +3695,7 @@ version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", "serde_core", "wasm-bindgen", @@ -3771,9 +3792,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -3784,9 +3805,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", "futures-util", @@ -3798,9 +3819,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3808,22 +3829,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -3856,7 +3877,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap", "semver", @@ -3864,9 +3885,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -3925,7 +3946,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3936,7 +3957,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -3969,7 +3990,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -3978,16 +3999,7 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", + "windows-targets", ] [[package]] @@ -4005,31 +4017,14 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -4038,101 +4033,53 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] @@ -4177,7 +4124,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.114", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -4193,7 +4140,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -4205,7 +4152,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.10.0", + "bitflags 2.11.0", "indexmap", "log", "serde", @@ -4279,28 +4226,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4320,7 +4267,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "synstructure", ] @@ -4341,7 +4288,7 @@ checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4374,7 +4321,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -4403,7 +4350,7 @@ dependencies = [ "crc32fast", "deflate64", "flate2", - "getrandom 0.4.1", + "getrandom 0.4.2", "hmac", "indexmap", "lzma-rust2", @@ -4420,15 +4367,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zopfli" diff --git a/backend/src/db.rs b/backend/src/db.rs index f8b8e2f..74baad7 100644 --- a/backend/src/db.rs +++ b/backend/src/db.rs @@ -77,6 +77,14 @@ fn ensure_workspace_schema_and_backfill(conn: &duckdb::Connection) { "CREATE INDEX IF NOT EXISTS idx_files_workspace ON files(workspace_id)", [], ); + let _ = conn.execute( + "ALTER TABLE files ADD COLUMN source_type VARCHAR DEFAULT 'upload'", + [], + ); + let _ = conn.execute( + "CREATE INDEX IF NOT EXISTS idx_files_source_type ON files(source_type)", + [], + ); recover_detached_workspace_members(conn).expect("Failed to recover detached workspace members"); backfill_workspace_data(conn).expect("Failed to backfill workspace data"); diff --git a/backend/src/handlers.rs b/backend/src/handlers.rs index 4d0cd67..657a1be 100644 --- a/backend/src/handlers.rs +++ b/backend/src/handlers.rs @@ -56,7 +56,7 @@ pub type TileFileMetadata = ( Option, ); -async fn get_workspace_id( +pub async fn get_workspace_id( auth_session: &AuthSession, state: &AppState, ) -> Result)> { diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 068f800..674cedc 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -15,6 +15,7 @@ mod password; mod postgis; mod public; mod routes; +mod server_files_handlers; mod session_store; mod static_assets; mod test_routes; diff --git a/backend/src/routes.rs b/backend/src/routes.rs index 7aab1e6..85ec564 100644 --- a/backend/src/routes.rs +++ b/backend/src/routes.rs @@ -20,6 +20,7 @@ use crate::{ }, postgis::{register_postgis_source, test_postgis_connection}, public::{get_public_pmtiles, get_public_tile, get_public_tile_meta, head_public_pmtiles}, + server_files_handlers::{browse_directory, import_files, list_directories}, upload::upload_file, workspace_handlers::{ create_workspace, delete_workspace, get_current_workspace, get_workspace, invite_member, @@ -137,7 +138,11 @@ fn build_api_router_with_auth(state: AppState, with_auth: bool) -> Router { .route( "/api/workspaces/{id}/members/{user_id}", delete(remove_member), - ); + ) + // Server file import routes + .route("/api/server-files/directories", get(list_directories)) + .route("/api/server-files/browse", get(browse_directory)) + .route("/api/server-files/import", post(import_files)); if with_auth { api_router = api_router.route_layer(axum_login::login_required!(crate::AuthBackend)); diff --git a/backend/src/server_files_handlers.rs b/backend/src/server_files_handlers.rs new file mode 100644 index 0000000..b5e9381 --- /dev/null +++ b/backend/src/server_files_handlers.rs @@ -0,0 +1,541 @@ +use axum::{ + extract::{Query, State}, + http::StatusCode, + response::{IntoResponse, Json, Response}, +}; +use axum_login::AuthSession; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use tracing::{info, info_span, warn, Instrument}; + +use crate::{ + config::BYTES_PER_MB, handlers::get_workspace_id, import::import_spatial_data, mbtiles, + models::ErrorResponse, AppState, +}; + +const MAX_IMPORT_FILES: usize = 20; +const MAX_FILE_SIZE_MB: u64 = 1024; + +type ApiResult = Result; + +fn err(status: StatusCode, message: &str) -> Response { + ( + status, + Json(ErrorResponse { + error: message.to_string(), + }), + ) + .into_response() +} + +fn bad_req(message: &str) -> Response { + err(StatusCode::BAD_REQUEST, message) +} + +fn forbidden(message: &str) -> Response { + err(StatusCode::FORBIDDEN, message) +} + +fn not_found(message: &str) -> Response { + err(StatusCode::NOT_FOUND, message) +} + +fn internal_err(e: E) -> Response { + tracing::error!(error = ?e, "Internal server error"); + err(StatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error") +} + +/// Get allowed data directories from environment variable +fn get_allowed_directories() -> Vec { + std::env::var("SERVER_DATA_DIRS") + .unwrap_or_else(|_| "./data".to_string()) + .split(',') + .map(|s| PathBuf::from(s.trim())) + .filter(|p| p.exists() && p.is_dir()) + .collect() +} + +/// Get canonicalized allowed directories (cached for request lifetime) +fn get_allowed_directories_canonical() -> Vec { + get_allowed_directories() + .into_iter() + .filter_map(|p| p.canonicalize().ok()) + .collect() +} + +/// Check if a canonical path is within allowed directories +fn is_canonical_path_allowed(canonical: &PathBuf, allowed_dirs: &[PathBuf]) -> bool { + allowed_dirs + .iter() + .any(|allowed| canonical.starts_with(allowed)) +} + +/// Validate file extension is supported +fn is_supported_extension(ext: &str) -> bool { + let ext_lower = ext.to_lowercase(); + matches!( + ext_lower.as_str(), + "zip" + | "geojson" + | "json" + | "geojsonl" + | "geojsons" + | "kml" + | "gpx" + | "topojson" + | "mbtiles" + | "pmtiles" + ) +} + +#[derive(Debug, Serialize)] +pub struct DirectoryInfo { + pub path: String, + pub name: String, +} + +#[derive(Debug, Serialize)] +pub struct DirectoriesResponse { + pub directories: Vec, +} + +/// GET /api/server-files/directories +/// Returns list of allowed directories for server file import +pub async fn list_directories( + _auth_session: AuthSession, +) -> ApiResult { + let dirs = get_allowed_directories(); + + let directories: Vec = dirs + .into_iter() + .filter_map(|p| { + let canonical = p.canonicalize().ok()?; + let name = canonical.file_name()?.to_string_lossy().to_string(); + let path = canonical.to_string_lossy().to_string(); + Some(DirectoryInfo { path, name }) + }) + .collect(); + + Ok(Json(DirectoriesResponse { directories })) +} + +#[derive(Debug, Serialize)] +pub struct BrowseItem { + pub name: String, + #[serde(rename = "type")] + pub item_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub size: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ext: Option, +} + +#[derive(Debug, Serialize)] +pub struct BrowseResponse { + #[serde(rename = "currentPath")] + pub current_path: String, + #[serde(rename = "parentPath", skip_serializing_if = "Option::is_none")] + pub parent_path: Option, + pub items: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct BrowseQuery { + pub path: Option, +} + +/// GET /api/server-files/browse?path=... +/// Browse directory contents +pub async fn browse_directory( + _auth_session: AuthSession, + Query(query): Query, +) -> ApiResult { + let allowed_dirs = get_allowed_directories_canonical(); + if allowed_dirs.is_empty() { + return Err(bad_req("No data directories configured")); + } + + // Resolve path: canonicalize first, then check if allowed + let canonical = match query.path { + Some(ref p) if !p.is_empty() => { + let path = PathBuf::from(p); + path.canonicalize() + .map_err(|_| not_found("Directory not found"))? + } + _ => allowed_dirs.first().cloned().unwrap(), + }; + + // Security: verify canonical path is within allowed directories + if !is_canonical_path_allowed(&canonical, &allowed_dirs) { + return Err(forbidden("Access denied: path outside allowed directories")); + } + + if !canonical.is_dir() { + return Err(bad_req("Not a directory")); + } + + let read_dir = std::fs::read_dir(&canonical).map_err(internal_err)?; + + let mut items: Vec = Vec::new(); + for entry in read_dir { + let entry = match entry { + Ok(e) => e, + Err(_) => continue, + }; + + let name = match entry.file_name().to_str() { + Some(n) => n.to_string(), + None => continue, + }; + + // Skip hidden files + if name.starts_with('.') { + continue; + } + + let full_path = canonical.join(&name); + + let entry_canonical = match full_path.canonicalize() { + Ok(c) => c, + Err(_) => continue, + }; + + if !is_canonical_path_allowed(&entry_canonical, &allowed_dirs) { + continue; + } + + let metadata = match entry.metadata() { + Ok(m) => m, + Err(_) => continue, + }; + + if metadata.is_dir() { + items.push(BrowseItem { + name, + item_type: "directory".to_string(), + size: None, + ext: None, + }); + } else if metadata.is_file() { + let ext = full_path + .extension() + .map(|e| format!(".{}", e.to_string_lossy())); + + if let Some(ref e) = ext { + let ext_without_dot = e.trim_start_matches('.'); + if !is_supported_extension(ext_without_dot) { + continue; + } + } else { + continue; + } + + items.push(BrowseItem { + name, + item_type: "file".to_string(), + size: Some(metadata.len()), + ext, + }); + } + } + + // Sort: directories first, then files, both alphabetically + items.sort_by(|a, b| match (a.item_type.as_str(), b.item_type.as_str()) { + ("directory", "file") => std::cmp::Ordering::Less, + ("file", "directory") => std::cmp::Ordering::Greater, + _ => a.name.to_lowercase().cmp(&b.name.to_lowercase()), + }); + + // Calculate parent path if within allowed directories + let parent_path = canonical.parent().and_then(|p| { + if is_canonical_path_allowed(&p.to_path_buf(), &allowed_dirs) { + Some(p.to_string_lossy().to_string()) + } else { + None + } + }); + + Ok(Json(BrowseResponse { + current_path: canonical.to_string_lossy().to_string(), + parent_path, + items, + })) +} + +#[derive(Debug, Deserialize)] +pub struct ImportFile { + pub path: String, +} + +#[derive(Debug, Deserialize)] +pub struct ImportRequest { + pub files: Vec, +} + +#[derive(Debug, Serialize)] +pub struct ImportedFile { + pub id: String, + pub name: String, + pub path: String, + pub status: String, +} + +#[derive(Debug, Serialize)] +pub struct FailedFile { + pub path: String, + pub reason: String, +} + +#[derive(Debug, Serialize)] +pub struct ImportResponse { + pub imported: Vec, + pub failed: Vec, +} + +/// POST /api/server-files/import +/// Import files from server (reference mode - no copy) +pub async fn import_files( + auth_session: AuthSession, + State(state): State, + Json(req): Json, +) -> ApiResult { + let workspace_id = get_workspace_id(&auth_session, &state) + .await + .map_err(|(status, json)| err(status, &json.error))?; + + if req.files.is_empty() { + return Err(bad_req("No files specified")); + } + + if req.files.len() > MAX_IMPORT_FILES { + return Err(bad_req(&format!( + "Maximum {} files per import", + MAX_IMPORT_FILES + ))); + } + + let allowed_dirs = get_allowed_directories_canonical(); + let max_size_bytes = *state.max_size.read().await; + let mut imported = Vec::new(); + let mut failed = Vec::new(); + let mut files_to_process: Vec<(String, String, PathBuf)> = Vec::new(); + + { + let conn = state.db.lock().await; + + for file in &req.files { + let path = PathBuf::from(&file.path); + + let canonical = match path.canonicalize() { + Ok(c) => c, + Err(e) => { + warn!(error = %e, "Import failed: file not found"); + failed.push(FailedFile { + path: file.path.clone(), + reason: "File not found".to_string(), + }); + continue; + } + }; + + if !is_canonical_path_allowed(&canonical, &allowed_dirs) { + warn!("Import blocked: path outside allowed directories"); + failed.push(FailedFile { + path: file.path.clone(), + reason: "Path outside allowed directories".to_string(), + }); + continue; + } + + if !canonical.is_file() { + warn!("Import failed: not a file"); + failed.push(FailedFile { + path: file.path.clone(), + reason: "Not a file".to_string(), + }); + continue; + } + + let ext = canonical + .extension() + .map(|e| e.to_string_lossy().to_lowercase()); + + if let Some(ref e) = ext { + if !is_supported_extension(e) { + warn!(ext = %e, "Import failed: unsupported file type"); + failed.push(FailedFile { + path: file.path.clone(), + reason: format!("Unsupported file type: {}", e), + }); + continue; + } + } else { + failed.push(FailedFile { + path: file.path.clone(), + reason: "File has no extension".to_string(), + }); + continue; + } + + let metadata = match std::fs::metadata(&canonical) { + Ok(m) => m, + Err(e) => { + warn!(error = %e, "Import failed: cannot read metadata"); + failed.push(FailedFile { + path: file.path.clone(), + reason: "Cannot read file metadata".to_string(), + }); + continue; + } + }; + + let file_size = metadata.len(); + if file_size > max_size_bytes { + let max_mb = max_size_bytes / BYTES_PER_MB; + let file_mb = file_size / BYTES_PER_MB; + warn!( + file_mb = file_mb, + max_mb = max_mb, + "Import failed: file too large" + ); + failed.push(FailedFile { + path: file.path.clone(), + reason: format!("File too large ({}MB > {}MB limit)", file_mb, max_mb), + }); + continue; + } + + let file_name = canonical + .file_stem() + .map(|n| n.to_string_lossy().to_string()) + .unwrap_or_else(|| "imported".to_string()); + + let file_type = ext.unwrap_or_else(|| "unknown".to_string()); + let file_path_str = canonical.to_string_lossy().to_string(); + + let existing: Option = conn + .query_row( + "SELECT id FROM files WHERE path = ? AND workspace_id = ?", + duckdb::params![&file_path_str, &workspace_id], + |row| row.get(0), + ) + .ok() + .flatten(); + + if let Some(existing_id) = existing { + warn!(existing_id = %existing_id, "Import skipped: file already imported"); + failed.push(FailedFile { + path: file.path.clone(), + reason: "File already imported".to_string(), + }); + continue; + } + + let file_id = uuid::Uuid::new_v4().to_string(); + let now = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(); + let file_size_i64 = file_size as i64; + + let insert_result = conn.execute( + r#" + INSERT INTO files (id, name, type, size, uploaded_at, status, path, workspace_id, source_type) + VALUES (?, ?, ?, ?, ?, 'uploaded', ?, ?, 'server_import') + "#, + duckdb::params![ + &file_id, + &file_name, + &file_type, + file_size_i64, + &now, + &file_path_str, + &workspace_id + ], + ); + + match insert_result { + Ok(_) => { + info!(file_id = %file_id, workspace_id = %workspace_id, "Server file imported"); + imported.push(ImportedFile { + id: file_id.clone(), + name: file_name.clone(), + path: file_path_str.clone(), + status: "uploaded".to_string(), + }); + files_to_process.push((file_id, file_type, canonical)); + } + Err(e) => { + warn!(error = %e, "Import failed: database error"); + failed.push(FailedFile { + path: file.path.clone(), + reason: "Database error".to_string(), + }); + } + } + } + } + + for (file_id, file_type, file_path) in files_to_process { + let db = state.db.clone(); + let span = info_span!("server_import", file_id = %file_id, file_type = %file_type); + + tokio::spawn( + async move { + tracing::info!("Starting server file import processing"); + { + let conn = db.lock().await; + if let Err(e) = conn.execute( + "UPDATE files SET status = 'processing' WHERE id = ?", + duckdb::params![&file_id], + ) { + tracing::error!(error = %e, file_id = %file_id, "Failed to update status to processing"); + return; + } + } + + let result: Result<(), String> = match file_type.as_str() { + "mbtiles" => mbtiles::import_mbtiles(&db, &file_id, &file_path).await, + "pmtiles" => { + let conn = db.lock().await; + match conn.execute( + "UPDATE files SET status = 'ready', tile_source = 'pmtiles' WHERE id = ?", + duckdb::params![&file_id], + ) { + Ok(_) => Ok(()), + Err(e) => Err(e.to_string()), + } + } + _ => import_spatial_data(&db, &file_id, &file_path).await, + }; + + match result { + Ok(_) => { + tracing::info!(file_id = %file_id, "Server file import completed successfully"); + let conn = db.lock().await; + if let Err(e) = conn.execute( + "UPDATE files SET status = 'ready' WHERE id = ?", + duckdb::params![&file_id], + ) { + tracing::error!(error = %e, file_id = %file_id, "Failed to update status to ready"); + } + } + Err(e) => { + tracing::error!(error = %e, file_id = %file_id, "Server file import failed"); + let conn = db.lock().await; + if let Err(update_err) = conn.execute( + "UPDATE files SET status = 'failed', error = ? WHERE id = ?", + duckdb::params![e.to_string(), &file_id], + ) { + tracing::error!(error = %update_err, file_id = %file_id, "Failed to update status to failed"); + } + } + } + } + .instrument(span), + ); + } + + if imported.is_empty() { + return Err(bad_req("No files could be imported")); + } + + Ok(Json(ImportResponse { imported, failed })) +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 231b4d9..433c3f7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -15,6 +15,9 @@ import { updatePublishSettings, listWorkspaces, switchWorkspace, + listServerDirectories, + browseServerDirectory, + importServerFiles, } from './api.js'; import { formatSize, parseType, validateSlug } from './utils.js'; @@ -1312,6 +1315,16 @@ export default function App() { ); const [showWorkspaceDropdown, setShowWorkspaceDropdown] = useState(false); const [workspaceLoading, setWorkspaceLoading] = useState(false); + // Server file import states + const [showServerImportModal, setShowServerImportModal] = useState(false); + const [serverDirectories, setServerDirectories] = useState([]); + const [currentBrowsePath, setCurrentBrowsePath] = useState(''); + const [parentPath, setParentPath] = useState(null); + const [browseItems, setBrowseItems] = useState([]); + const [selectedFiles, setSelectedFiles] = useState([]); + const [isLoadingBrowse, setIsLoadingBrowse] = useState(false); + const [isImporting, setIsImporting] = useState(false); + const [importError, setImportError] = useState(''); async function refreshFiles(nextSelectedId = null) { const data = await listFiles(); @@ -1411,6 +1424,103 @@ export default function App() { } } + // Server file import functions + async function openServerImportModal() { + setShowServerImportModal(true); + setImportError(''); + setSelectedFiles([]); + setIsLoadingBrowse(true); + + try { + const dirs = await listServerDirectories(); + setServerDirectories(dirs.directories || []); + + if (dirs.directories && dirs.directories.length > 0) { + await browseServerPath(dirs.directories[0].path); + } + } catch (error) { + setImportError(error instanceof Error ? error.message : '获取服务器目录失败'); + } finally { + setIsLoadingBrowse(false); + } + } + + async function browseServerPath(path) { + setIsLoadingBrowse(true); + setCurrentBrowsePath(path); + setImportError(''); + + try { + const result = await browseServerDirectory(path); + setCurrentBrowsePath(result.currentPath); + setParentPath(result.parentPath); + setBrowseItems(result.items || []); + } catch (error) { + setImportError(error instanceof Error ? error.message : '浏览目录失败'); + setBrowseItems([]); + } finally { + setIsLoadingBrowse(false); + } + } + + function toggleFileSelection(item) { + if (item.type !== 'file') return; + + const filePath = joinPath(currentBrowsePath, item.name); + setSelectedFiles((prev) => { + if (prev.includes(filePath)) { + return prev.filter((f) => f !== filePath); + } else { + return [...prev, filePath]; + } + }); + } + + function formatFileSize(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; + } + + function joinPath(base, name) { + const normalizedBase = base.endsWith('/') ? base.slice(0, -1) : base; + return `${normalizedBase}/${name}`; + } + + async function handleImportFiles() { + if (selectedFiles.length === 0) { + setImportError('请选择要导入的文件'); + return; + } + + setIsImporting(true); + setImportError(''); + + try { + const result = await importServerFiles(selectedFiles); + + if (result.failed && result.failed.length > 0) { + const failedNames = result.failed.map((f) => `${f.path}: ${f.reason}`).join('\n'); + setImportError(`部分文件导入失败:\n${failedNames}`); + setSelectedFiles([]); + } + + if (result.imported && result.imported.length > 0) { + await refreshFiles(result.imported[0].id); + if (!result.failed || result.failed.length === 0) { + setShowServerImportModal(false); + setSelectedFiles([]); + } + } + } catch (error) { + setImportError(error instanceof Error ? error.message : '导入文件失败'); + } finally { + setIsImporting(false); + } + } + async function handlePublish(fileId, options) { const result = await publishFile(fileId, options); setFiles((prev) => @@ -1702,6 +1812,11 @@ export default function App() { /> 上传 + {user && ( + + )} {user && ( + +
+
+ ⚠️ 文件将被引用,不会被复制。原文件变更会影响数据。 +
+ + {importError && ( +
+ {importError} +
+ )} + + {/* Directory breadcrumb */} +
+ 📂 + + {currentBrowsePath || '/data'} + +
+ + {/* Directory selector */} + {serverDirectories.length > 1 && ( +
+ +
+ )} + + {isLoadingBrowse ? ( +
加载中...
+ ) : ( +
+ {/* Parent directory link */} + {parentPath && ( +
browseServerPath(parentPath)} + style={{ + padding: '10px 12px', + borderBottom: '1px solid #e0e0e0', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + gap: '8px', + }} + > + 📁 + .. +
+ )} + + {/* Items list */} + {browseItems.length === 0 ? ( +
+ 目录为空 +
+ ) : ( + browseItems.map((item, index) => ( +
+ item.type === 'directory' + ? browseServerPath(joinPath(currentBrowsePath, item.name)) + : toggleFileSelection(item) + } + style={{ + padding: '10px 12px', + borderBottom: + index < browseItems.length - 1 ? '1px solid #e0e0e0' : 'none', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + gap: '8px', + background: + item.type === 'file' && + selectedFiles.includes(joinPath(currentBrowsePath, item.name)) + ? '#e3f2fd' + : 'transparent', + }} + > + {item.type === 'directory' ? ( + <> + 📁 + {item.name} + + + ) : ( + <> + {}} + onClick={(e) => e.stopPropagation()} + style={{ marginRight: '4px' }} + /> + 📄 + {item.name} + + {formatFileSize(item.size || 0)} + + + )} +
+ )) + )} +
+ )} + + {/* Selection summary */} + {selectedFiles.length > 0 && ( +
+ 已选择 {selectedFiles.length} 个文件 +
+ )} +
+
+ + +
+ + + )} ); } diff --git a/frontend/src/api.js b/frontend/src/api.js index fd691fd..256a5a7 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -298,3 +298,38 @@ export async function leaveWorkspace(workspaceId) { } return null; } + +// Server file import APIs +export async function listServerDirectories() { + const res = await fetchWithAuth('/api/server-files/directories'); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.error || '获取服务器目录失败'); + } + return res.json(); +} + +export async function browseServerDirectory(path) { + const url = path + ? `/api/server-files/browse?path=${encodeURIComponent(path)}` + : '/api/server-files/browse'; + const res = await fetchWithAuth(url); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.error || '浏览目录失败'); + } + return res.json(); +} + +export async function importServerFiles(files) { + const res = await fetchWithAuth('/api/server-files/import', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ files: files.map((f) => ({ path: f })) }), + }); + if (!res.ok) { + const data = await res.json().catch(() => ({})); + throw new Error(data.error || '导入文件失败'); + } + return res.json(); +}