diff --git a/.vscode/settings.json b/.vscode/settings.json
index bfc0469..c33b832 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,6 @@
{
"i18n-ally.localesPaths": [
"frontend/src/locales"
- ]
+ ],
+ "i18n-ally.keystyle": "nested"
}
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index f5b68eb..60e1537 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -22,12 +22,12 @@ dependencies = [
"bytes",
"cfg-if",
"http",
- "indexmap",
- "schemars",
+ "indexmap 2.14.0",
+ "schemars 0.9.0",
"serde",
"serde_json",
"serde_qs",
- "thiserror",
+ "thiserror 2.0.19",
"tower-layer",
"tower-service",
"tracing",
@@ -39,7 +39,7 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f2a08f14808f3c46f3e3004b727bace64af44c3c5996d0480a14d3852b1b25a"
dependencies = [
- "darling",
+ "darling 0.20.11",
"proc-macro2",
"quote",
"syn 2.0.119",
@@ -151,6 +151,25 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "axum-login"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "964ea6eb764a227baa8c3368e45c94d23b6863cc7b880c6c9e341c143c5a5ff7"
+dependencies = [
+ "axum",
+ "form_urlencoded",
+ "serde",
+ "subtle",
+ "thiserror 2.0.19",
+ "tower-cookies",
+ "tower-layer",
+ "tower-service",
+ "tower-sessions",
+ "tracing",
+ "urlencoding",
+]
+
[[package]]
name = "axum-macros"
version = "0.5.1"
@@ -162,6 +181,18 @@ dependencies = [
"syn 2.0.119",
]
+[[package]]
+name = "base16ct"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
[[package]]
name = "base64"
version = "0.22.1"
@@ -174,6 +205,12 @@ version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
[[package]]
name = "bitflags"
version = "2.13.1"
@@ -192,6 +229,15 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "bs58"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
+dependencies = [
+ "tinyvec",
+]
+
[[package]]
name = "bumpalo"
version = "3.20.3"
@@ -217,15 +263,19 @@ dependencies = [
"aide",
"async-trait",
"axum",
+ "axum-login",
"chrono",
"config",
- "schemars",
+ "indexmap 2.14.0",
+ "openidconnect",
+ "schemars 0.9.0",
"serde",
"serde_json",
"sqlx",
- "thiserror",
+ "thiserror 2.0.19",
"tokio",
"tower-http",
+ "tower-sessions",
"tracing",
"tracing-subscriber",
]
@@ -246,6 +296,23 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+[[package]]
+name = "cfg_aliases"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
+
+[[package]]
+name = "chacha20"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "rand_core 0.10.1",
+]
+
[[package]]
name = "chrono"
version = "0.4.45"
@@ -253,8 +320,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
+ "js-sys",
"num-traits",
"serde",
+ "wasm-bindgen",
"windows-link",
]
@@ -299,7 +368,7 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
- "getrandom",
+ "getrandom 0.2.17",
"once_cell",
"tiny-keccak",
]
@@ -313,6 +382,17 @@ dependencies = [
"unicode-segmentation",
]
+[[package]]
+name = "cookie"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87"
+dependencies = [
+ "percent-encoding",
+ "time",
+ "version_check",
+]
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
@@ -328,6 +408,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "crc"
version = "3.4.0"
@@ -364,6 +453,18 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+[[package]]
+name = "crypto-bigint"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "crypto-common"
version = "0.1.6"
@@ -374,14 +475,51 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
- "darling_core",
- "darling_macro",
+ "darling_core 0.20.11",
+ "darling_macro 0.20.11",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core 0.23.0",
+ "darling_macro 0.23.0",
]
[[package]]
@@ -398,17 +536,72 @@ dependencies = [
"syn 2.0.119",
]
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.119",
+]
+
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
- "darling_core",
+ "darling_core 0.20.11",
"quote",
"syn 2.0.119",
]
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core 0.23.0",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "defmt"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
+dependencies = [
+ "bitflags 1.3.2",
+ "defmt-macros",
+]
+
+[[package]]
+name = "defmt-macros"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
+dependencies = [
+ "defmt-parser",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "defmt-parser"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
+dependencies = [
+ "thiserror 2.0.19",
+]
+
[[package]]
name = "der"
version = "0.7.10"
@@ -420,6 +613,15 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "serde_core",
+]
+
[[package]]
name = "digest"
version = "0.10.7"
@@ -464,6 +666,44 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
+[[package]]
+name = "ecdsa"
+version = "0.16.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
+dependencies = [
+ "der",
+ "digest",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+ "spki",
+]
+
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "pkcs8",
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "serde",
+ "sha2",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "either"
version = "1.17.0"
@@ -473,6 +713,27 @@ dependencies = [
"serde",
]
+[[package]]
+name = "elliptic-curve"
+version = "0.13.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "digest",
+ "ff",
+ "generic-array",
+ "group",
+ "hkdf",
+ "pem-rfc7468",
+ "pkcs8",
+ "rand_core 0.6.4",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "encoding_rs"
version = "0.8.35"
@@ -530,6 +791,22 @@ dependencies = [
"pin-project-lite",
]
+[[package]]
+name = "ff"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
+dependencies = [
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
@@ -681,6 +958,7 @@ checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
dependencies = [
"typenum",
"version_check",
+ "zeroize",
]
[[package]]
@@ -690,10 +968,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
"wasi",
+ "wasm-bindgen",
]
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi",
+ "rand_core 0.10.1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "group"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
+dependencies = [
+ "ff",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
[[package]]
name = "hashbrown"
version = "0.14.5"
@@ -852,6 +1163,23 @@ dependencies = [
"pin-project-lite",
"smallvec",
"tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+ "webpki-roots",
]
[[package]]
@@ -860,13 +1188,21 @@ version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
+ "base64 0.22.1",
"bytes",
+ "futures-channel",
+ "futures-util",
"http",
"http-body",
"hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
"pin-project-lite",
+ "socket2",
"tokio",
"tower-service",
+ "tracing",
]
[[package]]
@@ -1002,6 +1338,17 @@ dependencies = [
"icu_properties",
]
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
[[package]]
name = "indexmap"
version = "2.14.0"
@@ -1014,12 +1361,80 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "ipnet"
+version = "2.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+[[package]]
+name = "jiff"
+version = "0.2.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc"
+dependencies = [
+ "defmt",
+ "jiff-core",
+ "jiff-static",
+ "jiff-tzdb-platform",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde_core",
+ "windows-link",
+]
+
+[[package]]
+name = "jiff-core"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09"
+dependencies = [
+ "defmt",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204"
+dependencies = [
+ "jiff-core",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "jiff-tzdb"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e"
+
+[[package]]
+name = "jiff-tzdb-platform"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
+dependencies = [
+ "jiff-tzdb",
+]
+
[[package]]
name = "js-sys"
version = "0.3.103"
@@ -1069,7 +1484,7 @@ version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
dependencies = [
- "bitflags",
+ "bitflags 2.13.1",
"libc",
"plain",
"redox_syscall 0.9.1",
@@ -1098,6 +1513,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
+ "serde",
]
[[package]]
@@ -1106,6 +1522,12 @@ version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
[[package]]
name = "matchers"
version = "0.2.0"
@@ -1184,11 +1606,17 @@ dependencies = [
"num-integer",
"num-iter",
"num-traits",
- "rand",
+ "rand 0.8.7",
"smallvec",
"zeroize",
]
+[[package]]
+name = "num-conv"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
+
[[package]]
name = "num-integer"
version = "0.1.46"
@@ -1218,12 +1646,72 @@ dependencies = [
"libm",
]
+[[package]]
+name = "oauth2"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
+dependencies = [
+ "base64 0.22.1",
+ "chrono",
+ "getrandom 0.2.17",
+ "http",
+ "rand 0.8.7",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "sha2",
+ "thiserror 1.0.69",
+ "url",
+]
+
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+[[package]]
+name = "openidconnect"
+version = "4.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d8c6709ba2ea764bbed26bce1adf3c10517113ddea6f2d4196e4851757ef2b2"
+dependencies = [
+ "base64 0.21.7",
+ "chrono",
+ "dyn-clone",
+ "ed25519-dalek",
+ "hmac",
+ "http",
+ "itertools",
+ "log",
+ "oauth2",
+ "p256",
+ "p384",
+ "rand 0.8.7",
+ "rsa",
+ "serde",
+ "serde-value",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_plain",
+ "serde_with",
+ "sha2",
+ "subtle",
+ "thiserror 1.0.69",
+ "url",
+]
+
+[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
+]
+
[[package]]
name = "ordered-multimap"
version = "0.7.3"
@@ -1234,6 +1722,30 @@ dependencies = [
"hashbrown 0.14.5",
]
+[[package]]
+name = "p256"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
+ "sha2",
+]
+
+[[package]]
+name = "p384"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
+ "sha2",
+]
+
[[package]]
name = "parking"
version = "2.2.1"
@@ -1365,6 +1877,21 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
+[[package]]
+name = "portable-atomic"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
+dependencies = [
+ "portable-atomic",
+]
+
[[package]]
name = "potential_utf"
version = "0.1.5"
@@ -1374,6 +1901,12 @@ dependencies = [
"zerovec",
]
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
[[package]]
name = "ppv-lite86"
version = "0.2.21"
@@ -1383,6 +1916,15 @@ dependencies = [
"zerocopy",
]
+[[package]]
+name = "primeorder"
+version = "0.13.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
+dependencies = [
+ "elliptic-curve",
+]
+
[[package]]
name = "proc-macro2"
version = "1.0.107"
@@ -1392,6 +1934,62 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "quinn"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror 2.0.19",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83"
+dependencies = [
+ "bytes",
+ "getrandom 0.4.3",
+ "lru-slab",
+ "rand 0.10.2",
+ "rand_pcg",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.19",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "quote"
version = "1.0.47"
@@ -1401,6 +1999,12 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
[[package]]
name = "rand"
version = "0.8.7"
@@ -1409,7 +2013,18 @@ checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
dependencies = [
"libc",
"rand_chacha",
- "rand_core",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.3",
+ "rand_core 0.10.1",
]
[[package]]
@@ -1419,7 +2034,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
- "rand_core",
+ "rand_core 0.6.4",
]
[[package]]
@@ -1428,7 +2043,22 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
- "getrandom",
+ "getrandom 0.2.17",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
+name = "rand_pcg"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
+dependencies = [
+ "rand_core 0.10.1",
]
[[package]]
@@ -1437,7 +2067,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
- "bitflags",
+ "bitflags 2.13.1",
]
[[package]]
@@ -1446,7 +2076,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07507be7b4a5f9f26eeb41eeaebb1f5a7ff29dfb29739facc21d35bf8b11c21e"
dependencies = [
- "bitflags",
+ "bitflags 2.13.1",
]
[[package]]
@@ -1486,13 +2116,75 @@ version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
+[[package]]
+name = "reqwest"
+version = "0.12.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "webpki-roots",
+]
+
+[[package]]
+name = "rfc6979"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
+dependencies = [
+ "hmac",
+ "subtle",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
[[package]]
name = "ron"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81116b9531d61eabc41aeb228e4b6b2435bcca3233b98cf3b3077d4e6e9debb3"
dependencies = [
- "bitflags",
+ "bitflags 2.13.1",
"once_cell",
"serde",
"serde_derive",
@@ -1513,7 +2205,7 @@ dependencies = [
"num-traits",
"pkcs1",
"pkcs8",
- "rand_core",
+ "rand_core 0.6.4",
"signature",
"spki",
"subtle",
@@ -1530,6 +2222,56 @@ dependencies = [
"ordered-multimap",
]
+[[package]]
+name = "rustc-hash"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
[[package]]
name = "rustversion"
version = "1.0.23"
@@ -1550,13 +2292,25 @@ checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
dependencies = [
"chrono",
"dyn-clone",
- "indexmap",
+ "indexmap 2.14.0",
"ref-cast",
"schemars_derive",
"serde",
"serde_json",
]
+[[package]]
+name = "schemars"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "schemars_derive"
version = "0.9.0"
@@ -1575,6 +2329,26 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+[[package]]
+name = "sec1"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+dependencies = [
+ "base16ct",
+ "der",
+ "generic-array",
+ "pkcs8",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+
[[package]]
name = "serde"
version = "1.0.229"
@@ -1597,6 +2371,16 @@ dependencies = [
"typeid",
]
+[[package]]
+name = "serde-value"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float",
+ "serde",
+]
+
[[package]]
name = "serde_core"
version = "1.0.229"
@@ -1652,6 +2436,15 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "serde_plain"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "serde_qs"
version = "0.14.0"
@@ -1662,7 +2455,7 @@ dependencies = [
"futures",
"percent-encoding",
"serde",
- "thiserror",
+ "thiserror 2.0.19",
]
[[package]]
@@ -1686,6 +2479,39 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_with"
+version = "3.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a"
+dependencies = [
+ "base64 0.22.1",
+ "bs58",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.14.0",
+ "jiff",
+ "schemars 0.9.0",
+ "schemars 1.2.2",
+ "serde_core",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46"
+dependencies = [
+ "darling 0.23.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
[[package]]
name = "sha1"
version = "0.10.7"
@@ -1693,7 +2519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"digest",
]
@@ -1704,7 +2530,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"digest",
]
@@ -1740,7 +2566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
- "rand_core",
+ "rand_core 0.6.4",
]
[[package]]
@@ -1806,7 +2632,7 @@ version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bytes",
"chrono",
"crc",
@@ -1819,7 +2645,7 @@ dependencies = [
"futures-util",
"hashbrown 0.15.5",
"hashlink 0.10.0",
- "indexmap",
+ "indexmap 2.14.0",
"log",
"memchr",
"once_cell",
@@ -1828,7 +2654,7 @@ dependencies = [
"serde_json",
"sha2",
"smallvec",
- "thiserror",
+ "thiserror 2.0.19",
"tokio",
"tokio-stream",
"tracing",
@@ -1880,8 +2706,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
dependencies = [
"atoi",
- "base64",
- "bitflags",
+ "base64 0.22.1",
+ "bitflags 2.13.1",
"byteorder",
"bytes",
"chrono",
@@ -1903,7 +2729,7 @@ dependencies = [
"memchr",
"once_cell",
"percent-encoding",
- "rand",
+ "rand 0.8.7",
"rsa",
"serde",
"sha1",
@@ -1911,7 +2737,7 @@ dependencies = [
"smallvec",
"sqlx-core",
"stringprep",
- "thiserror",
+ "thiserror 2.0.19",
"tracing",
"whoami",
]
@@ -1923,8 +2749,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
dependencies = [
"atoi",
- "base64",
- "bitflags",
+ "base64 0.22.1",
+ "bitflags 2.13.1",
"byteorder",
"chrono",
"crc",
@@ -1942,14 +2768,14 @@ dependencies = [
"md-5",
"memchr",
"once_cell",
- "rand",
+ "rand 0.8.7",
"serde",
"serde_json",
"sha2",
"smallvec",
"sqlx-core",
"stringprep",
- "thiserror",
+ "thiserror 2.0.19",
"tracing",
"whoami",
]
@@ -1974,7 +2800,7 @@ dependencies = [
"serde",
"serde_urlencoded",
"sqlx-core",
- "thiserror",
+ "thiserror 2.0.19",
"tracing",
"url",
]
@@ -2035,6 +2861,9 @@ name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
[[package]]
name = "synstructure"
@@ -2047,13 +2876,33 @@ dependencies = [
"syn 2.0.119",
]
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
[[package]]
name = "thiserror"
version = "2.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
dependencies = [
- "thiserror-impl",
+ "thiserror-impl 2.0.19",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
]
[[package]]
@@ -2076,6 +2925,36 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "time"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
+dependencies = [
+ "deranged",
+ "num-conv",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
+
+[[package]]
+name = "time-macros"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
[[package]]
name = "tiny-keccak"
version = "2.0.2"
@@ -2137,6 +3016,16 @@ dependencies = [
"syn 3.0.3",
]
+[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
[[package]]
name = "tokio-stream"
version = "0.1.19"
@@ -2208,13 +3097,29 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "tower-cookies"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36"
+dependencies = [
+ "axum-core",
+ "cookie",
+ "futures-util",
+ "http",
+ "parking_lot",
+ "pin-project-lite",
+ "tower-layer",
+ "tower-service",
+]
+
[[package]]
name = "tower-http"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
- "bitflags",
+ "bitflags 2.13.1",
"bytes",
"futures-core",
"futures-util",
@@ -2229,8 +3134,10 @@ dependencies = [
"pin-project-lite",
"tokio",
"tokio-util",
+ "tower",
"tower-layer",
"tower-service",
+ "url",
]
[[package]]
@@ -2245,6 +3152,56 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+[[package]]
+name = "tower-sessions"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a05911f23e8fae446005fe9b7b97e66d95b6db589dc1c4d59f6a2d4d4927d3"
+dependencies = [
+ "async-trait",
+ "http",
+ "time",
+ "tokio",
+ "tower-cookies",
+ "tower-layer",
+ "tower-service",
+ "tower-sessions-core",
+ "tower-sessions-memory-store",
+ "tracing",
+]
+
+[[package]]
+name = "tower-sessions-core"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce8cce604865576b7751b7a6bc3058f754569a60d689328bb74c52b1d87e355b"
+dependencies = [
+ "async-trait",
+ "base64 0.22.1",
+ "futures",
+ "http",
+ "parking_lot",
+ "rand 0.8.7",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.19",
+ "time",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "tower-sessions-memory-store"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb05909f2e1420135a831dd5df9f5596d69196d0a64c3499ca474c4bd3d33242"
+dependencies = [
+ "async-trait",
+ "time",
+ "tokio",
+ "tower-sessions-core",
+]
+
[[package]]
name = "tracing"
version = "0.1.44"
@@ -2307,6 +3264,12 @@ dependencies = [
"tracing-log",
]
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
[[package]]
name = "typeid"
version = "1.0.3"
@@ -2364,6 +3327,12 @@ version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
[[package]]
name = "url"
version = "2.5.8"
@@ -2374,8 +3343,15 @@ dependencies = [
"idna",
"percent-encoding",
"serde",
+ "serde_derive",
]
+[[package]]
+name = "urlencoding"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+
[[package]]
name = "utf8_iter"
version = "1.0.4"
@@ -2400,6 +3376,15 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@@ -2425,6 +3410,16 @@ dependencies = [
"wasm-bindgen-shared",
]
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.126"
@@ -2457,6 +3452,35 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "web-sys"
+version = "0.3.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
+dependencies = [
+ "rustls-pki-types",
+]
+
[[package]]
name = "whoami"
version = "1.6.1"
@@ -2532,7 +3556,16 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
- "windows-targets",
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
]
[[package]]
@@ -2550,13 +3583,29 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+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",
+ "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]]
@@ -2565,42 +3614,90 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+[[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_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+[[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_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+[[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_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
[[package]]
name = "winnow"
version = "1.0.4"
diff --git a/Cargo.toml b/Cargo.toml
index aca7a08..ae825ee 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,8 +13,11 @@ aide = { version = "0.15.1", features = [
] }
async-trait = "0.1.89"
axum = { version = "0.8.9", features = ["macros"] }
+axum-login = "0.18.0"
chrono = { version = "0.4.45", default-features = false, features = ["serde", "clock", "std"] }
config = "0.15.23"
+indexmap = "2.14.0"
+openidconnect = "4.0.1"
schemars = { version = "0.9", features = ["chrono04"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
@@ -27,5 +30,6 @@ sqlx = { version = "0.8.6", features = [
thiserror = "2.0.18"
tokio = { version = "1.52.3", features = ["rt-multi-thread", "signal"] }
tower-http = { version = "0.6.10", features = ["fs"] }
+tower-sessions = { version = "0.14", default-features = false, features = ["memory-store"] }
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
diff --git a/README.md b/README.md
index 6cb6b03..85c7e6c 100644
--- a/README.md
+++ b/README.md
@@ -17,11 +17,17 @@ routes and the frontend views are not written yet.
- Tailwind 4 and the shadcn-vue components already vendored in `frontend/src/components/ui`
- A single binary in production: the backend serves the built frontend
-Authentication is **not wired yet**. Users are mirrored from AGEPoly's OIDC provider
-(Whiskey): `users.oidc_sub` is the identity, and `AppController::login` upserts the row from
-the claims of the token. Because the units also come from Whiskey, they are stored as plain
-text (`units_users.unit_name`, `reservations.unit`) rather than as an enum or a reference
-table: a unit unknown to us must never break a login.
+Users are mirrored from AGEPoly's OIDC provider (Whiskey): `users.oidc_sub` is the
+identity, and a login upserts the row from the claims of the token. Whiskey calls the units
+**groups** and sends them in the `groups` claim; each one is a row in `units`, shared by
+everybody who belongs to it, and the memberships are rewritten at every login — so a user
+removed from a group there loses it here too. A group we have never seen is created on the
+spot rather than rejected: an unknown unit must never break a login.
+
+`admin` is ours, and is never touched by a login.
+
+In a debug build, `dev_users` from the configuration can be logged in through
+`POST /api/login` without going through the provider — see the login page.
## Getting started
@@ -90,6 +96,27 @@ another implementation (a mock in tests, another storage) without touching the l
Handlers stay thin — extract the controller, call it, map the error to a status code — and
their OpenAPI documentation sits right next to them (`fn *_docs`).
+### Authorization is a type, not a check
+
+There are four controllers, each dereferencing into the one above it:
+
+```
+AnonAppController anybody: reading the fleet, the calendar, the login routes
+ └─ AppController a logged in user
+ ├─ ManagerAppController a member of one unit, acting for that unit
+ └─ AdminAppController an admin
+```
+
+A handler declares what it needs in its signature. `AnonAppController` always extracts;
+`AppController` resolves the session cookie and answers **401** on its own, so a protected
+route cannot be left open by forgetting a check. Going further down is explicit and
+fallible — `try_into_manager(unit)` and `try_into_admin()`, wrapped by `api::helpers` so a
+refusal becomes a 403.
+
+Sessions are handled by `axum-login` on top of `tower-sessions`, wired in `api/mod.rs`.
+The store is in memory: **everybody is logged out when the backend restarts**. Swap it for a
+persistent store if that becomes a problem.
+
### Adding an entity
1. `dbmate n create_things` and write the migration, then `dbmate up`
@@ -157,10 +184,13 @@ vue router keeps working on a page reload. Only one process to deploy.
## Data model
```
-users ──< units_users a user belongs to several units (from Whiskey)
- │
- ├──< reservations_users >── reservations ──< reservations_bikes >── bikes
- └──── reservations.requester_id
+units ──< units_users >── users a unit (a Whiskey group) has many members,
+ │ │ and a user belongs to many units
+ │ ├──< reservations_users >── reservations
+ │ └──── reservations.requester_id
+ └──── reservations.unit_id exactly one unit borrows the bikes
+
+reservations ──< reservations_bikes >── bikes
```
A reservation moves through a state machine, enforced in
diff --git a/config.example.yml b/config.example.yml
index 067184c..e69de29 100644
--- a/config.example.yml
+++ b/config.example.yml
@@ -1,15 +0,0 @@
-# Copy to config.yml (gitignored) and adapt.
-# Every value can also be given as an environment variable, e.g. APP__SERVER__PORT=3000
-server:
- address: 0.0.0.0
- port: 3000
-
-postgres:
- host: localhost
- port: 5432
- user: postgres
- password: postgres
- name: cargagep
-
-# Directory containing the built frontend (frontend/dist after `npm run build`)
-frontend_dir: frontend/dist
diff --git a/db/migrations/20260823170000_create_oidc_states.sql b/db/migrations/20260823170000_create_oidc_states.sql
new file mode 100644
index 0000000..2f51d85
--- /dev/null
+++ b/db/migrations/20260823170000_create_oidc_states.sql
@@ -0,0 +1,16 @@
+-- migrate:up
+
+-- Short lived state of an in-flight OIDC authorization: the csrf token is the
+-- key the provider hands back, `data` holds the pkce verifier and the nonce.
+-- A row is consumed by the callback (deleted on read), so a state can never be
+-- replayed, and `created_at` lets the stale ones be swept.
+CREATE TABLE oidc_states (
+ key TEXT PRIMARY KEY,
+ data TEXT NOT NULL,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
+);
+
+CREATE INDEX oidc_states_created_at_idx ON oidc_states (created_at);
+
+-- migrate:down
+DROP TABLE IF EXISTS oidc_states;
diff --git a/db/migrations/20260823210000_create_units.sql b/db/migrations/20260823210000_create_units.sql
new file mode 100644
index 0000000..2662b38
--- /dev/null
+++ b/db/migrations/20260823210000_create_units.sql
@@ -0,0 +1,56 @@
+-- migrate:up
+
+-- A unit (an AGEPoly committee or commission) is a row of its own rather than a
+-- string repeated on every membership: it can be renamed, and per-unit data can
+-- be hung off it later without touching anything else. `name` is the Whiskey
+-- group name and stays the key we match the provider on.
+CREATE TABLE units (
+ id SERIAL PRIMARY KEY,
+ name TEXT NOT NULL UNIQUE
+);
+
+-- Everything that already named a unit becomes a row
+INSERT INTO units ("name")
+SELECT DISTINCT unit_name FROM units_users
+UNION
+SELECT DISTINCT unit FROM reservations
+ON CONFLICT ("name") DO NOTHING;
+
+-- units_users: unit_name -> unit_id
+ALTER TABLE units_users
+ ADD COLUMN unit_id INTEGER REFERENCES units (id) ON DELETE CASCADE;
+UPDATE units_users uu SET unit_id = u.id FROM units u WHERE u."name" = uu.unit_name;
+ALTER TABLE units_users ALTER COLUMN unit_id SET NOT NULL;
+ALTER TABLE units_users DROP CONSTRAINT units_users_pkey;
+ALTER TABLE units_users DROP COLUMN unit_name;
+ALTER TABLE units_users ADD PRIMARY KEY (unit_id, user_id);
+
+CREATE INDEX units_users_user_id_idx ON units_users (user_id);
+
+-- reservations: unit -> unit_id
+ALTER TABLE reservations ADD COLUMN unit_id INTEGER REFERENCES units (id);
+UPDATE reservations r SET unit_id = u.id FROM units u WHERE u."name" = r.unit;
+ALTER TABLE reservations ALTER COLUMN unit_id SET NOT NULL;
+DROP INDEX reservations_unit_idx;
+ALTER TABLE reservations DROP COLUMN unit;
+CREATE INDEX reservations_unit_id_idx ON reservations (unit_id);
+
+-- migrate:down
+
+ALTER TABLE reservations ADD COLUMN unit TEXT;
+UPDATE reservations r SET unit = u."name" FROM units u WHERE u.id = r.unit_id;
+ALTER TABLE reservations ALTER COLUMN unit SET NOT NULL;
+DROP INDEX reservations_unit_id_idx;
+ALTER TABLE reservations DROP COLUMN unit_id;
+CREATE INDEX reservations_unit_idx ON reservations (unit);
+
+ALTER TABLE units_users ADD COLUMN unit_name TEXT;
+UPDATE units_users uu SET unit_name = u."name" FROM units u WHERE u.id = uu.unit_id;
+ALTER TABLE units_users ALTER COLUMN unit_name SET NOT NULL;
+ALTER TABLE units_users DROP CONSTRAINT units_users_pkey;
+DROP INDEX units_users_user_id_idx;
+ALTER TABLE units_users DROP COLUMN unit_id;
+ALTER TABLE units_users ADD PRIMARY KEY (user_id, unit_name);
+CREATE INDEX units_users_unit_name_idx ON units_users (unit_name);
+
+DROP TABLE units;
diff --git a/db/schema.sql b/db/schema.sql
index f2912e8..6288684 100644
--- a/db/schema.sql
+++ b/db/schema.sql
@@ -1,6 +1,6 @@
\restrict dbmate
--- Dumped from database version 18.6
+-- Dumped from database version 18.3
-- Dumped by pg_dump version 18.6
SET statement_timeout = 0;
@@ -79,19 +79,30 @@ CREATE SEQUENCE public.bikes_id_seq
ALTER SEQUENCE public.bikes_id_seq OWNED BY public.bikes.id;
+--
+-- Name: oidc_states; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.oidc_states (
+ key text NOT NULL,
+ data text NOT NULL,
+ created_at timestamp with time zone DEFAULT now() NOT NULL
+);
+
+
--
-- Name: reservations; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.reservations (
id integer NOT NULL,
- unit text NOT NULL,
start_time timestamp with time zone NOT NULL,
end_time timestamp with time zone NOT NULL,
requester_id integer NOT NULL,
telegram text NOT NULL,
description text DEFAULT ''::text NOT NULL,
status public.reservation_status DEFAULT 'requested'::public.reservation_status NOT NULL,
+ unit_id integer NOT NULL,
CONSTRAINT reservations_telegram_handle CHECK ((telegram ~ '^@[A-Za-z][A-Za-z0-9_]{4,31}$'::text)),
CONSTRAINT reservations_time_order CHECK ((end_time > start_time))
);
@@ -146,13 +157,43 @@ CREATE TABLE public.schema_migrations (
);
+--
+-- Name: units; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.units (
+ id integer NOT NULL,
+ name text NOT NULL
+);
+
+
+--
+-- Name: units_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.units_id_seq
+ AS integer
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: units_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.units_id_seq OWNED BY public.units.id;
+
+
--
-- Name: units_users; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.units_users (
user_id integer NOT NULL,
- unit_name text NOT NULL
+ unit_id integer NOT NULL
);
@@ -205,6 +246,13 @@ ALTER TABLE ONLY public.bikes ALTER COLUMN id SET DEFAULT nextval('public.bikes_
ALTER TABLE ONLY public.reservations ALTER COLUMN id SET DEFAULT nextval('public.reservations_id_seq'::regclass);
+--
+-- Name: units id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.units ALTER COLUMN id SET DEFAULT nextval('public.units_id_seq'::regclass);
+
+
--
-- Name: users id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -220,6 +268,14 @@ ALTER TABLE ONLY public.bikes
ADD CONSTRAINT bikes_pkey PRIMARY KEY (id);
+--
+-- Name: oidc_states oidc_states_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.oidc_states
+ ADD CONSTRAINT oidc_states_pkey PRIMARY KEY (key);
+
+
--
-- Name: reservations_bikes reservations_bikes_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -252,12 +308,28 @@ ALTER TABLE ONLY public.schema_migrations
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
+--
+-- Name: units units_name_key; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.units
+ ADD CONSTRAINT units_name_key UNIQUE (name);
+
+
+--
+-- Name: units units_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.units
+ ADD CONSTRAINT units_pkey PRIMARY KEY (id);
+
+
--
-- Name: units_users units_users_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.units_users
- ADD CONSTRAINT units_users_pkey PRIMARY KEY (user_id, unit_name);
+ ADD CONSTRAINT units_users_pkey PRIMARY KEY (unit_id, user_id);
--
@@ -292,6 +364,13 @@ ALTER TABLE ONLY public.users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
+--
+-- Name: oidc_states_created_at_idx; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX oidc_states_created_at_idx ON public.oidc_states USING btree (created_at);
+
+
--
-- Name: reservations_bikes_bike_id_idx; Type: INDEX; Schema: public; Owner: -
--
@@ -314,17 +393,17 @@ CREATE INDEX reservations_status_idx ON public.reservations USING btree (status)
--
--- Name: reservations_unit_idx; Type: INDEX; Schema: public; Owner: -
+-- Name: reservations_unit_id_idx; Type: INDEX; Schema: public; Owner: -
--
-CREATE INDEX reservations_unit_idx ON public.reservations USING btree (unit);
+CREATE INDEX reservations_unit_id_idx ON public.reservations USING btree (unit_id);
--
--- Name: units_users_unit_name_idx; Type: INDEX; Schema: public; Owner: -
+-- Name: units_users_user_id_idx; Type: INDEX; Schema: public; Owner: -
--
-CREATE INDEX units_users_unit_name_idx ON public.units_users USING btree (unit_name);
+CREATE INDEX units_users_user_id_idx ON public.units_users USING btree (user_id);
--
@@ -351,6 +430,14 @@ ALTER TABLE ONLY public.reservations
ADD CONSTRAINT reservations_requester_id_fkey FOREIGN KEY (requester_id) REFERENCES public.users(id);
+--
+-- Name: reservations reservations_unit_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.reservations
+ ADD CONSTRAINT reservations_unit_id_fkey FOREIGN KEY (unit_id) REFERENCES public.units(id);
+
+
--
-- Name: reservations_users reservations_users_reservation_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -367,6 +454,14 @@ ALTER TABLE ONLY public.reservations_users
ADD CONSTRAINT reservations_users_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
+--
+-- Name: units_users units_users_unit_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.units_users
+ ADD CONSTRAINT units_users_unit_id_fkey FOREIGN KEY (unit_id) REFERENCES public.units(id) ON DELETE CASCADE;
+
+
--
-- Name: units_users units_users_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
@@ -389,4 +484,6 @@ ALTER TABLE ONLY public.units_users
INSERT INTO public.schema_migrations (version) VALUES
('20260823153300'),
('20260823153310'),
- ('20260823153320');
+ ('20260823153320'),
+ ('20260823170000'),
+ ('20260823210000');
diff --git a/db/seed.sql b/db/seed.sql
index 22b4286..f348361 100644
--- a/db/seed.sql
+++ b/db/seed.sql
@@ -8,26 +8,47 @@ INSERT INTO public.users (id, external_id, firstname, "name", email, oidc_sub, a
(3, NULL, 'Chloé', 'Favre', 'chloe.favre@epfl.ch', 'oidc-sub-chloe', FALSE)
ON CONFLICT DO NOTHING;
--- Unit names come from Whiskey; these are placeholders for development
-INSERT INTO public.units_users (user_id, unit_name) VALUES
+-- Units are Whiskey's "groups". In real life the rows are created on the fly at
+-- login; these are placeholders for development.
+INSERT INTO public.units ("name") VALUES
+ ('agepoly'),
+ ('clic'),
+ ('S4S'),
+ ('Balélec')
+ON CONFLICT ("name") DO NOTHING;
+
+-- Resolved by name so the seed never depends on generated ids
+INSERT INTO public.units_users (user_id, unit_id)
+SELECT membership.user_id, u.id
+FROM (VALUES
(1, 'agepoly'),
(2, 'agepoly'),
(2, 'clic'),
+ (1, 'S4S'),
+ (1, 'Balélec'),
(3, 'clic')
+) AS membership (user_id, unit_name)
+JOIN public.units u ON u."name" = membership.unit_name
ON CONFLICT DO NOTHING;
INSERT INTO public.bikes (id, "name", key_number, key_quantity, drivetrain, battery, status) VALUES
- (1, 'Cargo 1', 'K-1042', 2, 'Shimano Nexus 8', 'BAT-A12', 'in_service'),
- (2, 'Cargo 2', 'K-1043', 2, 'Shimano Nexus 8', 'BAT-A13', 'in_service'),
- (3, 'Cargo 3', 'K-1044', 1, 'Rohloff Speedhub', NULL, 'out_of_service')
+ (1, '1000', 'K-1042', 2, 'Shimano Nexus 8', 'BAT-A12', 'in_service'),
+ (2, '2000', 'K-1043', 2, 'Shimano Nexus 8', 'BAT-A13', 'in_service'),
+ (3, '3000', 'K-1044', 1, 'Rohloff Speedhub', NULL, 'out_of_service'),
+ (4, '4000', 'K-1042', 2, 'Shimano Nexus 8', 'BAT-A12', 'in_service'),
+ (5, '5000', 'K-1043', 2, 'Shimano Nexus 8', 'BAT-A13', 'in_service')
ON CONFLICT DO NOTHING;
INSERT INTO public.reservations
- (id, unit, start_time, end_time, requester_id, telegram, "description", status) VALUES
- (1, 'agepoly', '2026-09-01 08:00:00+02', '2026-09-01 18:00:00+02', 1, '@alice_martin',
- 'Transport du matériel pour la rentrée', 'approved'),
- (2, 'clic', '2026-09-05 09:00:00+02', '2026-09-06 17:00:00+02', 3, '@chloe_favre',
- 'Déménagement du stock de la commission', 'requested')
+ (id, unit_id, start_time, end_time, requester_id, telegram, "description", status)
+SELECT r.id, u.id, r.start_time, r.end_time, r.requester_id, r.telegram, r."description", r.status
+FROM (VALUES
+ (1, 'agepoly', '2026-09-01 08:00:00+02'::timestamptz, '2026-09-01 18:00:00+02'::timestamptz,
+ 1, '@alice_martin', 'Transport du matériel pour la rentrée', 'approved'::reservation_status),
+ (2, 'clic', '2026-09-05 09:00:00+02'::timestamptz, '2026-09-06 17:00:00+02'::timestamptz,
+ 3, '@chloe_favre', 'Déménagement du stock de la commission', 'requested'::reservation_status)
+) AS r (id, unit_name, start_time, end_time, requester_id, telegram, "description", status)
+JOIN public.units u ON u."name" = r.unit_name
ON CONFLICT DO NOTHING;
INSERT INTO public.reservations_users (reservation_id, user_id) VALUES
@@ -39,6 +60,7 @@ INSERT INTO public.reservations_bikes (reservation_id, bike_id) VALUES
ON CONFLICT DO NOTHING;
-- Keep the sequences in sync with the explicit ids inserted above
+SELECT setval('public.units_id_seq', (SELECT COALESCE(MAX(id), 1) FROM public.units));
SELECT setval('public.users_id_seq', (SELECT COALESCE(MAX(id), 1) FROM public.users));
SELECT setval('public.bikes_id_seq', (SELECT COALESCE(MAX(id), 1) FROM public.bikes));
SELECT setval('public.reservations_id_seq', (SELECT COALESCE(MAX(id), 1) FROM public.reservations));
diff --git a/frontend/index.html b/frontend/index.html
index 2b77d5c..d321743 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -9,7 +9,7 @@
href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.3.2/css/flag-icons.min.css"
/>
-
CarGAGEP
+ Cargobikes
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000..6bae555
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,6787 @@
+{
+ "name": "cargagep-frontend",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "cargagep-frontend",
+ "version": "0.0.0",
+ "dependencies": {
+ "@internationalized/date": "^3.12.3",
+ "@lucide/vue": "^1.33.0",
+ "@tailwindcss/vite": "^4.3.0",
+ "@tanstack/vue-query": "^5.100.10",
+ "@tanstack/vue-query-devtools": "^6.1.29",
+ "@vueuse/core": "^14.4.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "http-status-ts": "^2.0.1",
+ "openapi-fetch": "^0.17.0",
+ "postcss": "^8.5.14",
+ "reka-ui": "^2.10.3",
+ "tailwind-merge": "^3.6.0",
+ "tailwindcss": "^4.3.0",
+ "tw-animate-css": "^1.4.0",
+ "vue": "^3.5.18",
+ "vue-i18n": "^11.4.2",
+ "vue-router": "^5.0.7",
+ "vue-sonner": "^2.0.9"
+ },
+ "devDependencies": {
+ "@intlify/unplugin-vue-i18n": "^11.2.1",
+ "@tsconfig/node22": "^22.0.5",
+ "@types/node": "^25.8.0",
+ "@vitejs/plugin-vue": "^6.0.7",
+ "@vue/eslint-config-prettier": "^10.2.0",
+ "@vue/eslint-config-typescript": "^14.7.0",
+ "@vue/tsconfig": "^0.9.1",
+ "eslint": "^10.4.0",
+ "eslint-plugin-vue": "~10.9.1",
+ "jiti": "^2.7.0",
+ "npm-run-all2": "^8.0.4",
+ "openapi-typescript": "^7.13.0",
+ "prettier": "3.8.3",
+ "typescript": "~5.9.3",
+ "vite": "^8.0.13",
+ "vite-plugin-vue-devtools": "^8.1.2",
+ "vue-tsc": "^3.2.9"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz",
+ "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-create-class-features-plugin": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz",
+ "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.29.7",
+ "@babel/helper-member-expression-to-functions": "^7.29.7",
+ "@babel/helper-optimise-call-expression": "^7.29.7",
+ "@babel/helper-replace-supers": "^7.29.7",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-member-expression-to-functions": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz",
+ "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-optimise-call-expression": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz",
+ "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-replace-supers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz",
+ "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-member-expression-to-functions": "^7.29.7",
+ "@babel/helper-optimise-call-expression": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz",
+ "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
+ "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-decorators": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz",
+ "integrity": "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.29.7",
+ "@babel/helper-plugin-utils": "^7.29.7",
+ "@babel/plugin-syntax-decorators": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-decorators": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz",
+ "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz",
+ "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz",
+ "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz",
+ "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-typescript": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz",
+ "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.29.7",
+ "@babel/helper-create-class-features-plugin": "^7.29.7",
+ "@babel/helper-plugin-utils": "^7.29.7",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7",
+ "@babel/plugin-syntax-typescript": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
+ "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.8",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz",
+ "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.23.5",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz",
+ "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^3.0.5",
+ "debug": "^4.3.1",
+ "minimatch": "^10.2.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz",
+ "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^1.2.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz",
+ "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
+ "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz",
+ "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^1.2.1",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
+ "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.12"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz",
+ "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.8.0",
+ "@floating-ui/utils": "^0.2.12"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz",
+ "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
+ "license": "MIT"
+ },
+ "node_modules/@floating-ui/vue": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.11.tgz",
+ "integrity": "sha512-HzHKCNVxnGS35r9fCHBc3+uCnjw9IWIlCPL683cGgM9Kgj2BiAl8x1mS7vtvP6F9S/e/q4O6MApwSHj8hNLGfw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.6",
+ "@floating-ui/utils": "^0.2.11",
+ "vue-demi": ">=0.13.0"
+ }
+ },
+ "node_modules/@floating-ui/vue/node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/types": "^0.15.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/types": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@internationalized/date": {
+ "version": "3.12.3",
+ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.3.tgz",
+ "integrity": "sha512-fuLX+3ZKLsxI73y8b01EG/WjHb6gE6weCqlfawPO27kBWGMh9G1yH6Csv1uU7/cac9H2GHmOMt6CjmuQ1aia4Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@internationalized/number": {
+ "version": "3.6.7",
+ "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.7.tgz",
+ "integrity": "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@intlify/bundle-utils": {
+ "version": "11.2.5",
+ "resolved": "https://registry.npmjs.org/@intlify/bundle-utils/-/bundle-utils-11.2.5.tgz",
+ "integrity": "sha512-gl7CGygRALtuT4H8n/PNC0hubg4rnctPoT04//GZNsTemvJw0Q7i6jSaq0PZHxnH0cdGqyowNXmW4FYILbahzg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/message-compiler": "~11.4.2",
+ "@intlify/shared": "~11.4.2",
+ "acorn": "^8.8.2",
+ "esbuild": "^0.25.4",
+ "escodegen": "^2.1.0",
+ "estree-walker": "^2.0.2",
+ "jsonc-eslint-parser": "^2.3.0",
+ "source-map-js": "^1.2.1",
+ "yaml-eslint-parser": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 22.13"
+ },
+ "peerDependenciesMeta": {
+ "petite-vue-i18n": {
+ "optional": true
+ },
+ "vue-i18n": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@intlify/bundle-utils/node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/@intlify/core-base": {
+ "version": "11.4.9",
+ "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.9.tgz",
+ "integrity": "sha512-qbGZHXBUwodVCxm6E/IXY0yVLGHuh0GDxnoeCZ5FB75Wq5koKHLrANw5cFhC8fT3WyrkDyvesRzMoFOWsLpPOw==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/devtools-types": "11.4.9",
+ "@intlify/message-compiler": "11.4.9",
+ "@intlify/shared": "11.4.9"
+ },
+ "engines": {
+ "node": ">= 22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/devtools-types": {
+ "version": "11.4.9",
+ "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.9.tgz",
+ "integrity": "sha512-DFwTGShMQBK9ODzt+EKTRdoHdD65fTA/KbgxBq5gSEsbwYZKVFUKfHwnOYvZnZDL+h56swwXcP6jYRmQMOomvg==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/core-base": "11.4.9",
+ "@intlify/shared": "11.4.9"
+ },
+ "engines": {
+ "node": ">= 22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/message-compiler": {
+ "version": "11.4.9",
+ "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.9.tgz",
+ "integrity": "sha512-4vlAQsttSX1NgyDOY3sXAEq7HiQPvy21YgBeQUc7Ylu66MwLYVvtqOYSYudDR/SyOahttXvezuNs52s+G104xA==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/shared": "11.4.9",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/shared": {
+ "version": "11.4.9",
+ "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.9.tgz",
+ "integrity": "sha512-4qMBu3D64EN5KbkAj9Mv3TFjiPHtKG/YNd5LU5bpb0DZMR+bKh9/uGNx2GovzxWn2Wnyh5uvEhbPR718EVSkCw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/unplugin-vue-i18n": {
+ "version": "11.2.5",
+ "resolved": "https://registry.npmjs.org/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-11.2.5.tgz",
+ "integrity": "sha512-wLpS0cZTNAzaNL/STjjwSFhLunuoARKzEJh7jM/G00A40cVH+GEPhdo/WsvnesPvQVoN6JfjI/mGXwDNnTbvhg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@intlify/bundle-utils": "11.2.5",
+ "@intlify/shared": "~11.4.2",
+ "@intlify/vue-i18n-extensions": "^8.0.0",
+ "@rollup/pluginutils": "^5.1.0",
+ "@typescript-eslint/scope-manager": "^8.13.0",
+ "@typescript-eslint/typescript-estree": "^8.13.0",
+ "debug": "^4.3.3",
+ "fast-glob": "^3.2.12",
+ "pathe": "^2.0.3",
+ "picocolors": "^1.0.0",
+ "unplugin": "^2.3.4",
+ "vue": "~3.5.34"
+ },
+ "engines": {
+ "node": ">= 22.13"
+ },
+ "peerDependencies": {
+ "petite-vue-i18n": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "vue": "^3.2.25",
+ "vue-i18n": "*"
+ },
+ "peerDependenciesMeta": {
+ "petite-vue-i18n": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ },
+ "vue-i18n": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@intlify/vue-i18n-extensions": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@intlify/vue-i18n-extensions/-/vue-i18n-extensions-8.0.0.tgz",
+ "integrity": "sha512-w0+70CvTmuqbskWfzeYhn0IXxllr6mU+IeM2MU0M+j9OW64jkrvqY+pYFWrUnIIC9bEdij3NICruicwd5EgUuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.24.6",
+ "@intlify/shared": "^10.0.0",
+ "@vue/compiler-dom": "^3.2.45",
+ "vue-i18n": "^10.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "@intlify/shared": "^9.0.0 || ^10.0.0 || ^11.0.0",
+ "@vue/compiler-dom": "^3.0.0",
+ "vue": "^3.0.0",
+ "vue-i18n": "^9.0.0 || ^10.0.0 || ^11.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@intlify/shared": {
+ "optional": true
+ },
+ "@vue/compiler-dom": {
+ "optional": true
+ },
+ "vue": {
+ "optional": true
+ },
+ "vue-i18n": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@intlify/vue-i18n-extensions/node_modules/@intlify/core-base": {
+ "version": "10.0.8",
+ "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-10.0.8.tgz",
+ "integrity": "sha512-FoHslNWSoHjdUBLy35bpm9PV/0LVI/DSv9L6Km6J2ad8r/mm0VaGg06C40FqlE8u2ADcGUM60lyoU7Myo4WNZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/message-compiler": "10.0.8",
+ "@intlify/shared": "10.0.8"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/vue-i18n-extensions/node_modules/@intlify/message-compiler": {
+ "version": "10.0.8",
+ "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-10.0.8.tgz",
+ "integrity": "sha512-DV+sYXIkHVd5yVb2mL7br/NEUwzUoLBsMkV3H0InefWgmYa34NLZUvMCGi5oWX+Hqr2Y2qUxnVrnOWF4aBlgWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/shared": "10.0.8",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/vue-i18n-extensions/node_modules/@intlify/shared": {
+ "version": "10.0.8",
+ "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-10.0.8.tgz",
+ "integrity": "sha512-BcmHpb5bQyeVNrptC3UhzpBZB/YHHDoEREOUERrmF2BRxsyOEuRrq+Z96C/D4+2KJb8kuHiouzAei7BXlG0YYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/vue-i18n-extensions/node_modules/vue-i18n": {
+ "version": "10.0.8",
+ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-10.0.8.tgz",
+ "integrity": "sha512-mIjy4utxMz9lMMo6G9vYePv7gUFt4ztOMhY9/4czDJxZ26xPeJ49MAGa9wBAE3XuXbYCrtVPmPxNjej7JJJkZQ==",
+ "deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/core-base": "10.0.8",
+ "@intlify/shared": "10.0.8",
+ "@vue/devtools-api": "^6.5.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ },
+ "peerDependencies": {
+ "vue": "^3.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@lucide/vue": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/@lucide/vue/-/vue-1.33.0.tgz",
+ "integrity": "sha512-8d3HnIIglHnosvsnm+IWNy8XhYYJvkf4YEgQqb74HnY3ihzdz+7XX4MxMZoyKdw7pbKOGno2HoaHTq7DH/v5AA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "vue": ">=3.0.1"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.146.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz",
+ "integrity": "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@pkgr/core": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz",
+ "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pkgr"
+ }
+ },
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.29",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
+ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@redocly/ajv": {
+ "version": "8.11.2",
+ "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz",
+ "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js-replace": "^1.0.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@redocly/ajv/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@redocly/config": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz",
+ "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@redocly/openapi-core": {
+ "version": "1.34.19",
+ "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.19.tgz",
+ "integrity": "sha512-o/0VgsBXgwcY1lyeqcVtSGdTQAPnVggo0fbFVPlxl5XVDKUcVH0OLRqt3CbkwByT5FU305E0iE0O7MzThjDblw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@redocly/ajv": "8.11.2",
+ "@redocly/config": "0.22.0",
+ "colorette": "1.4.0",
+ "https-proxy-agent": "7.0.6",
+ "js-levenshtein": "1.1.6",
+ "js-yaml": "4.3.1",
+ "minimatch": "5.1.9",
+ "pluralize": "8.0.0",
+ "yaml-ast-parser": "0.0.43"
+ },
+ "engines": {
+ "node": ">=18.17.0",
+ "npm": ">=9.5.0"
+ }
+ },
+ "node_modules/@redocly/openapi-core/node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@redocly/openapi-core/node_modules/brace-expansion": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
+ "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@redocly/openapi-core/node_modules/minimatch": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+ "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm-eabi": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz",
+ "integrity": "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.5.tgz",
+ "integrity": "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.5.tgz",
+ "integrity": "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.5.tgz",
+ "integrity": "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.5.tgz",
+ "integrity": "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.5.tgz",
+ "integrity": "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.5.tgz",
+ "integrity": "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.5.tgz",
+ "integrity": "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.5.tgz",
+ "integrity": "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.5.tgz",
+ "integrity": "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.5.tgz",
+ "integrity": "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.5.tgz",
+ "integrity": "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.5.tgz",
+ "integrity": "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.5.tgz",
+ "integrity": "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.5.tgz",
+ "integrity": "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "license": "MIT"
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz",
+ "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.23",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz",
+ "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
+ "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.24.1",
+ "jiti": "^2.7.0",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.3.3"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
+ "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.3.3",
+ "@tailwindcss/oxide-darwin-arm64": "4.3.3",
+ "@tailwindcss/oxide-darwin-x64": "4.3.3",
+ "@tailwindcss/oxide-freebsd-x64": "4.3.3",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.3.3",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.3.3",
+ "@tailwindcss/oxide-linux-x64-musl": "4.3.3",
+ "@tailwindcss/oxide-wasm32-wasi": "4.3.3",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.3.3"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz",
+ "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz",
+ "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz",
+ "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz",
+ "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz",
+ "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz",
+ "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz",
+ "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz",
+ "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz",
+ "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz",
+ "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.11.1",
+ "@emnapi/runtime": "^1.11.1",
+ "@emnapi/wasi-threads": "^1.2.2",
+ "@napi-rs/wasm-runtime": "^1.1.4",
+ "@tybys/wasm-util": "^0.10.2",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
+ "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz",
+ "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz",
+ "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.3.3",
+ "@tailwindcss/oxide": "4.3.3",
+ "tailwindcss": "4.3.3"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/@tanstack/match-sorter-utils": {
+ "version": "8.19.4",
+ "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz",
+ "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==",
+ "license": "MIT",
+ "dependencies": {
+ "remove-accents": "0.5.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/query-core": {
+ "version": "5.102.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.102.1.tgz",
+ "integrity": "sha512-bJTsrO2ODWSgg+x1leTsK036GBtmNNjnrF1Vk06J5fg/phYZLQIdXrBe0uf5xTItXYvIW1MtbLhyPw7cPMqUUQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/query-devtools": {
+ "version": "5.102.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.102.1.tgz",
+ "integrity": "sha512-zX3fv4ZpkK+5Wzw0V2Axk1z6d1jMfMGgRdLVkF6BCJwZt8Iqveboy1G8rXV1o4cQ4ptKpMS45iOWPHape4QXoQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.17.8",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.8.tgz",
+ "integrity": "sha512-BfEvehNpOT75r5Ksc5xW6NZuXujTfb7nlSEyVu4XHG3gdxNg1KqXruWbDewXOUaUYIo4oRbSfkjIajz4MAT8tA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/vue-query": {
+ "version": "5.102.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/vue-query/-/vue-query-5.102.1.tgz",
+ "integrity": "sha512-873Pt1wYlQYPD2jgKEizxJ0/SqzGfKfaCxB2y2Aaco62vn+zHWW/whq+mFA+fjjYii20AFjtmTiZ6URGRPK4Jw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/match-sorter-utils": "^8.19.4",
+ "@tanstack/query-core": "5.102.1",
+ "@vue/devtools-api": "^6.6.3",
+ "vue-demi": "^0.14.10"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.1.2",
+ "vue": "^2.6.0 || ^3.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tanstack/vue-query-devtools": {
+ "version": "6.1.40",
+ "resolved": "https://registry.npmjs.org/@tanstack/vue-query-devtools/-/vue-query-devtools-6.1.40.tgz",
+ "integrity": "sha512-9cPYxm7cSu1MOecfjr6Dld2OAFtJLmaua0nMbIO1/Tl6nWaG/ns4n8NkjmWCE5T7GSYo8edBLc5lkxcGabKORA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-devtools": "5.102.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "@tanstack/vue-query": "^5.102.1",
+ "vue": "^3.3.0"
+ }
+ },
+ "node_modules/@tanstack/vue-query/node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tanstack/vue-virtual": {
+ "version": "3.13.36",
+ "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.36.tgz",
+ "integrity": "sha512-gKpExv4RbB9luVG+SucTXoqPZv/gzu/Yvz6BNO+8kpNxJ2x+I/ulryzl5W9BRciahZGp5Tls3Dp5XP1ztVGbMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.17.8"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "vue": "^2.7.0 || ^3.0.0"
+ }
+ },
+ "node_modules/@tsconfig/node22": {
+ "version": "22.0.6",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.6.tgz",
+ "integrity": "sha512-/5thavHAnWDZwH2H6eEn/T8pBEBhtuKaWGMslsj82kJfvzQgOgEMK7X4goBbIUjgVtAzPTtM9Ml64LA0uxO6Iw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/esrecurse": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
+ "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/jsesc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz",
+ "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "25.9.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz",
+ "integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": ">=7.24.0 <7.24.7"
+ }
+ },
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.21",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
+ "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz",
+ "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.67.0",
+ "@typescript-eslint/type-utils": "8.67.0",
+ "@typescript-eslint/utils": "8.67.0",
+ "@typescript-eslint/visitor-keys": "8.67.0",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.67.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz",
+ "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz",
+ "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.67.0",
+ "@typescript-eslint/types": "8.67.0",
+ "@typescript-eslint/typescript-estree": "8.67.0",
+ "@typescript-eslint/visitor-keys": "8.67.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz",
+ "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.67.0",
+ "@typescript-eslint/types": "^8.67.0",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz",
+ "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.67.0",
+ "@typescript-eslint/visitor-keys": "8.67.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz",
+ "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz",
+ "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.67.0",
+ "@typescript-eslint/typescript-estree": "8.67.0",
+ "@typescript-eslint/utils": "8.67.0",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz",
+ "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz",
+ "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.67.0",
+ "@typescript-eslint/tsconfig-utils": "8.67.0",
+ "@typescript-eslint/types": "8.67.0",
+ "@typescript-eslint/visitor-keys": "8.67.0",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.5.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz",
+ "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.67.0",
+ "@typescript-eslint/types": "8.67.0",
+ "@typescript-eslint/typescript-estree": "8.67.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz",
+ "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.67.0",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "6.0.8",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz",
+ "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@volar/language-core": {
+ "version": "2.4.28",
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz",
+ "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/source-map": "2.4.28"
+ }
+ },
+ "node_modules/@volar/source-map": {
+ "version": "2.4.28",
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz",
+ "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@volar/typescript": {
+ "version": "2.4.28",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz",
+ "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-core": "2.4.28",
+ "path-browserify": "^1.0.1",
+ "vscode-uri": "^3.0.8"
+ }
+ },
+ "node_modules/@vue-macros/common": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.4.tgz",
+ "integrity": "sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-sfc": "^3.5.22",
+ "ast-kit": "^2.1.2",
+ "local-pkg": "^1.1.2",
+ "magic-string-ast": "^1.0.2",
+ "unplugin-utils": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/vue-macros"
+ },
+ "peerDependencies": {
+ "vue": "^2.7.0 || ^3.2.25"
+ },
+ "peerDependenciesMeta": {
+ "vue": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vue/babel-helper-vue-transform-on": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz",
+ "integrity": "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vue/babel-plugin-jsx": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.5.0.tgz",
+ "integrity": "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/plugin-syntax-jsx": "^7.27.1",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.0",
+ "@babel/types": "^7.28.2",
+ "@vue/babel-helper-vue-transform-on": "1.5.0",
+ "@vue/babel-plugin-resolve-type": "1.5.0",
+ "@vue/shared": "^3.5.18"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vue/babel-plugin-resolve-type": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.5.0.tgz",
+ "integrity": "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/parser": "^7.28.0",
+ "@vue/compiler-sfc": "^3.5.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sxzz"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.41",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.41.tgz",
+ "integrity": "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@vue/shared": "3.5.41",
+ "entities": "^7.0.1",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.41",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.41.tgz",
+ "integrity": "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.41",
+ "@vue/shared": "3.5.41"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.41",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz",
+ "integrity": "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@vue/compiler-core": "3.5.41",
+ "@vue/compiler-dom": "3.5.41",
+ "@vue/compiler-ssr": "3.5.41",
+ "@vue/shared": "3.5.41",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.21",
+ "postcss": "^8.5.19",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.41",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz",
+ "integrity": "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.41",
+ "@vue/shared": "3.5.41"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
+ "license": "MIT"
+ },
+ "node_modules/@vue/devtools-core": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-8.2.1.tgz",
+ "integrity": "sha512-s/VfAY9oDTb/kFEWmy461jaFde2MIV1RO/gi1vwM+PAZBZ/Pc2Ndu3BNBdZUze8QDUuyYvElbEEGA83syjJfzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-kit": "^8.2.1",
+ "@vue/devtools-shared": "^8.2.1"
+ },
+ "peerDependencies": {
+ "vue": "^3.0.0"
+ }
+ },
+ "node_modules/@vue/devtools-kit": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz",
+ "integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-shared": "^8.2.1",
+ "birpc": "^2.6.1",
+ "hookable": "^5.5.3",
+ "perfect-debounce": "^2.0.0"
+ }
+ },
+ "node_modules/@vue/devtools-shared": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz",
+ "integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==",
+ "license": "MIT"
+ },
+ "node_modules/@vue/eslint-config-prettier": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.2.0.tgz",
+ "integrity": "sha512-GL3YBLwv/+b86yHcNNfPJxOTtVFJ4Mbc9UU3zR+KVoG7SwGTjPT+32fXamscNumElhcpXW3mT0DgzS9w32S7Bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-config-prettier": "^10.0.1",
+ "eslint-plugin-prettier": "^5.2.2"
+ },
+ "peerDependencies": {
+ "eslint": ">= 8.21.0",
+ "prettier": ">= 3.0.0"
+ }
+ },
+ "node_modules/@vue/eslint-config-typescript": {
+ "version": "14.9.0",
+ "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.9.0.tgz",
+ "integrity": "sha512-E3j9hDlfVf10F30MRcLTPY2IIhWIx1nsvkVukk14kTcuA+oBVot9zsP1hzsO+PAMDxV3Fd9FimBJtUBNBL5KFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/utils": "^8.60.0",
+ "fast-glob": "^3.3.3",
+ "typescript-eslint": "^8.60.0",
+ "vue-eslint-parser": "^10.4.0"
+ },
+ "bin": {
+ "vue-eslint-config-typescript": "dist/bin.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "peerDependencies": {
+ "eslint": "^9.10.0 || ^10.0.0",
+ "eslint-plugin-vue": "^9.28.0 || ^10.0.0",
+ "typescript": ">=4.8.4"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vue/language-core": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.11.tgz",
+ "integrity": "sha512-QJmpliwAVpC/OxubIByPAhNzsQPRc8/gxlN2qnVzVfIMjMDz/9RnXRFoetjz5yEgXVXyp4LqhXq3V53PjmNzFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-core": "2.4.28",
+ "@vue/compiler-dom": "^3.5.0",
+ "@vue/shared": "^3.5.0",
+ "alien-signals": "^3.2.1",
+ "muggle-string": "^0.4.1",
+ "path-browserify": "^1.0.1",
+ "picomatch": "^4.0.4"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.41",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.41.tgz",
+ "integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.41"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.41",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.41.tgz",
+ "integrity": "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.41",
+ "@vue/shared": "3.5.41"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.41",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.41.tgz",
+ "integrity": "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.41",
+ "@vue/runtime-core": "3.5.41",
+ "@vue/shared": "3.5.41",
+ "csstype": "^3.2.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.41",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.41.tgz",
+ "integrity": "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.41",
+ "@vue/runtime-dom": "3.5.41",
+ "@vue/shared": "3.5.41"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.41",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz",
+ "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==",
+ "license": "MIT"
+ },
+ "node_modules/@vue/tsconfig": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.9.1.tgz",
+ "integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": ">= 5.8",
+ "vue": "^3.4.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ },
+ "vue": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vueuse/core": {
+ "version": "14.4.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.4.0.tgz",
+ "integrity": "sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.21",
+ "@vueuse/metadata": "14.4.0",
+ "@vueuse/shared": "14.4.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "14.4.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.4.0.tgz",
+ "integrity": "sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "14.4.0",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.4.0.tgz",
+ "integrity": "sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
+ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/alien-signals": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz",
+ "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+ "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/ansis": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz",
+ "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ast-kit": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz",
+ "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "pathe": "^2.0.3"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ },
+ "node_modules/ast-walker-scope": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz",
+ "integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.2",
+ "@babel/types": "^7.29.0",
+ "ast-kit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.18",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.18.tgz",
+ "integrity": "sha512-1iEmLEYSiE1SeBoAfPo/Mnx3PzfzHUkDK61ASkCpuk3YXugYLH5DYK1SzqV55F8FMI6s0F+/tCP7Polz1QRjxw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/birpc": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz",
+ "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.8",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
+ "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.12",
+ "caniuse-lite": "^1.0.30001809",
+ "electron-to-chromium": "^1.5.402",
+ "node-releases": "^2.0.53",
+ "update-browserslist-db": "^1.3.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bundle-name": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
+ "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "run-applescript": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001809",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz",
+ "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/change-case": {
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
+ "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/chokidar": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
+ "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^5.0.0"
+ },
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/colorette": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/confbox": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
+ "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/default-browser": {
+ "version": "5.5.1",
+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.1.tgz",
+ "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bundle-name": "^4.1.0",
+ "default-browser-id": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser-id": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
+ "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/define-lazy-prop": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
+ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/defu": {
+ "version": "6.1.7",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz",
+ "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==",
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.412",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.412.tgz",
+ "integrity": "sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.24.5",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
+ "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/error-stack-parser-es": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz",
+ "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "10.9.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.9.0.tgz",
+ "integrity": "sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw==",
+ "dev": true,
+ "license": "MIT",
+ "workspaces": [
+ "packages/*"
+ ],
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@eslint/config-array": "^0.23.5",
+ "@eslint/config-helpers": "^0.7.0",
+ "@eslint/core": "^1.2.1",
+ "@eslint/plugin-kit": "^0.7.2",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^9.1.2",
+ "eslint-visitor-keys": "^5.0.1",
+ "espree": "^11.2.0",
+ "esquery": "^1.7.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "minimatch": "^10.2.5",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "10.1.8",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
+ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.5.6",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.6.tgz",
+ "integrity": "sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.1",
+ "synckit": "^0.11.13"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-plugin-prettier"
+ },
+ "peerDependencies": {
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-vue": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.9.2.tgz",
+ "integrity": "sha512-4g7ZP3pYcuqd7Zp0pzUKcos0W+RkjBz4EGdhJ92FcYk6v03Ti/GK5NwjgsjxHK+98eXDbHeK7VtX1az7/8doZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "natural-compare": "^1.4.0",
+ "nth-check": "^2.1.1",
+ "postcss-selector-parser": "^7.1.0",
+ "semver": "^7.6.3",
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "peerDependencies": {
+ "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
+ "@typescript-eslint/parser": "^7.0.0 || ^8.0.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "vue-eslint-parser": "^10.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@stylistic/eslint-plugin": {
+ "optional": true
+ },
+ "@typescript-eslint/parser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
+ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+ "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.16.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^5.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT"
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/exsolve": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz",
+ "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==",
+ "license": "MIT"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.4",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz",
+ "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/hookable": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
+ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
+ "license": "MIT"
+ },
+ "node_modules/http-status-ts": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-status-ts/-/http-status-ts-2.0.1.tgz",
+ "integrity": "sha512-oUiRl48Ov900YfDgXkaHjmC6V5KJX9Wp2DUL23MwONsLf6SKGxDVgg0Q3v0PBbNhtyzLzCPN/Dz9vFpsY6dSvw==",
+ "license": "MIT"
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/index-to-position": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz",
+ "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-docker": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
+ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-in-ssh": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
+ "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-inside-container": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
+ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^3.0.0"
+ },
+ "bin": {
+ "is-inside-container": "cli.js"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
+ "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-inside-container": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
+ "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-levenshtein": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
+ "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
+ "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz",
+ "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonc-eslint-parser": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.2.tgz",
+ "integrity": "sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.5.0",
+ "eslint-visitor-keys": "^3.0.0",
+ "espree": "^9.0.0",
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
+ }
+ },
+ "node_modules/jsonc-eslint-parser/node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/kolorist": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
+ "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/local-pkg": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz",
+ "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==",
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.7.4",
+ "pkg-types": "^2.3.0",
+ "quansync": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/magic-string-ast": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz",
+ "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==",
+ "license": "MIT",
+ "dependencies": {
+ "magic-string": "^0.30.19"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ },
+ "node_modules/memorystream": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
+ "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
+ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.8"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/mlly": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz",
+ "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.16.0",
+ "pathe": "^2.0.3",
+ "pkg-types": "^1.3.1",
+ "ufo": "^1.6.3"
+ }
+ },
+ "node_modules/mlly/node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "license": "MIT"
+ },
+ "node_modules/mlly/node_modules/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
+ "node_modules/mrmime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
+ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/muggle-string": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
+ "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.53",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz",
+ "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/nostics": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/nostics/-/nostics-1.2.0.tgz",
+ "integrity": "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==",
+ "license": "MIT"
+ },
+ "node_modules/npm-normalize-package-bin": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz",
+ "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
+ "node_modules/npm-run-all2": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-8.0.4.tgz",
+ "integrity": "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "cross-spawn": "^7.0.6",
+ "memorystream": "^0.3.1",
+ "picomatch": "^4.0.2",
+ "pidtree": "^0.6.0",
+ "read-package-json-fast": "^4.0.0",
+ "shell-quote": "^1.7.3",
+ "which": "^5.0.0"
+ },
+ "bin": {
+ "npm-run-all": "bin/npm-run-all/index.js",
+ "npm-run-all2": "bin/npm-run-all/index.js",
+ "run-p": "bin/run-p/index.js",
+ "run-s": "bin/run-s/index.js"
+ },
+ "engines": {
+ "node": "^20.5.0 || >=22.0.0",
+ "npm": ">= 10"
+ }
+ },
+ "node_modules/npm-run-all2/node_modules/isexe": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz",
+ "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/npm-run-all2/node_modules/which": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz",
+ "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^3.1.1"
+ },
+ "bin": {
+ "node-which": "bin/which.js"
+ },
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
+ "node_modules/obug": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
+ "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/ohash": {
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz",
+ "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==",
+ "license": "MIT"
+ },
+ "node_modules/open": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/open/-/open-11.0.1.tgz",
+ "integrity": "sha512-NzwMUB6C1D0+Kd+9iMS/H4k+Ck3cTX6Ckyfr/gAGlmvSE1LUQZnEZvWBi4PYmMwH/S5SMeTXnE+9uAz8uF+pWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "default-browser": "^5.4.0",
+ "define-lazy-prop": "^3.0.0",
+ "is-in-ssh": "^1.0.0",
+ "is-inside-container": "^1.0.0",
+ "powershell-utils": "^0.2.0",
+ "wsl-utils": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/openapi-fetch": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.17.0.tgz",
+ "integrity": "sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig==",
+ "license": "MIT",
+ "dependencies": {
+ "openapi-typescript-helpers": "^0.1.0"
+ }
+ },
+ "node_modules/openapi-typescript": {
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz",
+ "integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@redocly/openapi-core": "^1.34.6",
+ "ansi-colors": "^4.1.3",
+ "change-case": "^5.4.4",
+ "parse-json": "^8.3.0",
+ "supports-color": "^10.2.2",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "openapi-typescript": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "typescript": "^5.x"
+ }
+ },
+ "node_modules/openapi-typescript-helpers": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.1.0.tgz",
+ "integrity": "sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw==",
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
+ "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "index-to-position": "^1.1.0",
+ "type-fest": "^4.39.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-browserify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "license": "MIT"
+ },
+ "node_modules/perfect-debounce": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
+ "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pidtree": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.1.tgz",
+ "integrity": "sha512-e0F9AOF1JMrCfBsyJOwU9lNvQ0WtXTq0j/4jk0BQ5JSI9VAybPXmDpPRw/2FQ3e5d3ZFN1mLh7jW99m/jjaptw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "pidtree": "bin/pidtree.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz",
+ "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==",
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.4",
+ "exsolve": "^1.0.8",
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.17",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.5.tgz",
+ "integrity": "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/powershell-utils": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.2.0.tgz",
+ "integrity": "sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.8.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
+ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz",
+ "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/quansync": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
+ "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/antfu"
+ },
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/read-package-json-fast": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz",
+ "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "json-parse-even-better-errors": "^4.0.0",
+ "npm-normalize-package-bin": "^4.0.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || >=20.5.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz",
+ "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/reka-ui": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.10.3.tgz",
+ "integrity": "sha512-nJGZbwcha8AcP2wbnjodfzsciTKBqp1mIzepC9Pi2xDI/YK3++ej3vpJOSPyxqrkq1Oorj4K+BdJkbVCV6x6ag==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.6.13",
+ "@floating-ui/vue": "^1.1.6",
+ "@internationalized/date": "^3.5.0",
+ "@internationalized/number": "^3.5.0",
+ "@tanstack/vue-virtual": "^3.12.0",
+ "@vueuse/core": "^14.1.0",
+ "@vueuse/shared": "^14.1.0",
+ "aria-hidden": "^1.2.4",
+ "defu": "^6.1.5",
+ "ohash": "^2.0.11"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/zernonia"
+ },
+ "peerDependencies": {
+ "vue": ">= 3.4.0"
+ }
+ },
+ "node_modules/remove-accents": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz",
+ "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==",
+ "license": "MIT"
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.5.tgz",
+ "integrity": "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==",
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.146.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm-eabi": "1.2.5",
+ "@rolldown/binding-android-arm64": "1.2.5",
+ "@rolldown/binding-darwin-arm64": "1.2.5",
+ "@rolldown/binding-darwin-x64": "1.2.5",
+ "@rolldown/binding-freebsd-x64": "1.2.5",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.5",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.5",
+ "@rolldown/binding-linux-arm64-musl": "1.2.5",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.5",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.5",
+ "@rolldown/binding-linux-x64-gnu": "1.2.5",
+ "@rolldown/binding-linux-x64-musl": "1.2.5",
+ "@rolldown/binding-openharmony-arm64": "1.2.5",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.5",
+ "@rolldown/binding-win32-x64-msvc": "1.2.5"
+ }
+ },
+ "node_modules/run-applescript": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
+ "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/scule": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz",
+ "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shell-quote": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz",
+ "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/sirv": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz",
+ "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
+ "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/synckit": {
+ "version": "0.11.13",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz",
+ "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@pkgr/core": "^0.3.6"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/synckit"
+ }
+ },
+ "node_modules/tailwind-merge": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz",
+ "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
+ "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+ "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/tw-animate-css": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
+ "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Wombosvideo"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz",
+ "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.67.0",
+ "@typescript-eslint/parser": "8.67.0",
+ "@typescript-eslint/typescript-estree": "8.67.0",
+ "@typescript-eslint/utils": "8.67.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz",
+ "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==",
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "7.24.6",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
+ "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/unplugin": {
+ "version": "2.3.11",
+ "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
+ "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "acorn": "^8.15.0",
+ "picomatch": "^4.0.3",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": ">=18.12.0"
+ }
+ },
+ "node_modules/unplugin-utils": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz",
+ "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz",
+ "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/uri-js-replace": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz",
+ "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
+ "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.26",
+ "rolldown": "~1.2.4",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.4.0 || ^0.5.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-dev-rpc": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-2.0.0.tgz",
+ "integrity": "sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "birpc": "^4.0.0",
+ "vite-hot-client": "^2.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 || ^8.0.0"
+ }
+ },
+ "node_modules/vite-dev-rpc/node_modules/birpc": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/birpc/-/birpc-4.2.0.tgz",
+ "integrity": "sha512-KxgKcZPfrtzJDDALHPguGpGJUrzdgpymyiQQgzFjWreHMOpWrnFNVREr5J48x2DBh8ZVioscrV1SBkDipGiX+Q==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/vite-hot-client": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.2.0.tgz",
+ "integrity": "sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0"
+ }
+ },
+ "node_modules/vite-plugin-inspect": {
+ "version": "11.4.1",
+ "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.4.1.tgz",
+ "integrity": "sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansis": "^4.3.0",
+ "error-stack-parser-es": "^1.0.5",
+ "obug": "^2.1.1",
+ "ohash": "^2.0.11",
+ "open": "^11.0.0",
+ "perfect-debounce": "^2.1.0",
+ "sirv": "^3.0.2",
+ "unplugin-utils": "^0.3.1",
+ "vite-dev-rpc": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-vue-devtools": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-8.2.1.tgz",
+ "integrity": "sha512-5JLxXWWCo5lJMw16/xVeNvJ8k2zLwZPf1vITLzya/2IePrCBeGe/p/iAokgXHZpEi39fcYtPXmO8SaKeXmqCAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-core": "^8.2.1",
+ "@vue/devtools-kit": "^8.2.1",
+ "@vue/devtools-shared": "^8.2.1",
+ "sirv": "^3.0.2",
+ "vite-plugin-inspect": "^11.3.3",
+ "vite-plugin-vue-inspector": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=v14.21.3"
+ },
+ "peerDependencies": {
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/vite-plugin-vue-inspector": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-6.0.0.tgz",
+ "integrity": "sha512-OpyITJLgZNibxlrik1EmRtvXHDjLRxNPsWkGFTERZs2LgMEdG4W0WoFt5GIgp3a3jRou+eJR8U1zOBk/XQgEbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.23.0",
+ "@babel/plugin-proposal-decorators": "^7.23.0",
+ "@babel/plugin-syntax-import-attributes": "^7.22.5",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-transform-typescript": "^7.22.15",
+ "@vue/babel-plugin-jsx": "^1.1.5",
+ "@vue/compiler-dom": "^3.3.4",
+ "kolorist": "^1.8.0",
+ "magic-string": "^0.30.4"
+ },
+ "peerDependencies": {
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-darwin-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-darwin-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-freebsd-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/vscode-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
+ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vue": {
+ "version": "3.5.41",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.41.tgz",
+ "integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.41",
+ "@vue/compiler-sfc": "3.5.41",
+ "@vue/runtime-dom": "3.5.41",
+ "@vue/server-renderer": "3.5.41",
+ "@vue/shared": "3.5.41"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-eslint-parser": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.1.tgz",
+ "integrity": "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "eslint-scope": "^8.2.0 || ^9.0.0",
+ "eslint-visitor-keys": "^4.2.0 || ^5.0.0",
+ "espree": "^10.3.0 || ^11.0.0",
+ "esquery": "^1.6.0",
+ "semver": "^7.6.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0"
+ }
+ },
+ "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/vue-i18n": {
+ "version": "11.4.9",
+ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.9.tgz",
+ "integrity": "sha512-SJoEYcmt+g8IW1Ro9zuF6GyYEi/2t4kYlg0y7bUPKAyciR4Zhg6f+RTxgEZ9H2o9LrxJdcr50OH2OkGweRzRiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/core-base": "11.4.9",
+ "@intlify/devtools-types": "11.4.9",
+ "@intlify/shared": "11.4.9",
+ "@vue/devtools-api": "^6.5.0"
+ },
+ "engines": {
+ "node": ">= 22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ },
+ "peerDependencies": {
+ "vue": "^3.0.0"
+ }
+ },
+ "node_modules/vue-router": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.2.0.tgz",
+ "integrity": "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/generator": "^8.0.0",
+ "@vue-macros/common": "^3.1.3",
+ "@vue/devtools-api": "^8.1.5",
+ "ast-walker-scope": "^0.9.0",
+ "chokidar": "^5.0.0",
+ "json5": "^2.2.3",
+ "local-pkg": "^1.2.1",
+ "magic-string": "^0.30.21",
+ "mlly": "^1.8.2",
+ "muggle-string": "^0.4.1",
+ "nostics": "^1.1.4",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.5",
+ "scule": "^1.3.0",
+ "tinyglobby": "^0.2.17",
+ "unplugin": "^3.3.0",
+ "unplugin-utils": "^0.3.2",
+ "yaml": "^2.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "@pinia/colada": ">=0.21.2",
+ "@vue/compiler-sfc": "^3.5.34 || ^4.0.0",
+ "pinia": "^3.0.4 || ^4.0.2",
+ "vite": "^7.3.0 || ^8.0.0",
+ "vue": "^3.5.34 || ^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@pinia/colada": {
+ "optional": true
+ },
+ "@vue/compiler-sfc": {
+ "optional": true
+ },
+ "pinia": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-router/node_modules/@babel/generator": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz",
+ "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^8.0.0",
+ "@babel/types": "^8.0.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "@types/jsesc": "^2.5.0",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/vue-router/node_modules/@babel/helper-string-parser": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz",
+ "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==",
+ "license": "MIT",
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/vue-router/node_modules/@babel/helper-validator-identifier": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz",
+ "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==",
+ "license": "MIT",
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/vue-router/node_modules/@babel/parser": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz",
+ "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^8.0.4"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/vue-router/node_modules/@babel/types": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz",
+ "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^8.0.0",
+ "@babel/helper-validator-identifier": "^8.0.4"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/vue-router/node_modules/@vue/devtools-api": {
+ "version": "8.2.1",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.2.1.tgz",
+ "integrity": "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-kit": "^8.2.1"
+ }
+ },
+ "node_modules/vue-router/node_modules/unplugin": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz",
+ "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "picomatch": "^4.0.4",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@farmfe/core": "*",
+ "@rspack/core": "*",
+ "bun-types-no-globals": "*",
+ "esbuild": "*",
+ "rolldown": "*",
+ "rollup": "*",
+ "unloader": "*",
+ "vite": "*",
+ "webpack": "*"
+ },
+ "peerDependenciesMeta": {
+ "@farmfe/core": {
+ "optional": true
+ },
+ "@rspack/core": {
+ "optional": true
+ },
+ "bun-types-no-globals": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "rolldown": {
+ "optional": true
+ },
+ "rollup": {
+ "optional": true
+ },
+ "unloader": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-sonner": {
+ "version": "2.0.9",
+ "resolved": "https://registry.npmjs.org/vue-sonner/-/vue-sonner-2.0.9.tgz",
+ "integrity": "sha512-i6BokNlNDL93fpzNxN/LZSn6D6MzlO+i3qXt6iVZne3x1k7R46d5HlFB4P8tYydhgqOrRbIZEsnRd3kG7qGXyw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@nuxt/kit": "^4.0.3",
+ "@nuxt/schema": "^4.0.3",
+ "nuxt": "^4.0.3"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ },
+ "@nuxt/schema": {
+ "optional": true
+ },
+ "nuxt": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-tsc": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.11.tgz",
+ "integrity": "sha512-gOb0B9rtU2+f1dszwPqSH5kAieIF9ReeLhD3kSRNHv5WZZUQz/JdVXW0RTdqhNTMlQkqKzrTTviqKr/4FYZraQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/typescript": "2.4.28",
+ "@vue/language-core": "3.3.11"
+ },
+ "bin": {
+ "vue-tsc": "bin/vue-tsc.js"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ }
+ },
+ "node_modules/webpack-virtual-modules": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
+ "license": "MIT"
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wsl-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-1.0.0.tgz",
+ "integrity": "sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-wsl": "^3.1.0",
+ "powershell-utils": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/wsl-utils/node_modules/powershell-utils": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz",
+ "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yaml": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
+ "node_modules/yaml-ast-parser": {
+ "version": "0.0.43",
+ "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
+ "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/yaml-eslint-parser": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/yaml-eslint-parser/-/yaml-eslint-parser-1.3.2.tgz",
+ "integrity": "sha512-odxVsHAkZYYglR30aPYRY4nUGJnoJ2y1ww2HDvZALo0BDETv9kWbi16J52eHs+PWRNmF4ub6nZqfVOeesOvntg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.0.0",
+ "yaml": "^2.0.0"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ota-meshi"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
index 0e5c4d2..1ea0c1e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -17,17 +17,18 @@
"openapi": "openapi-typescript http://localhost:3000/api/docs/private/api.json -o ./src/lib/api.d.ts"
},
"dependencies": {
- "@lucide/vue": "^1.26.0",
+ "@internationalized/date": "^3.12.3",
+ "@lucide/vue": "^1.33.0",
"@tailwindcss/vite": "^4.3.0",
"@tanstack/vue-query": "^5.100.10",
"@tanstack/vue-query-devtools": "^6.1.29",
- "@vueuse/core": "^14.3.0",
+ "@vueuse/core": "^14.4.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"http-status-ts": "^2.0.1",
"openapi-fetch": "^0.17.0",
"postcss": "^8.5.14",
- "reka-ui": "^2.10.1",
+ "reka-ui": "^2.10.3",
"tailwind-merge": "^3.6.0",
"tailwindcss": "^4.3.0",
"tw-animate-css": "^1.4.0",
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 294eb49..9d4284c 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -1,16 +1,16 @@
-
-
-
-
+
diff --git a/frontend/src/components/AppContent.vue b/frontend/src/components/AppContent.vue
deleted file mode 100644
index d6ed1c4..0000000
--- a/frontend/src/components/AppContent.vue
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
diff --git a/frontend/src/components/AppHeader.vue b/frontend/src/components/AppHeader.vue
new file mode 100644
index 0000000..7c98690
--- /dev/null
+++ b/frontend/src/components/AppHeader.vue
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+
+ {{
+ $t('app.title')
+ }}
+
+
+
+
+
+
+
+
+ {{ $t(entry.label) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ flags[currentLocale as Locale] }}
+ {{ currentLocale }}
+
+
+
+
+ {{ flags[locale.lang] }}
+ {{ locale.lang }}
+
+
+
+
+
+
+
+
+
+ {{ user?.firstname }}
+
+
+
+
+ {{ user?.firstname }} {{ user?.name }}
+ {{ user?.email }}
+
+
+
+
+ {{ $t('header.logout') }}
+
+
+
+
+ {{ $t('header.login') }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('header.menu') }}
+
+
+
+ {{ $t(entry.label) }}
+
+
+
+ {{ user?.firstname }} {{ user?.name }}
+
+
+
+ {{ $t('header.logout') }}
+
+
+ {{ $t('header.login') }}
+
+
+
+
+
+
diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue
deleted file mode 100644
index a627344..0000000
--- a/frontend/src/components/AppSidebar.vue
+++ /dev/null
@@ -1,105 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- {{ $t('app.title') }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ menuGroup.title }}
-
-
-
-
-
-
- {{ menuEntry.title }}
-
-
-
-
-
-
-
-
-
diff --git a/frontend/src/components/DatePicker.vue b/frontend/src/components/DatePicker.vue
new file mode 100644
index 0000000..7e2680f
--- /dev/null
+++ b/frontend/src/components/DatePicker.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+ {{ model ? formatter.format(model.toDate('UTC')) : $t('reservation.pick-date') }}
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/TimePicker.vue b/frontend/src/components/TimePicker.vue
new file mode 100644
index 0000000..6aa9b47
--- /dev/null
+++ b/frontend/src/components/TimePicker.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ slot }}
+
+
+
diff --git a/frontend/src/components/ui/alert/Alert.vue b/frontend/src/components/ui/alert/Alert.vue
new file mode 100644
index 0000000..f23fe2f
--- /dev/null
+++ b/frontend/src/components/ui/alert/Alert.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/alert/AlertDescription.vue b/frontend/src/components/ui/alert/AlertDescription.vue
new file mode 100644
index 0000000..2971a87
--- /dev/null
+++ b/frontend/src/components/ui/alert/AlertDescription.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/alert/AlertTitle.vue b/frontend/src/components/ui/alert/AlertTitle.vue
new file mode 100644
index 0000000..e28dd37
--- /dev/null
+++ b/frontend/src/components/ui/alert/AlertTitle.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/alert/index.ts b/frontend/src/components/ui/alert/index.ts
new file mode 100644
index 0000000..4451f86
--- /dev/null
+++ b/frontend/src/components/ui/alert/index.ts
@@ -0,0 +1,24 @@
+import type { VariantProps } from 'class-variance-authority'
+import { cva } from 'class-variance-authority'
+
+export { default as Alert } from './Alert.vue'
+export { default as AlertDescription } from './AlertDescription.vue'
+export { default as AlertTitle } from './AlertTitle.vue'
+
+export const alertVariants = cva(
+ 'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
+ {
+ variants: {
+ variant: {
+ default: 'bg-background text-foreground',
+ destructive:
+ 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+)
+
+export type AlertVariants = VariantProps
diff --git a/frontend/src/components/ui/calendar/Calendar.vue b/frontend/src/components/ui/calendar/Calendar.vue
new file mode 100644
index 0000000..2dffae8
--- /dev/null
+++ b/frontend/src/components/ui/calendar/Calendar.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ day }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/calendar/CalendarCell.vue b/frontend/src/components/ui/calendar/CalendarCell.vue
new file mode 100644
index 0000000..4a4c77e
--- /dev/null
+++ b/frontend/src/components/ui/calendar/CalendarCell.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/calendar/CalendarCellTrigger.vue b/frontend/src/components/ui/calendar/CalendarCellTrigger.vue
new file mode 100644
index 0000000..2c594fd
--- /dev/null
+++ b/frontend/src/components/ui/calendar/CalendarCellTrigger.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/calendar/CalendarGrid.vue b/frontend/src/components/ui/calendar/CalendarGrid.vue
new file mode 100644
index 0000000..ae4dbb5
--- /dev/null
+++ b/frontend/src/components/ui/calendar/CalendarGrid.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/calendar/CalendarGridBody.vue b/frontend/src/components/ui/calendar/CalendarGridBody.vue
new file mode 100644
index 0000000..949f740
--- /dev/null
+++ b/frontend/src/components/ui/calendar/CalendarGridBody.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/calendar/CalendarGridHead.vue b/frontend/src/components/ui/calendar/CalendarGridHead.vue
new file mode 100644
index 0000000..58761fd
--- /dev/null
+++ b/frontend/src/components/ui/calendar/CalendarGridHead.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/calendar/CalendarGridRow.vue b/frontend/src/components/ui/calendar/CalendarGridRow.vue
new file mode 100644
index 0000000..20e1183
--- /dev/null
+++ b/frontend/src/components/ui/calendar/CalendarGridRow.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/calendar/CalendarHeadCell.vue b/frontend/src/components/ui/calendar/CalendarHeadCell.vue
new file mode 100644
index 0000000..51715b3
--- /dev/null
+++ b/frontend/src/components/ui/calendar/CalendarHeadCell.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/calendar/CalendarHeader.vue b/frontend/src/components/ui/calendar/CalendarHeader.vue
new file mode 100644
index 0000000..c115968
--- /dev/null
+++ b/frontend/src/components/ui/calendar/CalendarHeader.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/calendar/CalendarHeading.vue b/frontend/src/components/ui/calendar/CalendarHeading.vue
new file mode 100644
index 0000000..9a8715b
--- /dev/null
+++ b/frontend/src/components/ui/calendar/CalendarHeading.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
+ {{ headingValue }}
+
+
+
diff --git a/frontend/src/components/ui/calendar/CalendarNextButton.vue b/frontend/src/components/ui/calendar/CalendarNextButton.vue
new file mode 100644
index 0000000..7df47c8
--- /dev/null
+++ b/frontend/src/components/ui/calendar/CalendarNextButton.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/calendar/CalendarPrevButton.vue b/frontend/src/components/ui/calendar/CalendarPrevButton.vue
new file mode 100644
index 0000000..a5edc6a
--- /dev/null
+++ b/frontend/src/components/ui/calendar/CalendarPrevButton.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/calendar/index.ts b/frontend/src/components/ui/calendar/index.ts
new file mode 100644
index 0000000..5239a1b
--- /dev/null
+++ b/frontend/src/components/ui/calendar/index.ts
@@ -0,0 +1,12 @@
+export { default as Calendar } from './Calendar.vue'
+export { default as CalendarCell } from './CalendarCell.vue'
+export { default as CalendarCellTrigger } from './CalendarCellTrigger.vue'
+export { default as CalendarGrid } from './CalendarGrid.vue'
+export { default as CalendarGridBody } from './CalendarGridBody.vue'
+export { default as CalendarGridHead } from './CalendarGridHead.vue'
+export { default as CalendarGridRow } from './CalendarGridRow.vue'
+export { default as CalendarHeadCell } from './CalendarHeadCell.vue'
+export { default as CalendarHeader } from './CalendarHeader.vue'
+export { default as CalendarHeading } from './CalendarHeading.vue'
+export { default as CalendarNextButton } from './CalendarNextButton.vue'
+export { default as CalendarPrevButton } from './CalendarPrevButton.vue'
diff --git a/frontend/src/components/ui/label/Label.vue b/frontend/src/components/ui/label/Label.vue
new file mode 100644
index 0000000..c4e5b16
--- /dev/null
+++ b/frontend/src/components/ui/label/Label.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/label/index.ts b/frontend/src/components/ui/label/index.ts
new file mode 100644
index 0000000..572c2f0
--- /dev/null
+++ b/frontend/src/components/ui/label/index.ts
@@ -0,0 +1 @@
+export { default as Label } from './Label.vue'
diff --git a/frontend/src/components/ui/popover/Popover.vue b/frontend/src/components/ui/popover/Popover.vue
new file mode 100644
index 0000000..72e0b5f
--- /dev/null
+++ b/frontend/src/components/ui/popover/Popover.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/popover/PopoverContent.vue b/frontend/src/components/ui/popover/PopoverContent.vue
new file mode 100644
index 0000000..6afbf7e
--- /dev/null
+++ b/frontend/src/components/ui/popover/PopoverContent.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/popover/PopoverTrigger.vue b/frontend/src/components/ui/popover/PopoverTrigger.vue
new file mode 100644
index 0000000..0195a09
--- /dev/null
+++ b/frontend/src/components/ui/popover/PopoverTrigger.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/popover/index.ts b/frontend/src/components/ui/popover/index.ts
new file mode 100644
index 0000000..c621f9b
--- /dev/null
+++ b/frontend/src/components/ui/popover/index.ts
@@ -0,0 +1,3 @@
+export { default as Popover } from './Popover.vue'
+export { default as PopoverContent } from './PopoverContent.vue'
+export { default as PopoverTrigger } from './PopoverTrigger.vue'
diff --git a/frontend/src/components/ui/select/Select.vue b/frontend/src/components/ui/select/Select.vue
new file mode 100644
index 0000000..b9b1f33
--- /dev/null
+++ b/frontend/src/components/ui/select/Select.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/select/SelectContent.vue b/frontend/src/components/ui/select/SelectContent.vue
new file mode 100644
index 0000000..8037f77
--- /dev/null
+++ b/frontend/src/components/ui/select/SelectContent.vue
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/select/SelectGroup.vue b/frontend/src/components/ui/select/SelectGroup.vue
new file mode 100644
index 0000000..37b1c9a
--- /dev/null
+++ b/frontend/src/components/ui/select/SelectGroup.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/select/SelectItem.vue b/frontend/src/components/ui/select/SelectItem.vue
new file mode 100644
index 0000000..e320f3f
--- /dev/null
+++ b/frontend/src/components/ui/select/SelectItem.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/select/SelectItemText.vue b/frontend/src/components/ui/select/SelectItemText.vue
new file mode 100644
index 0000000..1cc7db1
--- /dev/null
+++ b/frontend/src/components/ui/select/SelectItemText.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/select/SelectLabel.vue b/frontend/src/components/ui/select/SelectLabel.vue
new file mode 100644
index 0000000..0d68aa6
--- /dev/null
+++ b/frontend/src/components/ui/select/SelectLabel.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/select/SelectScrollDownButton.vue b/frontend/src/components/ui/select/SelectScrollDownButton.vue
new file mode 100644
index 0000000..70708ca
--- /dev/null
+++ b/frontend/src/components/ui/select/SelectScrollDownButton.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/select/SelectScrollUpButton.vue b/frontend/src/components/ui/select/SelectScrollUpButton.vue
new file mode 100644
index 0000000..8871414
--- /dev/null
+++ b/frontend/src/components/ui/select/SelectScrollUpButton.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/select/SelectSeparator.vue b/frontend/src/components/ui/select/SelectSeparator.vue
new file mode 100644
index 0000000..1cfbada
--- /dev/null
+++ b/frontend/src/components/ui/select/SelectSeparator.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/select/SelectTrigger.vue b/frontend/src/components/ui/select/SelectTrigger.vue
new file mode 100644
index 0000000..8b57792
--- /dev/null
+++ b/frontend/src/components/ui/select/SelectTrigger.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/select/SelectValue.vue b/frontend/src/components/ui/select/SelectValue.vue
new file mode 100644
index 0000000..4ffa580
--- /dev/null
+++ b/frontend/src/components/ui/select/SelectValue.vue
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/select/index.ts b/frontend/src/components/ui/select/index.ts
new file mode 100644
index 0000000..31b9294
--- /dev/null
+++ b/frontend/src/components/ui/select/index.ts
@@ -0,0 +1,11 @@
+export { default as Select } from './Select.vue'
+export { default as SelectContent } from './SelectContent.vue'
+export { default as SelectGroup } from './SelectGroup.vue'
+export { default as SelectItem } from './SelectItem.vue'
+export { default as SelectItemText } from './SelectItemText.vue'
+export { default as SelectLabel } from './SelectLabel.vue'
+export { default as SelectScrollDownButton } from './SelectScrollDownButton.vue'
+export { default as SelectScrollUpButton } from './SelectScrollUpButton.vue'
+export { default as SelectSeparator } from './SelectSeparator.vue'
+export { default as SelectTrigger } from './SelectTrigger.vue'
+export { default as SelectValue } from './SelectValue.vue'
diff --git a/frontend/src/lib/api.d.ts b/frontend/src/lib/api.d.ts
index 805b4af..0fa44d0 100644
--- a/frontend/src/lib/api.d.ts
+++ b/frontend/src/lib/api.d.ts
@@ -4,50 +4,394 @@
*/
export interface paths {
- '/api/version': {
- parameters: {
- query?: never
- header?: never
- path?: never
- cookie?: never
- }
- /** Get app version */
- get: {
- parameters: {
- query?: never
- header?: never
- path?: never
- cookie?: never
- }
- requestBody?: never
- responses: {
- 200: {
- headers: {
- [name: string]: unknown
- }
- content: {
- 'application/json': string
- }
- }
- }
- }
- put?: never
- post?: never
- delete?: never
- options?: never
- head?: never
- patch?: never
- trace?: never
- }
+ "/api/version": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Get app version */
+ get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": string;
+ };
+ };
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/me": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /**
+ * Get the logged in user
+ * @description Answers `null` when nobody is logged in.
+ */
+ get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["User"] | null;
+ };
+ };
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/logout": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /** Log the user out */
+ post: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description no content */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ /** @description no content */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/whiskey/authorize": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Whiskey - Get the authorization url */
+ get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["GetAuthorizeResponse"];
+ };
+ };
+ /** @description no content */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/whiskey/callback": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /** Whiskey - Complete the login */
+ post: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["PostCallbackParams"];
+ };
+ };
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["User"];
+ };
+ };
+ /** @description no content */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ /** @description no content */
+ 403: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/login": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /**
+ * Log in as a dev user
+ * @description Debug builds only. Takes the email of one of the `dev_users` of the configuration, creates the row if needed, and opens a session.
+ */
+ post: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["LoginDevForm"];
+ };
+ };
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["User"];
+ };
+ };
+ /** @description no content */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/login/dev-users": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /**
+ * List the dev users
+ * @description Debug builds only.
+ */
+ get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["DevUser"][];
+ };
+ };
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/bikes": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Get the fleet */
+ get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Bike"][];
+ };
+ };
+ };
+ };
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
}
-export type webhooks = Record
+export type webhooks = Record;
export interface components {
- schemas: never
- responses: never
- parameters: never
- requestBodies: never
- headers: never
- pathItems: never
+ schemas: {
+ Bike: {
+ battery?: string | null;
+ drivetrain?: string | null;
+ /** Format: int32 */
+ id: number;
+ key_number?: string | null;
+ /** Format: int32 */
+ key_quantity: number;
+ name: string;
+ status: components["schemas"]["BikeStatus"];
+ };
+ /** @enum {string} */
+ BikeStatus: "in_service" | "out_of_service";
+ DevUser: {
+ admin: boolean;
+ email: string;
+ firstname: string;
+ name: string;
+ };
+ GetAuthorizeResponse: {
+ redirect_to: string;
+ };
+ LoginDevForm: {
+ /** @description Email of one of the `dev_users` of the configuration */
+ user: string;
+ };
+ PostCallbackParams: {
+ code: string;
+ state: string;
+ };
+ Unit: {
+ /** Format: int32 */
+ id: number;
+ /** @description The Whiskey group name */
+ name: string;
+ };
+ User: {
+ admin: boolean;
+ email: string;
+ external_id?: string | null;
+ firstname: string;
+ /** Format: int32 */
+ id: number;
+ name: string;
+ oidc_sub: string;
+ units: components["schemas"]["Unit"][];
+ };
+ };
+ responses: never;
+ parameters: never;
+ requestBodies: never;
+ headers: never;
+ pathItems: never;
}
-export type $defs = Record
-export type operations = Record
+export type $defs = Record;
+export type operations = Record;
diff --git a/frontend/src/locales/en.yml b/frontend/src/locales/en.yml
index 1b03e30..eb29e67 100644
--- a/frontend/src/locales/en.yml
+++ b/frontend/src/locales/en.yml
@@ -1,9 +1,59 @@
locale: en
app:
- title: CarGAGEP
-sidebar:
- navigation: Navigation
- home: Home
-home:
- welcome: Welcome!
- version: 'Backend version: {version}'
+ title: Cargobikes
+header:
+ logout: Log out
+ logout-error: Unable to log out.
+ reserve: Book
+ calendar: Calendar
+ login: Log in
+ menu: Menu
+ language: Language
+ theme-dark: Switch to dark mode
+ theme-light: Switch to light mode
+reservation:
+ title: Book a Cargobike
+ intro: >-
+ Fill in the information below to submit your reservation request.
+ For more information, see the wiki:
+ association: Association name
+ reason: Reason for the reservation
+ reason-placeholder: Why do you need the cargobike?
+ start: Start of the reservation
+ end: End of the reservation
+ pick-date: Pick a date
+ pick-time: Pick a time
+ bikes: Desired bike size
+ bikes-pick-period: >-
+ Please first pick a start and end date and time to see the available cargobikes.
+ bikes-empty: No cargobike available for this period.
+ bikes-error: Unable to load the cargobikes.
+ bike-out-of-service: Out of service
+ telegram: Telegram username
+ emails: Email addresses of the Linka Go accounts to authorize
+ email-nth: 'Email address {n}'
+ add-email: Add an email
+ remove-email: Remove this address
+ submit: Send the request
+ reset: Reset
+ error-required: This field is required.
+ error-datetime-required: Pick a date and a time.
+ error-end-before-start: The end must be after the start.
+ error-no-bike: Select at least one cargobike.
+ # '@' starts a linked message in vue-i18n, so it has to be escaped
+ error-telegram: "Invalid Telegram username (example: {'@'}my_username)."
+ error-email: One of the email addresses is invalid.
+ error-form: The form contains errors.
+ not-implemented: Submitting is not wired to the backend yet.
+login:
+ title: Log in
+ intro: Log in with your AGEPoly account to book a cargobike.
+ with-whiskey: Log in with AGEPoly
+ already: You are already logged in.
+ finishing: Finishing the login…
+ dev: Development login
+ not-allowed: Your account is not allowed to use this application.
+ error: The login failed.
+calendar:
+ title: Calendar
+ todo: This page is not built yet.
diff --git a/frontend/src/locales/fr.yml b/frontend/src/locales/fr.yml
index 1a57ace..21ffda2 100644
--- a/frontend/src/locales/fr.yml
+++ b/frontend/src/locales/fr.yml
@@ -1,9 +1,60 @@
locale: fr
app:
- title: CarGAGEP
-sidebar:
- navigation: Navigation
- home: Accueil
-home:
- welcome: Bienvenue !
- version: 'Version du backend : {version}'
+ title: Cargobikes
+header:
+ logout: Se déconnecter
+ logout-error: Impossible de se déconnecter.
+ reserve: Réserver
+ calendar: Calendrier
+ login: Se connecter
+ menu: Menu
+ language: Langue
+ theme-dark: Passer en mode sombre
+ theme-light: Passer en mode clair
+reservation:
+ title: Réserver un Cargobike
+ intro: >-
+ Remplissez les informations ci-dessous pour soumettre votre demande de réservation.
+ Pour plus d'informations, consultez le wiki :
+ association: Nom de l'association
+ reason: Raison de la réservation
+ reason-placeholder: Pourquoi avez-vous besoin du cargobike ?
+ start: Début de la réservation
+ end: Fin de la réservation
+ pick-date: Choisir une date
+ pick-time: Choisir une heure
+ bikes: Taille de vélo souhaitée
+ bikes-pick-period: >-
+ Veuillez d'abord choisir une date et une heure de début et de fin pour voir les
+ cargobikes disponibles.
+ bikes-empty: Aucun cargobike disponible pour ce créneau.
+ bikes-error: Impossible de charger les cargobikes.
+ bike-out-of-service: Hors service
+ telegram: Username Telegram
+ emails: Adresses mail du/des comptes Linka Go à autoriser
+ email-nth: 'Adresse e-mail {n}'
+ add-email: Ajouter un e-mail
+ remove-email: Retirer cette adresse
+ submit: Envoyer la demande
+ reset: Réinitialiser
+ error-required: Ce champ est obligatoire.
+ error-datetime-required: Choisissez une date et une heure.
+ error-end-before-start: La fin doit être après le début.
+ error-no-bike: Sélectionnez au moins un cargobike.
+ # '@' starts a linked message in vue-i18n, so it has to be escaped
+ error-telegram: "Username Telegram invalide (exemple : {'@'}mon_username)."
+ error-email: Une des adresses e-mail est invalide.
+ error-form: Le formulaire contient des erreurs.
+ not-implemented: L'envoi n'est pas encore branché sur le backend.
+login:
+ title: Connexion
+ intro: Connectez-vous avec votre compte AGEPoly pour réserver un cargobike.
+ with-whiskey: Se connecter avec AGEPoly
+ already: Vous êtes déjà connecté.
+ finishing: Connexion en cours…
+ dev: Connexion de développement
+ not-allowed: Votre compte n'est pas autorisé à accéder à cette application.
+ error: La connexion a échoué.
+calendar:
+ title: Calendrier
+ todo: Cette page n'est pas encore construite.
diff --git a/frontend/src/main.ts b/frontend/src/main.ts
index ac24916..43cef54 100644
--- a/frontend/src/main.ts
+++ b/frontend/src/main.ts
@@ -1,13 +1,18 @@
import { createApp } from 'vue'
-import { VueQueryPlugin } from '@tanstack/vue-query'
+import { QueryClient, VueQueryPlugin } from '@tanstack/vue-query'
import App from './App.vue'
import router from './router'
import { i18nInstance } from './services/i18n'
+import { setQueryClient } from './services/api/client'
+
+const queryClient = new QueryClient()
+// The api client needs it to clear the session cache on a 401
+setQueryClient(queryClient)
const app = createApp(App)
app.use(i18nInstance)
app.use(router)
-app.use(VueQueryPlugin)
+app.use(VueQueryPlugin, { queryClient })
app.mount('#app')
diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts
index 0f6292d..85ab506 100644
--- a/frontend/src/router/index.ts
+++ b/frontend/src/router/index.ts
@@ -1,10 +1,19 @@
import { createRouter, createWebHistory } from 'vue-router'
-import HomeView from '@/views/HomeView.vue'
+import LoginView from '@/views/LoginView.vue'
+import ReservationView from '@/views/ReservationView.vue'
+import CalendarView from '@/views/CalendarView.vue'
+import WhiskeyCallbackView from '@/views/WhiskeyCallbackView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
- routes: [{ name: 'home', path: '/', component: HomeView }],
+ routes: [
+ { name: 'login', path: '/', component: LoginView },
+ { name: 'reservations', path: '/reservations', component: ReservationView },
+ { name: 'calendar', path: '/calendar', component: CalendarView },
+ // Registered as the OIDC redirect uri, see `server.base_url` in config.yml
+ { name: 'whiskey-callback', path: '/whiskey/callback', component: WhiskeyCallbackView },
+ ],
})
export default router
diff --git a/frontend/src/services/api/auth.ts b/frontend/src/services/api/auth.ts
new file mode 100644
index 0000000..15a3711
--- /dev/null
+++ b/frontend/src/services/api/auth.ts
@@ -0,0 +1,106 @@
+/**
+ * Session.
+ *
+ * `useSession` is the single source of truth for "who is logged in". Every
+ * mutation writes its answer straight into that cache, and `client.ts` clears
+ * it on a 401, so the header and the views react without an extra round trip.
+ */
+import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
+import { HttpStatus } from 'http-status-ts'
+import { computed } from 'vue'
+
+import type { User } from '@/utils/types'
+import { getClient } from './client'
+import { SESSION_KEY } from './keys'
+
+export { SESSION_KEY }
+
+/**
+ * `/api/me` is a probe, not a protected route: it answers 200 with `null` when
+ * nobody is logged in, so a page load never looks like an error.
+ */
+export function useSession() {
+ const query = useQuery({
+ queryKey: SESSION_KEY,
+ staleTime: Infinity,
+ retry: false,
+ queryFn: async () => {
+ const { data, response } = await getClient().GET('/api/me')
+ if (response.status !== HttpStatus.OK) {
+ throw new Error(`Unexpected status code received: ${response.status}`)
+ }
+ return data ?? null
+ },
+ })
+
+ return {
+ ...query,
+ user: computed(() => query.data.value ?? null),
+ isLoggedIn: computed(() => !!query.data.value),
+ }
+}
+
+/**
+ * Step 1 of the Whiskey login: ask the backend where to send the browser, then
+ * leave the app. The provider comes back on /whiskey/callback.
+ */
+export async function getWhiskeyAuthorizationUrl(): Promise {
+ const { data, response } = await getClient().GET('/api/whiskey/authorize')
+ if (response.status !== HttpStatus.OK || !data?.redirect_to) {
+ throw new Error(`authorize failed: ${response.status}`)
+ }
+ return data.redirect_to
+}
+
+/** Step 2: hand the code and the state back, which is what opens the session */
+export function useWhiskeyCallbackMutation() {
+ const queryClient = useQueryClient()
+ return useMutation({
+ retry: 0,
+ mutationFn: async (form: { code: string; state: string }) => {
+ const { data } = await getClient().POST('/api/whiskey/callback', { body: form })
+ return data ?? null
+ },
+ onSuccess: (user) => queryClient.setQueryData(SESSION_KEY, user),
+ })
+}
+
+export function useLogoutMutation() {
+ const queryClient = useQueryClient()
+ return useMutation({
+ retry: 0,
+ mutationFn: async () => {
+ await getClient().POST('/api/logout')
+ },
+ onSuccess: () => queryClient.setQueryData(SESSION_KEY, null),
+ })
+}
+
+// --- Development only -------------------------------------------------------
+// These routes exist only in a debug build of the backend, and the calls are
+// guarded by `import.meta.env.DEV` so they leave the production bundle.
+
+export function useDevUsers() {
+ return useQuery({
+ queryKey: ['dev-users'],
+ enabled: import.meta.env.DEV,
+ retry: false,
+ staleTime: Infinity,
+ queryFn: async () => {
+ const { data } = await getClient().GET('/api/login/dev-users')
+ return data ?? []
+ },
+ })
+}
+
+export function useLoginDevMutation() {
+ const queryClient = useQueryClient()
+ return useMutation({
+ retry: 0,
+ mutationFn: async (form: { user: string }) => {
+ const { data } = await getClient().POST('/api/login', { body: form })
+ return data ?? null
+ },
+ onSuccess: (user) => queryClient.setQueryData(SESSION_KEY, user),
+ })
+}
diff --git a/frontend/src/services/api/bikes.ts b/frontend/src/services/api/bikes.ts
new file mode 100644
index 0000000..fa2462f
--- /dev/null
+++ b/frontend/src/services/api/bikes.ts
@@ -0,0 +1,22 @@
+/**
+ * The fleet. One file per domain area, exposing vue-query hooks: views never
+ * call `fetch` themselves.
+ */
+import { useQuery } from '@tanstack/vue-query'
+import { HttpStatus } from 'http-status-ts'
+
+import { getClient } from './client'
+
+export function useBikes() {
+ return useQuery({
+ queryKey: ['bikes'],
+ staleTime: 60 * 1000,
+ queryFn: async () => {
+ const { data, response } = await getClient().GET('/api/bikes')
+ if (response.status === HttpStatus.OK) {
+ return data
+ }
+ throw new Error(`Unexpected status code received: ${response.status}`)
+ },
+ })
+}
diff --git a/frontend/src/services/api/client.ts b/frontend/src/services/api/client.ts
index 6ecf74e..15278e2 100644
--- a/frontend/src/services/api/client.ts
+++ b/frontend/src/services/api/client.ts
@@ -1,7 +1,18 @@
import createClient from 'openapi-fetch'
import { HttpStatus } from 'http-status-ts'
+import type { QueryClient } from '@tanstack/vue-query'
import type { paths } from '@/lib/api'
+import { Forbidden, Unauthorized } from '@/utils/types'
+import { SESSION_KEY } from './keys'
+
+// Registered by main.ts. The interceptor below needs it to drop the session
+// from the cache as soon as the backend says the cookie is gone.
+let queryClient: QueryClient | null = null
+
+export function setQueryClient(client: QueryClient) {
+ queryClient = client
+}
// Types come from src/lib/api.d.ts, generated from the backend: `npm run openapi`
const client = createClient({
@@ -17,6 +28,12 @@ client.use({
`Server error from ${response.url}: ${response.status} ${response.statusText}`,
)
throw new Error('Server error')
+ } else if (response.status === HttpStatus.UNAUTHORIZED) {
+ // The session is gone: every view reading it must see that at once
+ queryClient?.setQueryData(SESSION_KEY, null)
+ throw new Unauthorized()
+ } else if (response.status === HttpStatus.FORBIDDEN) {
+ throw new Forbidden()
} else if (response.status === HttpStatus.NOT_FOUND) {
throw new Error(`Not found: ${response.url}`)
}
diff --git a/frontend/src/services/api/keys.ts b/frontend/src/services/api/keys.ts
new file mode 100644
index 0000000..cfb90de
--- /dev/null
+++ b/frontend/src/services/api/keys.ts
@@ -0,0 +1,6 @@
+/**
+ * Query keys shared between the services and the response interceptor.
+ * Kept apart from `auth.ts` so that `client.ts` can use the session key without
+ * importing the service that imports it back.
+ */
+export const SESSION_KEY = ['session'] as const
diff --git a/frontend/src/services/theme.ts b/frontend/src/services/theme.ts
new file mode 100644
index 0000000..592f7fd
--- /dev/null
+++ b/frontend/src/services/theme.ts
@@ -0,0 +1,56 @@
+/**
+ * Dark mode. Tailwind is configured with `@custom-variant dark (&:is(.dark *))`,
+ * so the whole theme is driven by a single `dark` class on .
+ *
+ * Three states, like the rest of the web: an explicit choice is remembered in
+ * localStorage, and 'system' follows the OS preference live.
+ */
+import { ref, watchEffect } from 'vue'
+
+export const themes = ['light', 'dark', 'system'] as const
+export type Theme = (typeof themes)[number]
+
+const STORAGE_KEY = 'theme'
+
+function isTheme(value: unknown): value is Theme {
+ return themes.includes(value as Theme)
+}
+
+function fromLocalStorage(): Theme {
+ try {
+ const stored = window.localStorage.getItem(STORAGE_KEY)
+ return isTheme(stored) ? stored : 'system'
+ } catch {
+ // Private mode, or site data blocked
+ return 'system'
+ }
+}
+
+const prefersDark = window.matchMedia('(prefers-color-scheme: dark)')
+
+export const theme = ref(fromLocalStorage())
+/** What is actually painted, once 'system' is resolved */
+export const isDark = ref(false)
+
+function apply() {
+ isDark.value = theme.value === 'dark' || (theme.value === 'system' && prefersDark.matches)
+ document.documentElement.classList.toggle('dark', isDark.value)
+}
+
+// Re-applies on every change of `theme`, and follows the OS while on 'system'
+watchEffect(apply)
+prefersDark.addEventListener('change', apply)
+
+export function setTheme(newTheme: Theme) {
+ theme.value = newTheme
+ try {
+ window.localStorage.setItem(STORAGE_KEY, newTheme)
+ } catch {
+ // Nothing to do: the choice simply will not survive a reload
+ }
+}
+
+/** Toggles between light and dark, resolving 'system' to its opposite first */
+export function toggleTheme() {
+ setTheme(isDark.value ? 'light' : 'dark')
+}
diff --git a/frontend/src/styles/tailwind.css b/frontend/src/styles/tailwind.css
index 5997be4..3368459 100644
--- a/frontend/src/styles/tailwind.css
+++ b/frontend/src/styles/tailwind.css
@@ -50,7 +50,7 @@
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
- --primary: oklch(0.205 0 0);
+ --primary: oklch(0.546 0.245 262.881);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
@@ -62,7 +62,7 @@
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
- --ring: oklch(0.708 0 0);
+ --ring: oklch(0.546 0.245 262.881);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
@@ -85,8 +85,8 @@
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
- --primary: oklch(0.985 0 0);
- --primary-foreground: oklch(0.205 0 0);
+ --primary: oklch(0.623 0.214 259.815);
+ --primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
@@ -97,7 +97,7 @@
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
- --ring: oklch(0.439 0 0);
+ --ring: oklch(0.623 0.214 259.815);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
diff --git a/frontend/src/utils/types.ts b/frontend/src/utils/types.ts
index 023bda7..2275ce0 100644
--- a/frontend/src/utils/types.ts
+++ b/frontend/src/utils/types.ts
@@ -1,7 +1,25 @@
import type { components } from '@/lib/api'
-// Shorthands over the generated schemas, so views never import `@/lib/api` directly.
-// Re-export a type here for every schema the views use, e.g.
-// export type Bike = components['schemas']['Bike']
-// once `src/api/` exposes the routes and `npm run openapi` has been run again.
-export type Schemas = components['schemas']
+/** The backend refused the request for lack of a session (401) */
+export class Unauthorized extends Error {
+ constructor() {
+ super('Unauthorized')
+ this.name = 'Unauthorized'
+ Object.setPrototypeOf(this, Unauthorized.prototype)
+ }
+}
+
+/** Logged in, but not allowed to do this (403) */
+export class Forbidden extends Error {
+ constructor() {
+ super('Forbidden')
+ this.name = 'Forbidden'
+ Object.setPrototypeOf(this, Forbidden.prototype)
+ }
+}
+
+// Shorthands over the generated schemas, so views never import `@/lib/api` directly
+export type Bike = components['schemas']['Bike']
+export type BikeStatus = components['schemas']['BikeStatus']
+export type Unit = components['schemas']['Unit']
+export type User = components['schemas']['User']
diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/views/CalendarView.vue b/frontend/src/views/CalendarView.vue
new file mode 100644
index 0000000..8eb7d01
--- /dev/null
+++ b/frontend/src/views/CalendarView.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
{{ $t('calendar.title') }}
+
{{ $t('calendar.todo') }}
+
+
diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue
deleted file mode 100644
index 45720a0..0000000
--- a/frontend/src/views/HomeView.vue
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
{{ $t('home.welcome') }}
-
{{ $t('home.version', { version }) }}
-
-
diff --git a/frontend/src/views/LoginView.vue b/frontend/src/views/LoginView.vue
new file mode 100644
index 0000000..9c8b95e
--- /dev/null
+++ b/frontend/src/views/LoginView.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+ {{ $t('login.title') }}
+ {{ $t('login.intro') }}
+
+
+
+
+
+ {{ $t('login.already') }}
+
+
+
+ {{ $t('login.with-whiskey') }}
+
+
+
+
+
+ {{ $t('login.dev') }}
+
+
+ {{ user.firstname }} {{ user.name }}
+ admin
+
+
+
+
+
+
diff --git a/frontend/src/views/ReservationView.vue b/frontend/src/views/ReservationView.vue
new file mode 100644
index 0000000..0593f41
--- /dev/null
+++ b/frontend/src/views/ReservationView.vue
@@ -0,0 +1,329 @@
+
+
+
+
+
diff --git a/frontend/src/views/WhiskeyCallbackView.vue b/frontend/src/views/WhiskeyCallbackView.vue
new file mode 100644
index 0000000..27d4497
--- /dev/null
+++ b/frontend/src/views/WhiskeyCallbackView.vue
@@ -0,0 +1,49 @@
+
+
+
+
+
{{ $t('login.finishing') }}
+
+
+
diff --git a/src/api/auth.rs b/src/api/auth.rs
index 514fbc6..83bd23e 100644
--- a/src/api/auth.rs
+++ b/src/api/auth.rs
@@ -1,3 +1,10 @@
+//! Login, logout, and "who am I".
+//!
+//! The OIDC dance is driven by the browser: `authorize` hands back the provider
+//! url, the frontend goes there, the provider sends the browser back to
+//! `{base_url}/whiskey/callback` (a frontend route), and that page posts the
+//! code and the state here.
+
use aide::{
NoApi,
axum::{
@@ -10,20 +17,23 @@ use axum::{Json, debug_handler, http::StatusCode};
use axum_login::AuthSession;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use tracing::{debug, error, info};
+use tracing::{error, info};
use crate::{
api::helpers::unexpected_error,
core::{
- controller::{AnonAppController, ControllerError, authn::AuthnControllerError},
+ controller::{
+ AnonAppController, AppController, ControllerError, authn::AuthnControllerError,
+ },
models::user::User,
},
};
pub fn routes() -> ApiRouter {
- let mut r = ApiRouter::new()
+ #[allow(unused_mut)]
+ let mut router = ApiRouter::new()
+ .api_route("/api/me", get_with(me, me_docs))
.api_route("/api/logout", post_with(logout, logout_docs))
- // .api_route("/api/me", get_with(me, me_docs))
.api_route(
"/api/whiskey/authorize",
get_with(whiskey_authorize, whiskey_authorize_docs),
@@ -35,28 +45,40 @@ pub fn routes() -> ApiRouter {
#[cfg(debug_assertions)]
{
- r = r.api_route("/api/login", post_with(login_dev, login_dev_docs))
+ router = router
+ .api_route("/api/login", post_with(login_dev, login_dev_docs))
+ .api_route("/api/login/dev-users", get_with(dev_users, dev_users_docs));
}
- r
+ router
}
+/// The user behind the current session, or `null` when there is none.
+///
+/// Deliberately takes the session rather than an `AppController`: this is the
+/// probe the frontend runs on every page load, and "nobody is logged in" is a
+/// normal answer, not an error. Every other protected route takes an
+/// `AppController` and answers 401.
+#[debug_handler]
+async fn me(NoApi(auth_session): NoApi>) -> Json> {
+ Json(auth_session.user)
+}
+
+fn me_docs(op: TransformOperation) -> TransformOperation {
+ op.tag("Auth")
+ .summary("Get the logged in user")
+ .description("Answers `null` when nobody is logged in.")
+}
+
+/// Asking for an `AppController` *is* the "must be logged in" check: the
+/// extractor answers 401 on its own, there is nothing to test here.
#[debug_handler]
async fn logout(
+ _ac: AppController,
NoApi(mut auth_session): NoApi>,
) -> Result<(), StatusCode> {
- debug!("[HANDLER] logout");
-
- if auth_session.user.is_none() {
- debug!("[HANDLER] logout failed: no active session");
- return Err(StatusCode::UNAUTHORIZED);
- }
-
match auth_session.logout().await {
- Ok(_) => {
- debug!("[HANDLER] logout successful");
- Ok(())
- }
+ Ok(_) => Ok(()),
Err(err) => {
error!("[HANDLER] logout failed: {err:?}");
Err(StatusCode::INTERNAL_SERVER_ERROR)
@@ -65,10 +87,7 @@ async fn logout(
}
fn logout_docs(op: TransformOperation) -> TransformOperation {
- op.summary("logout the user")
- .tag("Auth")
- .response::<401, ()>()
- .security_requirement("session_cookie")
+ op.tag("Auth").summary("Log the user out").response::<401, ()>()
}
#[derive(Debug, JsonSchema, Serialize)]
@@ -81,18 +100,18 @@ async fn whiskey_authorize(
aac: AnonAppController,
) -> Result, (StatusCode, String)> {
match aac.whiskey_authorize().await {
+ Ok(redirect_to) => Ok(Json(GetAuthorizeResponse { redirect_to })),
Err(ControllerError::Authn(AuthnControllerError::OIDCProtocolError)) => {
info!("[HANDLER] whiskey_authorize: protocol error");
Err((StatusCode::BAD_REQUEST, "Protocol error".to_owned()))
}
Err(err) => unexpected_error("whiskey_authorize", err),
- Ok(redirect_to) => Ok(Json(GetAuthorizeResponse { redirect_to })),
}
}
fn whiskey_authorize_docs(op: TransformOperation) -> TransformOperation {
- op.summary("Whiskey - Get authorization URL")
- .tag("Auth")
+ op.tag("Auth")
+ .summary("Whiskey - Get the authorization url")
.response::<400, ()>()
}
@@ -110,20 +129,17 @@ async fn whiskey_callback(
) -> Result, (StatusCode, String)> {
match aac.whiskey_callback(code, state).await {
Ok(user) => {
- let login_res = auth_session.login(&user).await;
-
- if let Err(err) = login_res {
- error!("[HANDLER] whiskey_callback failed to login the user: {err:?}");
+ if let Err(err) = auth_session.login(&user).await {
+ error!("[HANDLER] whiskey_callback: failed to open the session: {err:?}");
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Unexpected error".to_owned(),
));
}
-
- Ok(Json(user.into()))
+ Ok(Json(user))
}
Err(ControllerError::Authn(AuthnControllerError::NotAuthorized)) => {
- info!("[HANDLER] whiskey_callback: user not authorized (missing group)");
+ info!("[HANDLER] whiskey_callback: user not authorized");
Err((
StatusCode::FORBIDDEN,
"You are not authorized to access this yet".to_owned(),
@@ -138,8 +154,8 @@ async fn whiskey_callback(
}
fn whiskey_callback_docs(op: TransformOperation) -> TransformOperation {
- op.summary("Whiskey - Callback endpoint")
- .tag("Auth")
+ op.tag("Auth")
+ .summary("Whiskey - Complete the login")
.response::<400, ()>()
.response::<403, ()>()
}
@@ -147,34 +163,77 @@ fn whiskey_callback_docs(op: TransformOperation) -> TransformOperation {
#[cfg(debug_assertions)]
#[derive(Debug, Deserialize, JsonSchema)]
struct LoginDevForm {
- pub user: String,
+ /// Email of one of the `dev_users` of the configuration
+ user: String,
}
#[cfg(debug_assertions)]
#[debug_handler]
async fn login_dev(
aac: AnonAppController,
- NoApi(mut auth): NoApi>,
+ NoApi(mut auth_session): NoApi>,
Json(form): Json,
-) -> Result<(), StatusCode> {
+) -> Result, (StatusCode, String)> {
match aac.get_dev_user(form.user).await {
Ok(user) => {
- if let Err(err) = auth.login(&user).await {
- error!("Login dev: failed to login the user: {err:?}");
- return Err(StatusCode::INTERNAL_SERVER_ERROR);
+ if let Err(err) = auth_session.login(&user).await {
+ error!("[HANDLER] login_dev: failed to open the session: {err:?}");
+ return Err((
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "Unexpected error".to_owned(),
+ ));
}
- Ok(())
- }
- Err(err) => {
- error!("Login dev: failed to get dev user: {err:?}");
- Err(StatusCode::INTERNAL_SERVER_ERROR)
+ Ok(Json(user))
}
+ Err(ControllerError::Authn(AuthnControllerError::DevUserNotFound)) => Err((
+ StatusCode::NOT_FOUND,
+ "No such dev user in the configuration".to_owned(),
+ )),
+ Err(err) => unexpected_error("login_dev", err),
}
}
#[cfg(debug_assertions)]
fn login_dev_docs(op: TransformOperation) -> TransformOperation {
op.tag("Auth")
- .summary("Login with a dev user")
- .description("This function expect only the name of the user. It created the user in db if required and logins the user with that user.")
+ .summary("Log in as a dev user")
+ .description(
+ "Debug builds only. Takes the email of one of the `dev_users` of the \
+ configuration, creates the row if needed, and opens a session.",
+ )
+ .response::<404, ()>()
+}
+
+#[cfg(debug_assertions)]
+#[derive(Debug, Serialize, JsonSchema)]
+struct DevUser {
+ email: String,
+ firstname: String,
+ name: String,
+ admin: bool,
+}
+
+/// Lets the login page offer the dev users instead of asking for an email
+#[cfg(debug_assertions)]
+#[debug_handler]
+async fn dev_users() -> Json> {
+ Json(
+ crate::utils::config::get()
+ .dev_users
+ .iter()
+ .map(|user| DevUser {
+ email: user.email.clone(),
+ firstname: user.firstname.clone(),
+ name: user.name.clone(),
+ admin: user.admin,
+ })
+ .collect(),
+ )
+}
+
+#[cfg(debug_assertions)]
+fn dev_users_docs(op: TransformOperation) -> TransformOperation {
+ op.tag("Auth")
+ .summary("List the dev users")
+ .description("Debug builds only.")
}
diff --git a/src/api/bikes.rs b/src/api/bikes.rs
new file mode 100644
index 0000000..f64da36
--- /dev/null
+++ b/src/api/bikes.rs
@@ -0,0 +1,29 @@
+//! Read-only view of the fleet, used by the reservation form to show which
+//! cargo bikes can be picked.
+
+use aide::{
+ axum::{ApiRouter, routing::get_with},
+ transform::TransformOperation,
+};
+use axum::{Json, http::StatusCode};
+
+use crate::{
+ api::helpers::unexpected_error,
+ core::{controller::AnonAppController, models::bike::Bike},
+};
+
+pub fn routes() -> ApiRouter {
+ ApiRouter::new().api_route("/", get_with(get_bikes, get_bikes_docs))
+}
+
+#[axum::debug_handler]
+async fn get_bikes(aac: AnonAppController) -> Result>, (StatusCode, String)> {
+ match aac.get_bikes().await {
+ Ok(bikes) => Ok(Json(bikes)),
+ Err(err) => unexpected_error("get_bikes", err),
+ }
+}
+
+fn get_bikes_docs(op: TransformOperation) -> TransformOperation {
+ op.tag("Bikes").summary("Get the fleet")
+}
diff --git a/src/api/docs.rs b/src/api/docs.rs
index c7b83fd..e85a317 100644
--- a/src/api/docs.rs
+++ b/src/api/docs.rs
@@ -15,6 +15,14 @@ use axum::{Extension, Json, response::IntoResponse, routing::get};
pub fn api_docs_metadata(api: TransformOpenApi) -> TransformOpenApi {
api.title(&format!("{} API documentation", env!("CRATE_NAME")))
.description(format!("Build version: {}", env!("GIT_HASH")).as_str())
+ .tag(Tag {
+ name: "Auth".to_owned(),
+ ..Default::default()
+ })
+ .tag(Tag {
+ name: "Bikes".to_owned(),
+ ..Default::default()
+ })
.tag(Tag {
name: "misc".to_owned(),
..Default::default()
diff --git a/src/api/helpers.rs b/src/api/helpers.rs
index 192784e..c09aa73 100644
--- a/src/api/helpers.rs
+++ b/src/api/helpers.rs
@@ -2,7 +2,42 @@ use aide::transform::TransformResponse;
use axum::http::StatusCode;
use tracing::error;
-use crate::core::controller::ControllerError;
+use crate::core::{
+ controller::{AdminAppController, AppController, ControllerError, ManagerAppController},
+ models::unit::UnitId,
+};
+
+/// Narrows a session down to "member of this unit", or 403.
+/// `manager(ac, unit)?` in a handler is the whole authorization check.
+pub fn manager(
+ ac: AppController,
+ unit: UnitId,
+) -> Result {
+ ac.try_into_manager(unit).map_err(|_| {
+ (
+ StatusCode::FORBIDDEN,
+ "You are not allowed to act for this unit".to_owned(),
+ )
+ })
+}
+
+pub fn manager_desc(op: TransformResponse<'_, T>) -> TransformResponse<'_, T> {
+ op.description("Forbidden - the user must be part of the unit")
+}
+
+/// Narrows a session down to "admin", or 403
+pub fn admin(ac: AppController) -> Result {
+ ac.try_into_admin().map_err(|_| {
+ (
+ StatusCode::FORBIDDEN,
+ "You are not allowed to perform this action".to_owned(),
+ )
+ })
+}
+
+pub fn admin_desc(op: TransformResponse<'_, T>) -> TransformResponse<'_, T> {
+ op.description("Forbidden - the user must be an admin")
+}
/// Last resort branch of a handler `match`: logs the error and answers 500
pub fn unexpected_error(fn_name: &str, err: ControllerError) -> Result {
@@ -14,7 +49,6 @@ pub fn unexpected_error(fn_name: &str, err: ControllerError) -> Result(desc("..."))`
-#[allow(dead_code)]
pub fn desc(
description: &str,
) -> impl FnOnce(TransformResponse<'_, T>) -> TransformResponse<'_, T> {
diff --git a/src/api/mod.rs b/src/api/mod.rs
index ca3ca31..2170003 100644
--- a/src/api/mod.rs
+++ b/src/api/mod.rs
@@ -1,31 +1,59 @@
-//! HTTP layer: routing and OpenAPI generation (aide).
+//! HTTP layer: routing, session handling and OpenAPI generation (aide).
//!
-//! Handlers stay thin: they extract the controller, call it, and map
-//! `ControllerError` to a status code. No business logic here.
+//! Authorization is carried by the extractor a handler asks for:
+//! - `AnonAppController` always succeeds;
+//! - `AppController` resolves the session and answers **401** without one, so
+//! a route cannot accidentally be left open.
+//!
+//! Handlers stay thin: extract the controller, call it, map `ControllerError`
+//! to a status code. No business logic here.
use std::sync::Arc;
use aide::{
OperationInput, OperationOutput,
axum::{ApiRouter, routing::get_with},
- openapi::OpenApi,
+ generate::GenContext,
+ openapi::{OpenApi, Operation, Response},
};
use axum::{
Extension, Json, Router,
extract::FromRequestParts,
http::{StatusCode, request::Parts},
};
+use axum_login::{AuthManagerLayerBuilder, AuthSession, tower_sessions::SessionManagerLayer};
+use indexmap::IndexMap;
+use tower_sessions::{Expiry, MemoryStore, cookie::SameSite, cookie::time::Duration};
use tracing::error;
-use crate::core::controller::AppController;
+use crate::{
+ core::controller::{AnonAppController, AppController},
+ utils,
+};
+mod auth;
+mod bikes;
mod docs;
mod helpers;
-pub fn get_router(controller: AppController) -> Router {
+pub fn get_router(aac: AnonAppController) -> Router {
aide::generate::on_error(|err| error!("aide generated error: {err}"));
aide::generate::extract_schemas(true);
+ let config = utils::config::get();
+
+ // Sessions live in memory: everybody is logged out when the backend
+ // restarts. Swap the store for a persistent one if that becomes a problem.
+ let session_layer = SessionManagerLayer::new(MemoryStore::default())
+ // Over plain http in development the cookie cannot be `Secure`
+ .with_secure(config.get_base_url().starts_with("https://"))
+ // The provider sends the browser back with a top level navigation
+ .with_same_site(SameSite::Lax)
+ .with_expiry(Expiry::OnInactivity(Duration::minutes(
+ config.get_session_lifetime(),
+ )));
+ let auth_layer = AuthManagerLayerBuilder::new(aac.clone(), session_layer).build();
+
let mut api = OpenApi::default();
ApiRouter::new()
.api_route(
@@ -35,14 +63,18 @@ pub fn get_router(controller: AppController) -> Router {
|op| op.tag("misc").summary("Get app version"),
),
)
+ // `auth` carries its own `/api/...` paths, so it is merged, not nested
+ .merge(auth::routes())
+ .nest_api_service("/api/bikes", bikes::routes())
.nest_api_service("/api/docs", docs::routes())
.finish_api_with(&mut api, docs::api_docs_metadata)
- .layer(Extension(controller))
+ .layer(Extension(aac))
.layer(Extension(Arc::new(api)))
+ .layer(auth_layer)
}
-/// Lets a handler take an `AppController` as an argument
-impl FromRequestParts for AppController
+/// Lets a handler take an `AnonAppController`: always available.
+impl FromRequestParts for AnonAppController
where
S: Send + Sync,
{
@@ -51,14 +83,65 @@ where
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result {
parts
.extensions
- .get::()
+ .get::()
.cloned()
.ok_or(StatusCode::INTERNAL_SERVER_ERROR)
}
}
-// The controller is not part of the request/response bodies: nothing to document
+/// Lets a handler take an `AppController`, which requires a session: asking for
+/// it *is* the authentication check.
+impl FromRequestParts for AppController
+where
+ S: Send + Sync,
+{
+ type Rejection = StatusCode;
+
+ async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result {
+ use axum::RequestPartsExt;
+
+ let aac = parts
+ .extensions
+ .get::()
+ .cloned()
+ .ok_or(StatusCode::INTERNAL_SERVER_ERROR)?;
+
+ let session = parts
+ .extract::>()
+ .await
+ .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
+
+ let user = session.user.ok_or(StatusCode::UNAUTHORIZED)?;
+ Ok(aac.auth(user))
+ }
+}
+
+// The controllers are not part of the request/response bodies, but asking for
+// an `AppController` documents the 401 and the cookie requirement.
+impl OperationOutput for AnonAppController {
+ type Inner = Self;
+}
+impl OperationInput for AnonAppController {}
+
impl OperationOutput for AppController {
type Inner = Self;
}
-impl OperationInput for AppController {}
+
+impl OperationInput for AppController {
+ fn inferred_early_responses(
+ _: &mut GenContext,
+ op: &mut Operation,
+ ) -> Vec<(Option, Response)> {
+ let mut session_cookie = IndexMap::new();
+ session_cookie.insert("session_cookie".to_owned(), Vec::new());
+ op.security = vec![session_cookie];
+ vec![(
+ Some(401),
+ Response {
+ description: "Unauthenticated - a session is required".to_owned(),
+ content: IndexMap::new(),
+ ..Default::default()
+ },
+ )]
+ }
+}
diff --git a/src/core/controller/authn.rs b/src/core/controller/authn.rs
index 32b89f5..ca30590 100644
--- a/src/core/controller/authn.rs
+++ b/src/core/controller/authn.rs
@@ -1,28 +1,42 @@
+//! Authentication.
+//!
+//! Two ways in, both ending on the same `User` row and the same session:
+//! - Whiskey (OIDC), the real one;
+//! - a user taken from `dev_users` in the configuration, debug builds only.
+//!
+//! `AppController` is also the `axum-login` backend: the session stores a user
+//! id, and `get_user` loads it back on every request.
+
use axum_login::{AuthUser, AuthnBackend};
use thiserror::Error;
+use tracing::debug;
use crate::{
core::{
controller::{AnonAppController, ControllerError},
- models::user::{User, UserNoId},
+ models::user::{NewUser, User, UserId},
},
- utils::whiskey::{WhiskeyError, authorize, callback},
+ utils::whiskey::{UserInfoData, WhiskeyError, authorize, callback},
};
impl AuthUser for User {
- type Id = i32;
+ type Id = UserId;
fn id(&self) -> Self::Id {
self.id
}
+ /// Invalidates the sessions of a user whose provider identity changed
fn session_auth_hash(&self) -> &[u8] {
- &self.oidc_sub.as_bytes()
+ self.oidc_sub.as_bytes()
}
}
impl AuthnBackend for AnonAppController {
type User = User;
+ /// Login always goes through `whiskey_callback` or `get_dev_user`, which
+ /// hand a `User` straight to `AuthSession::login`. There are no credentials
+ /// to verify here.
type Credentials = ();
type Error = ControllerError;
@@ -37,94 +51,139 @@ impl AuthnBackend for AnonAppController {
&self,
user_id: &axum_login::UserId,
) -> Result, Self::Error> {
- Ok(Some(self.db.get_user(user_id.clone()).await?))
+ match self.db.get_user(*user_id).await {
+ Ok(user) => Ok(Some(user)),
+ // The session outlived the user: treat it as logged out
+ Err(crate::core::repositories::RepositoryError::NotFound(_)) => Ok(None),
+ Err(err) => Err(err.into()),
+ }
}
}
-impl super::AnonAppController {
+impl AnonAppController {
+ /// Step 1 of the login: builds the provider url the browser must go to, and
+ /// remembers the pkce verifier and the nonce under the csrf token.
pub async fn whiskey_authorize(&self) -> Result {
+ // Cheap enough to piggyback on the login, and keeps the table small
+ if let Ok(count) = self.db.delete_expired_whiskey_data().await
+ && count > 0
+ {
+ debug!("swept {count} expired oidc states");
+ }
+
match authorize().await {
Ok((redirect_to, authorize_backend_data)) => {
self.db.save_whiskey_data(authorize_backend_data).await?;
Ok(redirect_to)
}
- Err(WhiskeyError::ProtocolError) => Err(ControllerError::Authn(
- AuthnControllerError::OIDCProtocolError,
- )),
+ Err(WhiskeyError::ProtocolError) => {
+ Err(AuthnControllerError::OIDCProtocolError.into())
+ }
Err(WhiskeyError::InternalError) => Err(ControllerError::InternalError(
- "Originated from Whiskey".to_string(),
+ "Originated from Whiskey".to_owned(),
)),
}
}
- async fn get_or_create_user_oidc(
- &self,
- sciper: String,
- firstname: String,
- name: String,
- sub: String,
- email: String,
- ) -> Result {
- self.db
- .upsert_user(UserNoId {
- external_id: Some(sciper),
- firstname,
- name,
- email,
- oidc_sub: sub,
- units: vec![], //TODO use real units
- admin: false,
- })
- .await
- .map_err(Into::into)
- }
-
+ /// Step 2: the browser comes back with a code, we exchange it and upsert
+ /// the user the provider describes.
pub async fn whiskey_callback(
&self,
code: String,
state: String,
) -> Result {
- let backend_data = self.db.get_whiskey_data(state.clone()).await?;
+ // Consumes the state: a callback can never be replayed
+ let backend_data = self.db.take_whiskey_data(state.clone()).await.map_err(|_| {
+ debug!("unknown, already used or expired oidc state");
+ AuthnControllerError::OIDCProtocolError
+ })?;
+
match callback(code, state, backend_data).await {
- Ok(user_info) => {
- self.get_or_create_user_oidc(
- user_info.sciper,
- user_info.firstname,
- user_info.name,
- user_info.sub,
- user_info.email,
- )
- .await
+ Ok(user_info) => self.upsert_oidc_user(user_info).await,
+ Err(WhiskeyError::ProtocolError) => {
+ Err(AuthnControllerError::OIDCProtocolError.into())
}
- Err(WhiskeyError::ProtocolError) => Err(ControllerError::Authn(
- AuthnControllerError::OIDCProtocolError,
- )),
Err(WhiskeyError::InternalError) => Err(ControllerError::InternalError(
- "originated from Whiskey".to_string(),
+ "originated from Whiskey".to_owned(),
)),
}
}
- #[cfg(debug_assertions)]
- pub async fn get_dev_user(&self, username: String) -> Result {
- use crate::core::repositories::RepositoryError;
+ async fn upsert_oidc_user(&self, info: UserInfoData) -> Result {
+ let user = self
+ .db
+ .upsert_user(NewUser {
+ external_id: Some(info.sciper),
+ firstname: info.firstname,
+ name: info.name,
+ email: info.email,
+ oidc_sub: info.sub,
+ })
+ .await?;
- let user = self.db.get_user_external_id(username.clone()).await;
- let user = if let Err(RepositoryError::NotFound(_)) = user {
- use crate::utils::config;
-
- let user = config::get()
- .get_dev_users()
- .into_iter()
- .find(|u| u.external_id.clone().is_some_and(|u| u == username))
- .ok_or(AuthnControllerError::DevUserNotFound)?;
-
- self.db.upsert_user(user).await?
- } else {
- user?
+ // Whiskey is authoritative on the units: a user removed from a group
+ // there must lose it here too. But only when it actually told us —
+ // `None` means the claim was absent, and wiping the units on that would
+ // silently strip everyone's access.
+ let Some(groups) = info.groups else {
+ debug!(
+ "no groups from Whiskey for user {}, units left untouched",
+ user.id
+ );
+ return Ok(user);
};
- Ok(user)
+ // Resolves the group names to units, creating the ones we have never
+ // seen: a brand new group in Whiskey must not break the login.
+ let units = self.db.upsert_units(&groups).await?;
+
+ if units == user.units {
+ return Ok(user);
+ }
+
+ debug!(
+ "units of user {}: {:?} -> {:?}",
+ user.id,
+ user.units.iter().map(|u| &u.name).collect::>(),
+ units.iter().map(|u| &u.name).collect::>()
+ );
+ let ids = units.iter().map(|u| u.id).collect();
+ self.db.set_user_units(user.id, ids).await?;
+ self.db.get_user(user.id).await.map_err(Into::into)
+ }
+
+ /// Logs in one of the `dev_users` of the configuration, picked by email.
+ /// The row is created on first use, then kept in sync with the config.
+ #[cfg(debug_assertions)]
+ pub async fn get_dev_user(&self, email: String) -> Result {
+ use crate::utils::config;
+
+ let dev_user = config::get()
+ .dev_users
+ .iter()
+ .find(|user| user.email == email)
+ .ok_or(AuthnControllerError::DevUserNotFound)?
+ .clone();
+
+ // Namespaced so a dev user can never collide with a real Whiskey subject
+ let user = self
+ .db
+ .upsert_user(NewUser {
+ external_id: dev_user.external_id.clone(),
+ firstname: dev_user.firstname.clone(),
+ name: dev_user.name.clone(),
+ email: dev_user.email.clone(),
+ oidc_sub: format!("dev:{}", dev_user.email),
+ })
+ .await?;
+
+ // Unlike the Whiskey path, the configuration is authoritative here
+ let units = self.db.upsert_units(&dev_user.units).await?;
+ let ids = units.iter().map(|u| u.id).collect();
+ self.db.set_user_units(user.id, ids).await?;
+ self.db.set_user_admin(user.id, dev_user.admin).await?;
+
+ self.db.get_user(user.id).await.map_err(Into::into)
}
}
@@ -136,6 +195,6 @@ pub enum AuthnControllerError {
NotAuthenticated,
#[error("Not authorized")]
NotAuthorized,
- #[error("Dev user does not exists")]
+ #[error("Dev user does not exist")]
DevUserNotFound,
}
diff --git a/src/core/controller/bikes.rs b/src/core/controller/bikes.rs
index 2cbee57..3eb046c 100644
--- a/src/core/controller/bikes.rs
+++ b/src/core/controller/bikes.rs
@@ -1,11 +1,13 @@
use thiserror::Error;
use crate::core::{
- controller::ControllerError,
+ controller::{AdminAppController, AnonAppController, ControllerError},
models::bike::{Bike, BikeId, BikeStatus, NewBike},
};
-impl super::AppController {
+/// Reading the fleet needs no session: the reservation form shows it before
+/// anybody logs in.
+impl AnonAppController {
pub async fn get_bikes(&self) -> Result, ControllerError> {
self.db.get_bikes().await.map_err(Into::into)
}
@@ -14,6 +16,10 @@ impl super::AppController {
self.db.get_bike(id).await.map_err(Into::into)
}
+}
+
+/// Changing the fleet is an admin action
+impl AdminAppController {
pub async fn create_bike(&self, bike: NewBike) -> Result {
if bike.key_quantity < 0 || bike.name.trim().is_empty() {
return Err(BikesControllerError::BikeInvalid.into());
diff --git a/src/core/controller/mod.rs b/src/core/controller/mod.rs
index dbbf693..c79449a 100644
--- a/src/core/controller/mod.rs
+++ b/src/core/controller/mod.rs
@@ -1,32 +1,126 @@
//! The controller holds the business logic: it is the only place that knows the
-//! rules of the app. It talks to the outside world through the repository traits,
-//! so it depends neither on axum nor on sqlx.
+//! rules of the app. It talks to the outside world through the repository
+//! traits, so it depends neither on axum nor on sqlx.
//!
-//! One file per domain area, each one adding methods to `AppController` through
-//! `impl super::AppController`.
+//! Authorization is carried by the **type**, not by a check inside the
+//! handlers. Each level derefs into the one below, so a manager can do
+//! everything a logged in user can:
+//!
+//! ```text
+//! AnonAppController anybody, logged in or not
+//! └─ AppController a logged in user (the api extractor answers 401 without a session)
+//! ├─ ManagerAppController a member of one unit, for that unit
+//! └─ AdminAppController an admin
+//! ```
+//!
+//! A handler that takes an `AppController` cannot be reached anonymously: there
+//! is no way to forget the check.
-use std::sync::Arc;
+use std::{ops::Deref, sync::Arc};
use thiserror::Error;
use crate::core::{
- controller::{bikes::BikesControllerError, reservations::ReservationsControllerError},
+ controller::{
+ authn::AuthnControllerError, bikes::BikesControllerError,
+ reservations::ReservationsControllerError,
+ },
+ models::{unit::UnitId, user::User},
repositories::{DatabaseRepository, RepositoryError},
};
+pub mod authn;
pub mod bikes;
pub mod reservations;
pub mod users;
-/// Entry point of the business logic. Cheap to clone: handlers get one per request.
+/// Entry point of the business logic, for anybody. Cheap to clone: handlers get
+/// one per request.
+#[derive(Clone)]
+pub struct AnonAppController {
+ pub(crate) db: Arc>,
+}
+
+impl AnonAppController {
+ pub fn new(db: Arc>) -> Self {
+ Self { db }
+ }
+
+ /// Called by the api extractor once the session has been resolved
+ pub fn auth(self, user: User) -> AppController {
+ AppController { inner: self, user }
+ }
+}
+
+/// A logged in user.
#[derive(Clone)]
pub struct AppController {
- db: Arc>,
+ inner: AnonAppController,
+ user: User,
+}
+
+impl Deref for AppController {
+ type Target = AnonAppController;
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
}
impl AppController {
- pub fn new(db: Arc>) -> Self {
- Self { db }
+ pub fn user(&self) -> &User {
+ &self.user
+ }
+
+ /// An admin manages every unit: refusing them here would only produce
+ /// surprising 403s on routes they are otherwise allowed to use.
+ pub fn try_into_manager(self, unit: UnitId) -> Result {
+ if self.user.admin || self.user.units.iter().any(|u| u.id == unit) {
+ Ok(ManagerAppController { inner: self, unit })
+ } else {
+ Err(ControllerError::ManagerAuthorizationError(unit))
+ }
+ }
+
+ pub fn try_into_admin(self) -> Result {
+ if self.user.admin {
+ Ok(AdminAppController { inner: self })
+ } else {
+ Err(ControllerError::AdminAuthorizationError)
+ }
+ }
+}
+
+/// A member of `unit`, acting for that unit
+#[derive(Clone)]
+pub struct ManagerAppController {
+ inner: AppController,
+ pub(crate) unit: UnitId,
+}
+
+impl Deref for ManagerAppController {
+ type Target = AppController;
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+}
+
+pub struct AdminAppController {
+ inner: AppController,
+}
+
+impl Deref for AdminAppController {
+ type Target = AppController;
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+}
+
+impl AdminAppController {
+ pub fn into_manager(self, unit: UnitId) -> ManagerAppController {
+ ManagerAppController {
+ inner: self.inner,
+ unit,
+ }
}
}
@@ -38,7 +132,15 @@ pub enum ControllerError {
InternalError(String),
#[error("Generic repository error")]
RepositoryError(#[from] RepositoryError),
+ #[error("Authorization denied: the user is not part of the unit {0:?}")]
+ ManagerAuthorizationError(UnitId),
+ #[error("Authorization denied: the user is not an admin")]
+ AdminAuthorizationError,
+ #[error("Object's unit modification is not allowed")]
+ ImmutableUnitModificationError,
+ #[error("Authentication error: {0}")]
+ Authn(#[from] AuthnControllerError),
#[error("Bike specific error: {0}")]
Bike(#[from] BikesControllerError),
#[error("Reservation specific error: {0}")]
diff --git a/src/core/controller/reservations.rs b/src/core/controller/reservations.rs
index d8dc821..111630c 100644
--- a/src/core/controller/reservations.rs
+++ b/src/core/controller/reservations.rs
@@ -1,7 +1,7 @@
use thiserror::Error;
use crate::core::{
- controller::ControllerError,
+ controller::{AnonAppController, AppController, ControllerError, ManagerAppController},
models::{
bike::BikeStatus,
reservation::{
@@ -11,7 +11,8 @@ use crate::core::{
},
};
-impl super::AppController {
+/// Reading the reservations needs no session: the calendar is public.
+impl AnonAppController {
pub async fn get_reservations(&self) -> Result, ControllerError> {
self.db.get_reservations().await.map_err(Into::into)
}
@@ -30,6 +31,11 @@ impl super::AppController {
self.db.get_reservation(id).await.map_err(Into::into)
}
+}
+
+/// Filing a request is done in one's own name: the requester is the session
+/// user, never something the client gets to choose.
+impl AppController {
pub async fn create_reservation(
&self,
reservation: NewReservation,
@@ -44,11 +50,14 @@ impl super::AppController {
}
}
self.db
- .create_reservation(reservation)
+ .create_reservation(reservation, self.user().id)
.await
.map_err(Into::into)
}
+}
+/// Touching an existing reservation is reserved to its unit (or an admin)
+impl ManagerAppController {
pub async fn update_reservation(
&self,
reservation: ReservationEdit,
@@ -58,6 +67,9 @@ impl super::AppController {
}
let current = self.db.get_reservation(reservation.id).await?;
+ if current.unit.id != self.unit {
+ return Err(ControllerError::ImmutableUnitModificationError);
+ }
if current.status.is_final() {
return Err(ReservationsControllerError::ReservationFinal(current.status).into());
}
@@ -73,7 +85,11 @@ impl super::AppController {
id: ReservationId,
status: ReservationStatus,
) -> Result<(), ControllerError> {
- let current = self.db.get_reservation(id).await?.status;
+ let current = self.db.get_reservation(id).await?;
+ if current.unit.id != self.unit {
+ return Err(ControllerError::ImmutableUnitModificationError);
+ }
+ let current = current.status;
if current == status {
return Ok(());
}
@@ -87,6 +103,9 @@ impl super::AppController {
}
pub async fn delete_reservation(&self, id: ReservationId) -> Result<(), ControllerError> {
+ if self.db.get_reservation(id).await?.unit.id != self.unit {
+ return Err(ControllerError::ImmutableUnitModificationError);
+ }
self.db.delete_reservation(id).await.map_err(Into::into)
}
}
diff --git a/src/core/controller/users.rs b/src/core/controller/users.rs
index aa57b03..594599c 100644
--- a/src/core/controller/users.rs
+++ b/src/core/controller/users.rs
@@ -2,15 +2,11 @@
//! provider on login. The only decision that belongs to us is `admin`.
use crate::core::{
- controller::ControllerError,
- models::user::{NewUser, User, UserId},
+ controller::{AdminAppController, AnonAppController, ControllerError},
+ models::user::{User, UserId},
};
-impl super::AppController {
- pub async fn login(&self, user: NewUser) -> Result {
- self.db.upsert_user(user).await.map_err(Into::into)
- }
-
+impl AnonAppController {
pub async fn get_user(&self, id: UserId) -> Result {
self.db.get_user(id).await.map_err(Into::into)
}
@@ -26,6 +22,9 @@ impl super::AppController {
.map_err(Into::into)
}
+}
+
+impl AdminAppController {
pub async fn set_user_admin(&self, id: UserId, admin: bool) -> Result<(), ControllerError> {
self.db.set_user_admin(id, admin).await.map_err(Into::into)
}
diff --git a/src/core/models/reservation.rs b/src/core/models/reservation.rs
index 6b4f894..843308e 100644
--- a/src/core/models/reservation.rs
+++ b/src/core/models/reservation.rs
@@ -4,7 +4,11 @@ use chrono::{DateTime, Utc};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use crate::core::models::{bike::BikeId, unit::UnitId, user::UserId};
+use crate::core::models::{
+ bike::BikeId,
+ unit::{Unit, UnitId},
+ user::{UserId, UserSummary},
+};
pub type ReservationId = i32;
@@ -42,11 +46,11 @@ impl ReservationStatus {
#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, PartialEq, Eq)]
pub struct Reservation {
pub id: ReservationId,
- pub unit: UnitId,
+ pub unit: Unit,
pub start_time: DateTime,
pub end_time: DateTime,
pub requester: UserId,
- pub users: Vec,
+ pub users: Vec,
pub telegram: String,
pub description: String,
pub bikes: Vec,
@@ -58,7 +62,6 @@ pub struct NewReservation {
pub unit: UnitId,
pub start_time: DateTime,
pub end_time: DateTime,
- pub requester: UserId,
pub users: Vec,
pub telegram: String,
pub description: String,
@@ -79,9 +82,7 @@ pub struct ReservationEdit {
impl NewReservation {
pub fn is_valid(&self) -> bool {
- self.end_time > self.start_time
- && !self.bikes.is_empty()
- && self.users.contains(&self.requester)
+ self.end_time > self.start_time && !self.bikes.is_empty()
}
}
diff --git a/src/core/models/unit.rs b/src/core/models/unit.rs
index 8cbab10..79ee81e 100644
--- a/src/core/models/unit.rs
+++ b/src/core/models/unit.rs
@@ -1 +1,11 @@
-pub type UnitId = String;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+pub type UnitId = i32;
+
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+pub struct Unit {
+ pub id: UnitId,
+ /// The Whiskey group name
+ pub name: String,
+}
diff --git a/src/core/models/user.rs b/src/core/models/user.rs
index 45b968c..6ab5c66 100644
--- a/src/core/models/user.rs
+++ b/src/core/models/user.rs
@@ -1,7 +1,7 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use crate::core::models::unit::UnitId;
+use crate::core::models::unit::Unit;
pub type UserId = i32;
@@ -13,7 +13,7 @@ pub struct User {
pub name: String,
pub email: String,
pub oidc_sub: String,
- pub units: Vec,
+ pub units: Vec,
pub admin: bool,
}
@@ -24,5 +24,14 @@ pub struct NewUser {
pub name: String,
pub email: String,
pub oidc_sub: String,
- pub units: Vec,
+}
+
+/// A user as they appear inside another object (a reservation, ...): enough to
+/// show who they are, without dragging their units along.
+#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, PartialEq, Eq)]
+pub struct UserSummary {
+ pub id: UserId,
+ pub firstname: String,
+ pub name: String,
+ pub email: String,
}
diff --git a/src/core/repositories/mod.rs b/src/core/repositories/mod.rs
index ede6268..7ee4581 100644
--- a/src/core/repositories/mod.rs
+++ b/src/core/repositories/mod.rs
@@ -8,18 +8,27 @@ use async_trait::async_trait;
use thiserror::Error;
use crate::core::repositories::{
- bikes_repository::BikesRepository, reservations_repository::ReservationsRepository,
+ bikes_repository::BikesRepository, oidc_states_repository::OidcStatesRepository,
+ reservations_repository::ReservationsRepository, units_repository::UnitsRepository,
users_repository::UsersRepository,
};
pub mod bikes_repository;
+pub mod oidc_states_repository;
pub mod reservations_repository;
+pub mod units_repository;
pub mod users_repository;
/// Add every new repository trait here so the controller can use it through `db`
#[async_trait]
pub trait DatabaseRepository:
- UsersRepository + BikesRepository + ReservationsRepository + Send + Sync
+ UsersRepository
+ + BikesRepository
+ + ReservationsRepository
+ + UnitsRepository
+ + OidcStatesRepository
+ + Send
+ + Sync
{
}
diff --git a/src/core/repositories/oidc_states_repository.rs b/src/core/repositories/oidc_states_repository.rs
index 01aa650..cc85459 100644
--- a/src/core/repositories/oidc_states_repository.rs
+++ b/src/core/repositories/oidc_states_repository.rs
@@ -4,12 +4,19 @@ use crate::{core::repositories::RepositoryError, utils::whiskey};
#[async_trait]
pub trait OidcStatesRepository {
- async fn get_whiskey_data(
+ /// Reads the state back **and consumes it**: an authorization state is
+ /// single use, otherwise the callback could be replayed. Returns
+ /// `NotFound` when the state is unknown, already used, or expired.
+ async fn take_whiskey_data(
&self,
csrf_token: String,
) -> Result;
+
async fn save_whiskey_data(
&self,
data: whiskey::AuthorizeBackendData,
) -> Result<(), RepositoryError>;
+
+ /// Drops the states nobody came back for
+ async fn delete_expired_whiskey_data(&self) -> Result;
}
diff --git a/src/core/repositories/reservations_repository.rs b/src/core/repositories/reservations_repository.rs
index 5827632..af5aa21 100644
--- a/src/core/repositories/reservations_repository.rs
+++ b/src/core/repositories/reservations_repository.rs
@@ -6,6 +6,7 @@ use crate::core::{
NewReservation, Reservation, ReservationEdit, ReservationId, ReservationStatus,
},
unit::UnitId,
+ user::UserId,
},
repositories::RepositoryError,
};
@@ -19,9 +20,12 @@ pub trait ReservationsRepository {
) -> Result, RepositoryError>;
async fn get_reservation(&self, id: ReservationId) -> Result;
+ /// Always stored as `Requested`: the state machine starts here. The
+ /// requester comes from the session, not from the request body.
async fn create_reservation(
&self,
reservation: NewReservation,
+ requester: UserId,
) -> Result;
async fn update_reservation(&self, reservation: ReservationEdit)
diff --git a/src/core/repositories/units_repository.rs b/src/core/repositories/units_repository.rs
new file mode 100644
index 0000000..f109aed
--- /dev/null
+++ b/src/core/repositories/units_repository.rs
@@ -0,0 +1,12 @@
+use async_trait::async_trait;
+
+use crate::core::{models::unit::Unit, repositories::RepositoryError};
+
+#[async_trait]
+pub trait UnitsRepository {
+ async fn get_units(&self) -> Result, RepositoryError>;
+
+ /// Resolves group names to units, creating the ones we have never seen.
+ /// This is what keeps a brand new Whiskey group from breaking a login.
+ async fn upsert_units(&self, names: &[String]) -> Result, RepositoryError>;
+}
diff --git a/src/core/repositories/users_repository.rs b/src/core/repositories/users_repository.rs
index bea0f9c..d049e90 100644
--- a/src/core/repositories/users_repository.rs
+++ b/src/core/repositories/users_repository.rs
@@ -1,7 +1,10 @@
use async_trait::async_trait;
use crate::core::{
- models::user::{NewUser, User, UserId},
+ models::{
+ unit::UnitId,
+ user::{NewUser, User, UserId},
+ },
repositories::RepositoryError,
};
@@ -12,7 +15,14 @@ pub trait UsersRepository {
async fn get_user_external_id(&self, external_id: String) -> Result;
async fn get_user_oidc_sub(&self, oidc_sub: String) -> Result;
+ /// Creates the user, or refreshes the row from the provider claims.
+ /// `oidc_sub` is the identity. `admin` and the units are ours and are left
+ /// untouched, so a login never demotes anybody nor loses their units.
async fn upsert_user(&self, user: NewUser) -> Result;
+ /// Replaces the whole set of units the user belongs to
+ async fn set_user_units(&self, id: UserId, units: Vec)
+ -> Result<(), RepositoryError>;
+
async fn set_user_admin(&self, id: UserId, admin: bool) -> Result<(), RepositoryError>;
}
diff --git a/src/main.rs b/src/main.rs
index 5c13945..e0d6c39 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,7 +9,7 @@ use tower_http::services::{ServeDir, ServeFile};
use tracing::info;
use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt};
-use crate::{core::controller::AppController, services::database::SqlxDatabase};
+use crate::{core::controller::AnonAppController, services::database::SqlxDatabase};
mod api;
mod core;
@@ -24,7 +24,7 @@ async fn main() {
.with(tracing_subscriber::fmt::layer())
.with(
EnvFilter::try_from_default_env()
- .unwrap_or_else(|_| EnvFilter::new(format!("info, {}=debug", env!("CRATE_NAME")))),
+ .unwrap_or_else(|_| EnvFilter::new(format!("info,{}=debug", env!("CRATE_NAME")))),
)
.init();
@@ -32,15 +32,17 @@ async fn main() {
.await
.expect("Unable to connect to database");
- let controller = AppController::new(Arc::new(Box::new(db)));
+ let aac = AnonAppController::new(Arc::new(Box::new(db)));
// Anything that is not an api route is served from the built frontend,
// falling back on index.html so the vue router can handle the path.
// (`fallback` and not `not_found_service`, which would force a 404 status)
- let app =
- api::get_router(controller).fallback_service(ServeDir::new(&config.frontend_dir).fallback(
- ServeFile::new(format!("{}/index.html", config.frontend_dir)),
- ));
+ let app = api::get_router(aac).fallback_service(
+ ServeDir::new(&config.frontend_dir).fallback(ServeFile::new(format!(
+ "{}/index.html",
+ config.frontend_dir
+ ))),
+ );
let bind_address = config.get_bind_address();
let listener = tokio::net::TcpListener::bind(&bind_address).await.unwrap();
diff --git a/src/services/database/mod.rs b/src/services/database/mod.rs
index c815cf6..5934b27 100644
--- a/src/services/database/mod.rs
+++ b/src/services/database/mod.rs
@@ -5,7 +5,9 @@
//! data must be present (`cargo sqlx prepare`).
mod bikes;
+mod oidc_states;
mod reservations;
+mod units;
mod users;
use async_trait::async_trait;
diff --git a/src/services/database/oidc_states.rs b/src/services/database/oidc_states.rs
index a82563e..c5e2e3f 100644
--- a/src/services/database/oidc_states.rs
+++ b/src/services/database/oidc_states.rs
@@ -1,5 +1,11 @@
+//! Short lived state of an in-flight OIDC authorization.
+//!
+//! The whole `AuthorizeBackendData` is stored as json under the csrf token: it
+//! is opaque to the database, and nothing else ever reads it.
+
use async_trait::async_trait;
-use sqlx::{prelude::FromRow, query, query_as};
+use chrono::{Duration, Utc};
+use sqlx::{query, query_as};
use crate::{
core::repositories::{RepositoryError, oidc_states_repository::OidcStatesRepository},
@@ -7,66 +13,66 @@ use crate::{
utils::whiskey,
};
-#[derive(FromRow)]
-struct DBOidcStateData {
+/// A user has this long to come back from the provider
+const STATE_LIFETIME_MINUTES: i64 = 15;
+
+struct OidcStateDB {
key: String,
data: String,
}
-impl TryFrom for DBOidcStateData {
+impl TryFrom for OidcStateDB {
type Error = RepositoryError;
fn try_from(value: whiskey::AuthorizeBackendData) -> Result {
- Ok(DBOidcStateData {
+ Ok(OidcStateDB {
key: value.csrf_token(),
data: serde_json::to_string(&value)?,
})
}
}
-impl TryInto for DBOidcStateData {
+impl TryFrom for whiskey::AuthorizeBackendData {
type Error = RepositoryError;
- fn try_into(self) -> Result {
- let value: whiskey::AuthorizeBackendData = serde_json::from_str(&self.data)?;
- if value.csrf_token() != self.key {
+ fn try_from(value: OidcStateDB) -> Result {
+ let data: whiskey::AuthorizeBackendData = serde_json::from_str(&value.data)?;
+ if data.csrf_token() != value.key {
return Err(RepositoryError::TypeConversion(
"key of whiskey authorize backend data doesn't match".to_owned(),
));
}
- Ok(value)
+ Ok(data)
}
}
#[async_trait]
impl OidcStatesRepository for SqlxDatabase {
- async fn get_whiskey_data(
+ async fn take_whiskey_data(
&self,
csrf_token: String,
) -> Result {
+ let cutoff = Utc::now() - Duration::minutes(STATE_LIFETIME_MINUTES);
query_as!(
- DBOidcStateData,
- r#"SELECT
- key,
- data
- FROM oidc_states
- WHERE key = $1"#,
- csrf_token
+ OidcStateDB,
+ r#"DELETE FROM oidc_states
+ WHERE key = $1 AND created_at > $2
+ RETURNING key, data"#,
+ csrf_token,
+ cutoff
)
.fetch_one(&self.pool)
.await?
.try_into()
}
- // TODO: Expire the data and remove it periodically
async fn save_whiskey_data(
&self,
data: whiskey::AuthorizeBackendData,
) -> Result<(), RepositoryError> {
- let data: DBOidcStateData = data.try_into()?;
+ let data: OidcStateDB = data.try_into()?;
query!(
- r#"INSERT INTO oidc_states
- (key, data)
+ r#"INSERT INTO oidc_states (key, data)
VALUES ($1, $2)"#,
data.key,
data.data
@@ -75,4 +81,14 @@ impl OidcStatesRepository for SqlxDatabase {
.await?;
Ok(())
}
+
+ async fn delete_expired_whiskey_data(&self) -> Result {
+ let cutoff = Utc::now() - Duration::minutes(STATE_LIFETIME_MINUTES);
+ Ok(
+ query!(r#"DELETE FROM oidc_states WHERE created_at <= $1"#, cutoff)
+ .execute(&self.pool)
+ .await?
+ .rows_affected(),
+ )
+ }
}
diff --git a/src/services/database/reservations.rs b/src/services/database/reservations.rs
index 685ad4d..6b08118 100644
--- a/src/services/database/reservations.rs
+++ b/src/services/database/reservations.rs
@@ -5,6 +5,7 @@
use async_trait::async_trait;
use chrono::{DateTime, Utc};
+use serde_json::Value;
use sqlx::{query, query_as};
use crate::{
@@ -13,7 +14,8 @@ use crate::{
reservation::{
NewReservation, Reservation, ReservationEdit, ReservationId, ReservationStatus,
},
- unit::UnitId,
+ unit::{Unit, UnitId},
+ user::UserId,
},
repositories::{RepositoryError, reservations_repository::ReservationsRepository},
},
@@ -59,31 +61,37 @@ impl From for ReservationStatusDB {
struct ReservationDB {
pub id: i32,
- pub unit: String,
+ pub unit_id: i32,
+ pub unit_name: String,
pub start_time: DateTime,
pub end_time: DateTime,
pub requester_id: i32,
pub telegram: String,
pub description: String,
pub status: ReservationStatusDB,
- pub users: Vec,
+ pub users: Value,
pub bikes: Vec,
}
-impl From for Reservation {
- fn from(value: ReservationDB) -> Self {
- Reservation {
+impl TryFrom for Reservation {
+ type Error = RepositoryError;
+
+ fn try_from(value: ReservationDB) -> Result {
+ Ok(Reservation {
id: value.id,
- unit: value.unit,
+ unit: Unit {
+ id: value.unit_id,
+ name: value.unit_name,
+ },
start_time: value.start_time,
end_time: value.end_time,
requester: value.requester_id,
- users: value.users,
+ users: serde_json::from_value(value.users)?,
telegram: value.telegram,
description: value.description,
bikes: value.bikes,
status: value.status.into(),
- }
+ })
}
}
@@ -135,29 +143,38 @@ impl ReservationsRepository for SqlxDatabase {
ReservationDB,
r#"SELECT
r.id,
- r.unit,
+ r.unit_id,
+ un."name" AS unit_name,
r.start_time,
r.end_time,
r.requester_id,
r.telegram,
r."description",
r.status AS "status: ReservationStatusDB",
- ARRAY(
- SELECT user_id FROM reservations_users
- WHERE reservation_id = r.id ORDER BY user_id
- ) AS "users!",
+ COALESCE((
+ SELECT json_agg(json_build_object(
+ 'id', u.id,
+ 'firstname', u.firstname,
+ 'name', u."name",
+ 'email', u.email
+ ) ORDER BY u."name", u.firstname)
+ FROM reservations_users ru
+ JOIN users u ON u.id = ru.user_id
+ WHERE ru.reservation_id = r.id
+ ), '[]'::json) AS "users!",
ARRAY(
SELECT bike_id FROM reservations_bikes
WHERE reservation_id = r.id ORDER BY bike_id
) AS "bikes!"
FROM reservations r
+ JOIN units un ON un.id = r.unit_id
ORDER BY r.start_time DESC"#
)
.fetch_all(&self.pool)
.await?
.into_iter()
- .map(Into::into)
- .collect())
+ .map(TryInto::try_into)
+ .collect::, _>>()?)
}
async fn get_unit_reservations(
@@ -168,31 +185,40 @@ impl ReservationsRepository for SqlxDatabase {
ReservationDB,
r#"SELECT
r.id,
- r.unit,
+ r.unit_id,
+ un."name" AS unit_name,
r.start_time,
r.end_time,
r.requester_id,
r.telegram,
r."description",
r.status AS "status: ReservationStatusDB",
- ARRAY(
- SELECT user_id FROM reservations_users
- WHERE reservation_id = r.id ORDER BY user_id
- ) AS "users!",
+ COALESCE((
+ SELECT json_agg(json_build_object(
+ 'id', u.id,
+ 'firstname', u.firstname,
+ 'name', u."name",
+ 'email', u.email
+ ) ORDER BY u."name", u.firstname)
+ FROM reservations_users ru
+ JOIN users u ON u.id = ru.user_id
+ WHERE ru.reservation_id = r.id
+ ), '[]'::json) AS "users!",
ARRAY(
SELECT bike_id FROM reservations_bikes
WHERE reservation_id = r.id ORDER BY bike_id
) AS "bikes!"
FROM reservations r
- WHERE r.unit = $1
+ JOIN units un ON un.id = r.unit_id
+ WHERE r.unit_id = $1
ORDER BY r.start_time DESC"#,
unit
)
.fetch_all(&self.pool)
.await?
.into_iter()
- .map(Into::into)
- .collect())
+ .map(TryInto::try_into)
+ .collect::, _>>()?)
}
async fn get_reservation(&self, id: ReservationId) -> Result {
@@ -200,46 +226,56 @@ impl ReservationsRepository for SqlxDatabase {
ReservationDB,
r#"SELECT
r.id,
- r.unit,
+ r.unit_id,
+ un."name" AS unit_name,
r.start_time,
r.end_time,
r.requester_id,
r.telegram,
r."description",
r.status AS "status: ReservationStatusDB",
- ARRAY(
- SELECT user_id FROM reservations_users
- WHERE reservation_id = r.id ORDER BY user_id
- ) AS "users!",
+ COALESCE((
+ SELECT json_agg(json_build_object(
+ 'id', u.id,
+ 'firstname', u.firstname,
+ 'name', u."name",
+ 'email', u.email
+ ) ORDER BY u."name", u.firstname)
+ FROM reservations_users ru
+ JOIN users u ON u.id = ru.user_id
+ WHERE ru.reservation_id = r.id
+ ), '[]'::json) AS "users!",
ARRAY(
SELECT bike_id FROM reservations_bikes
WHERE reservation_id = r.id ORDER BY bike_id
) AS "bikes!"
FROM reservations r
+ JOIN units un ON un.id = r.unit_id
WHERE r.id = $1"#,
id
)
.fetch_one(&self.pool)
.await?
- .into())
+ .try_into()?)
}
async fn create_reservation(
&self,
reservation: NewReservation,
+ requester: UserId,
) -> Result {
let mut tx = self.pool.begin().await?;
// No status here: the column defaults to 'requested', the start of the
// state machine.
let id = query!(
- r#"INSERT INTO reservations (unit, start_time, end_time, requester_id, telegram, "description")
+ r#"INSERT INTO reservations (unit_id, start_time, end_time, requester_id, telegram, "description")
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id"#,
reservation.unit,
reservation.start_time,
reservation.end_time,
- reservation.requester,
+ requester,
reservation.telegram,
reservation.description
)
@@ -247,22 +283,19 @@ impl ReservationsRepository for SqlxDatabase {
.await?
.id;
- Self::set_reservation_links(&mut tx, id, &reservation.users, &reservation.bikes).await?;
+ // The requester is always allowed to pick the bikes up
+ let mut users = reservation.users.clone();
+ if !users.contains(&requester) {
+ users.push(requester);
+ }
+ Self::set_reservation_links(&mut tx, id, &users, &reservation.bikes).await?;
tx.commit().await?;
- Ok(Reservation {
- id,
- unit: reservation.unit,
- start_time: reservation.start_time,
- end_time: reservation.end_time,
- requester: reservation.requester,
- users: reservation.users,
- telegram: reservation.telegram,
- description: reservation.description,
- bikes: reservation.bikes,
- status: ReservationStatus::Requested,
- })
+ // Read it back through the normal query rather than rebuilding it by
+ // hand: the caller gets the unit and the users exactly as any other read
+ // would return them.
+ self.get_reservation(id).await
}
async fn update_reservation(
@@ -273,7 +306,7 @@ impl ReservationsRepository for SqlxDatabase {
let result = query!(
r#"UPDATE reservations
- SET unit = $2, start_time = $3, end_time = $4, telegram = $5, "description" = $6
+ SET unit_id = $2, start_time = $3, end_time = $4, telegram = $5, "description" = $6
WHERE id = $1"#,
reservation.id,
reservation.unit,
diff --git a/src/services/database/units.rs b/src/services/database/units.rs
new file mode 100644
index 0000000..4ddb920
--- /dev/null
+++ b/src/services/database/units.rs
@@ -0,0 +1,48 @@
+//! Units. Rows are created on demand, from the group names Whiskey hands out.
+
+use async_trait::async_trait;
+use sqlx::{query, query_as};
+
+use crate::{
+ core::{
+ models::unit::Unit,
+ repositories::{RepositoryError, units_repository::UnitsRepository},
+ },
+ services::database::SqlxDatabase,
+};
+
+#[async_trait]
+impl UnitsRepository for SqlxDatabase {
+ async fn get_units(&self) -> Result, RepositoryError> {
+ Ok(
+ query_as!(Unit, r#"SELECT id, "name" FROM units ORDER BY "name""#)
+ .fetch_all(&self.pool)
+ .await?,
+ )
+ }
+
+ async fn upsert_units(&self, names: &[String]) -> Result, RepositoryError> {
+ let mut tx = self.pool.begin().await?;
+
+ query!(
+ r#"INSERT INTO units ("name")
+ SELECT DISTINCT UNNEST($1::text[])
+ ON CONFLICT ("name") DO NOTHING"#,
+ names
+ )
+ .execute(&mut *tx)
+ .await?;
+
+ // Read back after the insert: the ones that already existed are in here too
+ let units = query_as!(
+ Unit,
+ r#"SELECT id, "name" FROM units WHERE "name" = ANY($1::text[]) ORDER BY "name""#,
+ names
+ )
+ .fetch_all(&mut *tx)
+ .await?;
+
+ tx.commit().await?;
+ Ok(units)
+ }
+}
diff --git a/src/services/database/users.rs b/src/services/database/users.rs
index e218c2e..bf3a93a 100644
--- a/src/services/database/users.rs
+++ b/src/services/database/users.rs
@@ -4,7 +4,7 @@ use sqlx::{Executor, Postgres, query, query_as};
use crate::{
core::{
models::{
- unit::UnitId,
+ unit::{Unit, UnitId},
user::{NewUser, User, UserId},
},
repositories::{RepositoryError, users_repository::UsersRepository},
@@ -23,7 +23,7 @@ struct UserDB {
}
impl UserDB {
- fn into_user(self, units: Vec) -> User {
+ fn into_user(self, units: Vec) -> User {
User {
id: self.id,
external_id: self.external_id,
@@ -32,23 +32,23 @@ impl UserDB {
email: self.email,
oidc_sub: self.oidc_sub,
admin: self.admin,
- units: units.into_iter().map(|u| u.name).collect(),
+ units,
}
}
}
-struct UnitIdDB {
- pub name: UnitId,
-}
-
impl SqlxDatabase {
async fn user_with_units<'a, E>(user: UserDB, executor: E) -> Result
where
E: Executor<'a, Database = Postgres>,
{
let units = query_as!(
- UnitIdDB,
- r#"SELECT unit_name AS "name!" FROM units_users WHERE user_id = $1 ORDER BY unit_name"#,
+ Unit,
+ r#"SELECT u.id, u."name"
+ FROM units u
+ JOIN units_users uu ON uu.unit_id = u.id
+ WHERE uu.user_id = $1
+ ORDER BY u."name""#,
user.id
)
.fetch_all(executor)
@@ -130,30 +130,27 @@ impl UsersRepository for SqlxDatabase {
.fetch_one(&mut *tx)
.await?;
- query!(r#"DELETE FROM units_users WHERE user_id = $1"#, user_db.id)
+ let user = Self::user_with_units(user_db, &mut *tx).await?;
+ tx.commit().await?;
+ Ok(user)
+ }
+
+ async fn set_user_units(&self, id: UserId, units: Vec) -> Result<(), RepositoryError> {
+ let mut tx = self.pool.begin().await?;
+ query!(r#"DELETE FROM units_users WHERE user_id = $1"#, id)
.execute(&mut *tx)
.await?;
query!(
- r#"INSERT INTO units_users (user_id, unit_name)
- SELECT $1, UNNEST($2::text[])"#,
- user_db.id,
- &user.units
+ r#"INSERT INTO units_users (user_id, unit_id)
+ SELECT $1, UNNEST($2::integer[])
+ ON CONFLICT DO NOTHING"#,
+ id,
+ &units
)
.execute(&mut *tx)
.await?;
-
tx.commit().await?;
-
- Ok(User {
- id: user_db.id,
- external_id: user_db.external_id,
- firstname: user_db.firstname,
- name: user_db.name,
- email: user_db.email,
- oidc_sub: user_db.oidc_sub,
- units: user.units,
- admin: user_db.admin,
- })
+ Ok(())
}
async fn set_user_admin(&self, id: UserId, admin: bool) -> Result<(), RepositoryError> {
diff --git a/src/utils/config.rs b/src/utils/config.rs
index 63a8113..331b51c 100644
--- a/src/utils/config.rs
+++ b/src/utils/config.rs
@@ -7,6 +7,10 @@ use serde::Deserialize;
pub struct ServerConfig {
pub address: Option,
pub port: Option,
+ /// Public origin the browser reaches the app on. The OIDC redirect uri is
+ /// built from it, so it must match what is registered on the provider.
+ /// In development this is the vite dev server, not the backend.
+ pub base_url: Option,
}
#[derive(Deserialize, Clone)]
@@ -18,10 +22,38 @@ pub struct PostgresConfig {
pub name: String,
}
+/// Whiskey, the AGEPoly OIDC provider
+#[derive(Deserialize, Clone)]
+pub struct OidcConfig {
+ pub issuer_url: String,
+ pub client_id: String,
+ pub client_secret: String,
+ /// How long a session stays valid, in minutes
+ pub session_lifetime: Option,
+}
+
+/// A user that can be logged in without going through the provider.
+/// Only usable in debug builds, see `POST /api/login`.
+#[derive(Deserialize, Clone)]
+pub struct DevUserConfig {
+ pub firstname: String,
+ pub name: String,
+ /// Also the handle used to pick the user at login
+ pub email: String,
+ pub external_id: Option,
+ #[serde(default)]
+ pub units: Vec,
+ #[serde(default)]
+ pub admin: bool,
+}
+
#[derive(Deserialize, Clone)]
pub struct AppConfig {
pub server: ServerConfig,
pub postgres: PostgresConfig,
+ pub oidc: OidcConfig,
+ #[serde(default)]
+ pub dev_users: Vec,
/// Directory containing the built frontend
pub frontend_dir: String,
}
@@ -33,6 +65,20 @@ impl AppConfig {
format!("{}:{}", address, port)
}
+ /// Origin used to build the OIDC redirect uri. Defaults to the vite dev
+ /// server, which is what a developer reaches the app on.
+ pub fn get_base_url(&self) -> String {
+ self.server
+ .base_url
+ .clone()
+ .unwrap_or("http://localhost:5000".to_owned())
+ }
+
+ /// Session lifetime in minutes
+ pub fn get_session_lifetime(&self) -> i64 {
+ self.oidc.session_lifetime.unwrap_or(60)
+ }
+
pub fn get_postgresql_url(&self) -> String {
let host = self.postgres.host.clone().unwrap_or("localhost".to_owned());
let port = self.postgres.port.unwrap_or(5432);
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index ef68c36..4a1bb3b 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -1 +1,2 @@
pub mod config;
+pub mod whiskey;
diff --git a/src/utils/whiskey.rs b/src/utils/whiskey.rs
index 8a9e528..064c989 100644
--- a/src/utils/whiskey.rs
+++ b/src/utils/whiskey.rs
@@ -40,7 +40,9 @@ async fn get_client() -> &'static Client {
CLIENT
.get_or_init(|| async {
let http_client = get_http_client();
- let config = config::get().clone();
+ let config = config::get();
+ let redirect_url = format!("{}/whiskey/callback", config.get_base_url());
+ let config = config.clone();
let provider_metadata = CoreProviderMetadata::discover_async(
IssuerUrl::new(config.oidc.issuer_url).unwrap(),
http_client,
@@ -54,7 +56,7 @@ async fn get_client() -> &'static Client {
Some(ClientSecret::new(config.oidc.client_secret)),
)
.set_redirect_uri(
- RedirectUrl::new(format!("{}/whiskey/callback", config.server.base_url)).unwrap(),
+ RedirectUrl::new(redirect_url).unwrap(),
)
})
.await
@@ -83,7 +85,6 @@ pub async fn authorize() -> Result<(String, AuthorizeBackendData), WhiskeyError>
CsrfToken::new_random,
Nonce::new_random,
)
- .add_scope(Scope::new("openid".to_owned()))
.add_scope(Scope::new("profile".to_owned()))
.add_scope(Scope::new("email".to_owned()))
.set_pkce_challenge(pkce_challenge)
@@ -106,6 +107,7 @@ pub struct UserInfoData {
pub name: String,
pub firstname: String,
pub email: String,
+ pub groups: Option>,
}
pub async fn callback(
@@ -214,18 +216,56 @@ pub async fn callback(
let sciper = claims.additional_claims().sciper.clone();
+ let groups = match claims.additional_claims().groups.clone() {
+ Some(groups) => Some(groups),
+ None => {
+ debug!("no groups claim in the id_token, trying the userinfo endpoint");
+ groups_from_userinfo(client, token_response.access_token()).await
+ }
+ };
+ debug!("Whiskey groups for {sciper}: {groups:?}");
+
Ok(UserInfoData {
firstname,
name,
sub,
sciper,
email,
+ groups,
})
}
-#[derive(Serialize, Deserialize, Debug, PartialEq)]
+async fn groups_from_userinfo(
+ client: &Client,
+ access_token: &openidconnect::AccessToken,
+) -> Option> {
+ let request = match client.user_info(access_token.to_owned(), None) {
+ Ok(request) => request,
+ Err(err) => {
+ debug!("no userinfo endpoint advertised: {err:?}");
+ return None;
+ }
+ };
+
+ match request
+ .request_async(get_http_client())
+ .await
+ .map(|claims: openidconnect::UserInfoClaims| {
+ claims.additional_claims().groups.clone()
+ }) {
+ Ok(groups) => groups,
+ Err(err) => {
+ debug!("userinfo request failed: {err:?}");
+ None
+ }
+ }
+}
+
+#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct WhiskeyClaims {
pub sciper: String,
+ #[serde(default)]
+ pub groups: Option>,
}
impl openidconnect::AdditionalClaims for WhiskeyClaims {}
@@ -259,3 +299,51 @@ pub type Client = openidconnect::Client<
openidconnect::EndpointMaybeSet,
openidconnect::EndpointMaybeSet,
>;
+
+#[cfg(test)]
+mod tests {
+ use super::WhiskeyClaims;
+
+ fn parse(json: &str) -> WhiskeyClaims {
+ serde_json::from_str(json).expect("claims should parse")
+ }
+
+ /// An absent claim must not be read as "belongs to no group": that would
+ /// wipe the units of every user at every login.
+ #[test]
+ fn absent_groups_claim_is_none() {
+ assert_eq!(parse(r#"{"sciper":"123456"}"#).groups, None);
+ }
+
+ #[test]
+ fn groups_are_read() {
+ assert_eq!(
+ parse(r#"{"sciper":"123456","groups":["agepoly","balelec"]}"#).groups,
+ Some(vec!["agepoly".to_owned(), "balelec".to_owned()])
+ );
+ }
+
+ /// Distinct from the absent case: here Whiskey did answer, and the answer
+ /// is that the user is in nothing.
+ #[test]
+ fn empty_groups_claim_is_some_empty() {
+ assert_eq!(
+ parse(r#"{"sciper":"123456","groups":[]}"#).groups,
+ Some(vec![])
+ );
+ }
+
+ /// The id_token carries plenty of claims we do not model
+ #[test]
+ fn unknown_claims_are_ignored() {
+ assert_eq!(
+ parse(r#"{"sciper":"123456","groups":["agepoly"],"uid":"x","other":42}"#).groups,
+ Some(vec!["agepoly".to_owned()])
+ );
+ }
+
+ #[test]
+ fn a_null_groups_claim_is_none() {
+ assert_eq!(parse(r#"{"sciper":"123456","groups":null}"#).groups, None);
+ }
+}