feat: application complète de suivi de tempsUnverified
Serveur axum + askama + sqlx (Postgres sslmode=require), auth par biscuit en cookie (6 mois) avec liste de révocation cachée en mémoire, CSRF stateless HMAC, stats en SVG serveur (barres empilées par tâche), PWA minimale, CLI clap (serve / user add / keygen). IDs en UUIDv7 générés côté Rust.
7255330 added
.gitignore +1 -0 | new file mode 100644 | ||
| @@ -0,0 +1 @@ | ||
| 1 | +/target | |
| new file mode 100644 | |||
| @@ -0,0 +1 @@ | |||
| 1 | +/target | ||
added
Cargo.lock +3179 -0 | new file mode 100644 | ||
| @@ -0,0 +1,3179 @@ | ||
| 1 | +# This file is automatically @generated by Cargo. | |
| 2 | +# It is not intended for manual editing. | |
| 3 | +version = 4 | |
| 4 | + | |
| 5 | +[[package]] | |
| 6 | +name = "aho-corasick" | |
| 7 | +version = "1.1.5" | |
| 8 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 9 | +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" | |
| 10 | +dependencies = [ | |
| 11 | + "memchr", | |
| 12 | +] | |
| 13 | + | |
| 14 | +[[package]] | |
| 15 | +name = "allocator-api2" | |
| 16 | +version = "0.2.21" | |
| 17 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 18 | +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" | |
| 19 | + | |
| 20 | +[[package]] | |
| 21 | +name = "android_system_properties" | |
| 22 | +version = "0.1.6" | |
| 23 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 24 | +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" | |
| 25 | +dependencies = [ | |
| 26 | + "libc", | |
| 27 | +] | |
| 28 | + | |
| 29 | +[[package]] | |
| 30 | +name = "anstream" | |
| 31 | +version = "1.0.0" | |
| 32 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 33 | +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" | |
| 34 | +dependencies = [ | |
| 35 | + "anstyle", | |
| 36 | + "anstyle-parse", | |
| 37 | + "anstyle-query", | |
| 38 | + "anstyle-wincon", | |
| 39 | + "colorchoice", | |
| 40 | + "is_terminal_polyfill", | |
| 41 | + "utf8parse", | |
| 42 | +] | |
| 43 | + | |
| 44 | +[[package]] | |
| 45 | +name = "anstyle" | |
| 46 | +version = "1.0.14" | |
| 47 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 48 | +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" | |
| 49 | + | |
| 50 | +[[package]] | |
| 51 | +name = "anstyle-parse" | |
| 52 | +version = "1.0.0" | |
| 53 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 54 | +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" | |
| 55 | +dependencies = [ | |
| 56 | + "utf8parse", | |
| 57 | +] | |
| 58 | + | |
| 59 | +[[package]] | |
| 60 | +name = "anstyle-query" | |
| 61 | +version = "1.1.5" | |
| 62 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 63 | +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" | |
| 64 | +dependencies = [ | |
| 65 | + "windows-sys 0.61.2", | |
| 66 | +] | |
| 67 | + | |
| 68 | +[[package]] | |
| 69 | +name = "anstyle-wincon" | |
| 70 | +version = "3.0.11" | |
| 71 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 72 | +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" | |
| 73 | +dependencies = [ | |
| 74 | + "anstyle", | |
| 75 | + "once_cell_polyfill", | |
| 76 | + "windows-sys 0.61.2", | |
| 77 | +] | |
| 78 | + | |
| 79 | +[[package]] | |
| 80 | +name = "anyhow" | |
| 81 | +version = "1.0.104" | |
| 82 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 83 | +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" | |
| 84 | + | |
| 85 | +[[package]] | |
| 86 | +name = "argon2" | |
| 87 | +version = "0.5.3" | |
| 88 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 89 | +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" | |
| 90 | +dependencies = [ | |
| 91 | + "base64ct", | |
| 92 | + "blake2", | |
| 93 | + "cpufeatures", | |
| 94 | + "password-hash", | |
| 95 | +] | |
| 96 | + | |
| 97 | +[[package]] | |
| 98 | +name = "askama" | |
| 99 | +version = "0.16.0" | |
| 100 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 101 | +checksum = "f1bf825125edd887a019d0a3a837dcc5499a68b0d034cc3eb594070c3e18addc" | |
| 102 | +dependencies = [ | |
| 103 | + "askama_macros", | |
| 104 | + "itoa", | |
| 105 | + "percent-encoding", | |
| 106 | + "serde", | |
| 107 | + "serde_json", | |
| 108 | +] | |
| 109 | + | |
| 110 | +[[package]] | |
| 111 | +name = "askama_derive" | |
| 112 | +version = "0.16.0" | |
| 113 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 114 | +checksum = "e1c7065972a130eafa84215f21352ae15b4a7393da48c1f5e103904490736738" | |
| 115 | +dependencies = [ | |
| 116 | + "askama_parser", | |
| 117 | + "basic-toml", | |
| 118 | + "glob", | |
| 119 | + "memchr", | |
| 120 | + "proc-macro2", | |
| 121 | + "quote", | |
| 122 | + "rustc-hash", | |
| 123 | + "serde", | |
| 124 | + "serde_derive", | |
| 125 | + "syn 2.0.119", | |
| 126 | +] | |
| 127 | + | |
| 128 | +[[package]] | |
| 129 | +name = "askama_macros" | |
| 130 | +version = "0.16.0" | |
| 131 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 132 | +checksum = "0e23b1d2c4bd39a41971f6124cef4cc6fd0540913ecb90919b69ab3bbe44ae1a" | |
| 133 | +dependencies = [ | |
| 134 | + "askama_derive", | |
| 135 | +] | |
| 136 | + | |
| 137 | +[[package]] | |
| 138 | +name = "askama_parser" | |
| 139 | +version = "0.16.0" | |
| 140 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 141 | +checksum = "7db09fde9143e7ac4513358fb32ee32847125b63b18ea715afd487956da715da" | |
| 142 | +dependencies = [ | |
| 143 | + "rustc-hash", | |
| 144 | + "serde", | |
| 145 | + "serde_derive", | |
| 146 | + "unicode-ident", | |
| 147 | + "winnow", | |
| 148 | +] | |
| 149 | + | |
| 150 | +[[package]] | |
| 151 | +name = "atoi" | |
| 152 | +version = "2.0.0" | |
| 153 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 154 | +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" | |
| 155 | +dependencies = [ | |
| 156 | + "num-traits", | |
| 157 | +] | |
| 158 | + | |
| 159 | +[[package]] | |
| 160 | +name = "atomic-waker" | |
| 161 | +version = "1.1.2" | |
| 162 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 163 | +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" | |
| 164 | + | |
| 165 | +[[package]] | |
| 166 | +name = "autocfg" | |
| 167 | +version = "1.5.1" | |
| 168 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 169 | +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" | |
| 170 | + | |
| 171 | +[[package]] | |
| 172 | +name = "axum" | |
| 173 | +version = "0.8.9" | |
| 174 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 175 | +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" | |
| 176 | +dependencies = [ | |
| 177 | + "axum-core", | |
| 178 | + "bytes", | |
| 179 | + "form_urlencoded", | |
| 180 | + "futures-util", | |
| 181 | + "http", | |
| 182 | + "http-body", | |
| 183 | + "http-body-util", | |
| 184 | + "hyper", | |
| 185 | + "hyper-util", | |
| 186 | + "itoa", | |
| 187 | + "matchit", | |
| 188 | + "memchr", | |
| 189 | + "mime", | |
| 190 | + "percent-encoding", | |
| 191 | + "pin-project-lite", | |
| 192 | + "serde_core", | |
| 193 | + "serde_json", | |
| 194 | + "serde_path_to_error", | |
| 195 | + "serde_urlencoded", | |
| 196 | + "sync_wrapper", | |
| 197 | + "tokio", | |
| 198 | + "tower", | |
| 199 | + "tower-layer", | |
| 200 | + "tower-service", | |
| 201 | + "tracing", | |
| 202 | +] | |
| 203 | + | |
| 204 | +[[package]] | |
| 205 | +name = "axum-core" | |
| 206 | +version = "0.5.6" | |
| 207 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 208 | +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" | |
| 209 | +dependencies = [ | |
| 210 | + "bytes", | |
| 211 | + "futures-core", | |
| 212 | + "http", | |
| 213 | + "http-body", | |
| 214 | + "http-body-util", | |
| 215 | + "mime", | |
| 216 | + "pin-project-lite", | |
| 217 | + "sync_wrapper", | |
| 218 | + "tower-layer", | |
| 219 | + "tower-service", | |
| 220 | + "tracing", | |
| 221 | +] | |
| 222 | + | |
| 223 | +[[package]] | |
| 224 | +name = "axum-extra" | |
| 225 | +version = "0.12.6" | |
| 226 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 227 | +checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970" | |
| 228 | +dependencies = [ | |
| 229 | + "axum", | |
| 230 | + "axum-core", | |
| 231 | + "bytes", | |
| 232 | + "cookie", | |
| 233 | + "futures-core", | |
| 234 | + "futures-util", | |
| 235 | + "http", | |
| 236 | + "http-body", | |
| 237 | + "http-body-util", | |
| 238 | + "mime", | |
| 239 | + "pin-project-lite", | |
| 240 | + "tower-layer", | |
| 241 | + "tower-service", | |
| 242 | + "tracing", | |
| 243 | +] | |
| 244 | + | |
| 245 | +[[package]] | |
| 246 | +name = "base16ct" | |
| 247 | +version = "0.2.0" | |
| 248 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 249 | +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" | |
| 250 | + | |
| 251 | +[[package]] | |
| 252 | +name = "base64" | |
| 253 | +version = "0.13.1" | |
| 254 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 255 | +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" | |
| 256 | + | |
| 257 | +[[package]] | |
| 258 | +name = "base64" | |
| 259 | +version = "0.22.1" | |
| 260 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 261 | +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" | |
| 262 | + | |
| 263 | +[[package]] | |
| 264 | +name = "base64ct" | |
| 265 | +version = "1.8.3" | |
| 266 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 267 | +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" | |
| 268 | + | |
| 269 | +[[package]] | |
| 270 | +name = "basic-toml" | |
| 271 | +version = "0.1.10" | |
| 272 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 273 | +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" | |
| 274 | +dependencies = [ | |
| 275 | + "serde", | |
| 276 | +] | |
| 277 | + | |
| 278 | +[[package]] | |
| 279 | +name = "biscuit-auth" | |
| 280 | +version = "6.0.0" | |
| 281 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 282 | +checksum = "d5884fc86b3e21f5649ef4326e17ef729b3096e6502deaf13db7b7fb05bb992b" | |
| 283 | +dependencies = [ | |
| 284 | + "base64 0.13.1", | |
| 285 | + "biscuit-parser", | |
| 286 | + "biscuit-quote", | |
| 287 | + "ecdsa", | |
| 288 | + "ed25519-dalek", | |
| 289 | + "elliptic-curve", | |
| 290 | + "getrandom 0.2.17", | |
| 291 | + "hex", | |
| 292 | + "nom", | |
| 293 | + "p256", | |
| 294 | + "pkcs8 0.9.0", | |
| 295 | + "prost", | |
| 296 | + "prost-types", | |
| 297 | + "rand", | |
| 298 | + "rand_core", | |
| 299 | + "regex", | |
| 300 | + "serde_json", | |
| 301 | + "sha2 0.9.9", | |
| 302 | + "thiserror 1.0.69", | |
| 303 | + "time", | |
| 304 | + "zeroize", | |
| 305 | +] | |
| 306 | + | |
| 307 | +[[package]] | |
| 308 | +name = "biscuit-parser" | |
| 309 | +version = "0.2.0" | |
| 310 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 311 | +checksum = "9d7cafdbc8c30e1f0fb87df7161bec77f6f00da652cc33f102b0f95bd1cbc0fa" | |
| 312 | +dependencies = [ | |
| 313 | + "hex", | |
| 314 | + "nom", | |
| 315 | + "proc-macro2", | |
| 316 | + "quote", | |
| 317 | + "thiserror 1.0.69", | |
| 318 | + "time", | |
| 319 | +] | |
| 320 | + | |
| 321 | +[[package]] | |
| 322 | +name = "biscuit-quote" | |
| 323 | +version = "0.3.0" | |
| 324 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 325 | +checksum = "49d2332c742a07a846f1fb2760e58a0ee60f2bc30987046fcea816b40630335a" | |
| 326 | +dependencies = [ | |
| 327 | + "biscuit-parser", | |
| 328 | + "proc-macro-error2", | |
| 329 | + "proc-macro2", | |
| 330 | + "quote", | |
| 331 | + "syn 1.0.109", | |
| 332 | +] | |
| 333 | + | |
| 334 | +[[package]] | |
| 335 | +name = "bitflags" | |
| 336 | +version = "2.13.1" | |
| 337 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 338 | +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" | |
| 339 | +dependencies = [ | |
| 340 | + "serde_core", | |
| 341 | +] | |
| 342 | + | |
| 343 | +[[package]] | |
| 344 | +name = "blake2" | |
| 345 | +version = "0.10.6" | |
| 346 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 347 | +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" | |
| 348 | +dependencies = [ | |
| 349 | + "digest 0.10.7", | |
| 350 | +] | |
| 351 | + | |
| 352 | +[[package]] | |
| 353 | +name = "block-buffer" | |
| 354 | +version = "0.9.0" | |
| 355 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 356 | +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" | |
| 357 | +dependencies = [ | |
| 358 | + "generic-array", | |
| 359 | +] | |
| 360 | + | |
| 361 | +[[package]] | |
| 362 | +name = "block-buffer" | |
| 363 | +version = "0.10.4" | |
| 364 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 365 | +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" | |
| 366 | +dependencies = [ | |
| 367 | + "generic-array", | |
| 368 | +] | |
| 369 | + | |
| 370 | +[[package]] | |
| 371 | +name = "bumpalo" | |
| 372 | +version = "3.20.3" | |
| 373 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 374 | +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" | |
| 375 | + | |
| 376 | +[[package]] | |
| 377 | +name = "byteorder" | |
| 378 | +version = "1.5.0" | |
| 379 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 380 | +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" | |
| 381 | + | |
| 382 | +[[package]] | |
| 383 | +name = "bytes" | |
| 384 | +version = "1.12.1" | |
| 385 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 386 | +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" | |
| 387 | + | |
| 388 | +[[package]] | |
| 389 | +name = "cc" | |
| 390 | +version = "1.4.0" | |
| 391 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 392 | +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" | |
| 393 | +dependencies = [ | |
| 394 | + "find-msvc-tools", | |
| 395 | + "shlex", | |
| 396 | +] | |
| 397 | + | |
| 398 | +[[package]] | |
| 399 | +name = "cfg-if" | |
| 400 | +version = "1.0.4" | |
| 401 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 402 | +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" | |
| 403 | + | |
| 404 | +[[package]] | |
| 405 | +name = "chrono" | |
| 406 | +version = "0.4.45" | |
| 407 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 408 | +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" | |
| 409 | +dependencies = [ | |
| 410 | + "iana-time-zone", | |
| 411 | + "js-sys", | |
| 412 | + "num-traits", | |
| 413 | + "serde", | |
| 414 | + "wasm-bindgen", | |
| 415 | + "windows-link", | |
| 416 | +] | |
| 417 | + | |
| 418 | +[[package]] | |
| 419 | +name = "chrono-tz" | |
| 420 | +version = "0.10.4" | |
| 421 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 422 | +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" | |
| 423 | +dependencies = [ | |
| 424 | + "chrono", | |
| 425 | + "phf", | |
| 426 | +] | |
| 427 | + | |
| 428 | +[[package]] | |
| 429 | +name = "clap" | |
| 430 | +version = "4.6.6" | |
| 431 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 432 | +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" | |
| 433 | +dependencies = [ | |
| 434 | + "clap_builder", | |
| 435 | + "clap_derive", | |
| 436 | +] | |
| 437 | + | |
| 438 | +[[package]] | |
| 439 | +name = "clap_builder" | |
| 440 | +version = "4.6.6" | |
| 441 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 442 | +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" | |
| 443 | +dependencies = [ | |
| 444 | + "anstream", | |
| 445 | + "anstyle", | |
| 446 | + "clap_lex", | |
| 447 | + "strsim", | |
| 448 | +] | |
| 449 | + | |
| 450 | +[[package]] | |
| 451 | +name = "clap_derive" | |
| 452 | +version = "4.6.4" | |
| 453 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 454 | +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" | |
| 455 | +dependencies = [ | |
| 456 | + "heck", | |
| 457 | + "proc-macro2", | |
| 458 | + "quote", | |
| 459 | + "syn 3.0.3", | |
| 460 | +] | |
| 461 | + | |
| 462 | +[[package]] | |
| 463 | +name = "clap_lex" | |
| 464 | +version = "1.1.0" | |
| 465 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 466 | +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" | |
| 467 | + | |
| 468 | +[[package]] | |
| 469 | +name = "colorchoice" | |
| 470 | +version = "1.0.5" | |
| 471 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 472 | +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" | |
| 473 | + | |
| 474 | +[[package]] | |
| 475 | +name = "const-oid" | |
| 476 | +version = "0.9.6" | |
| 477 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 478 | +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" | |
| 479 | + | |
| 480 | +[[package]] | |
| 481 | +name = "cookie" | |
| 482 | +version = "0.18.1" | |
| 483 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 484 | +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" | |
| 485 | +dependencies = [ | |
| 486 | + "percent-encoding", | |
| 487 | + "time", | |
| 488 | + "version_check", | |
| 489 | +] | |
| 490 | + | |
| 491 | +[[package]] | |
| 492 | +name = "core-foundation-sys" | |
| 493 | +version = "0.8.7" | |
| 494 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 495 | +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" | |
| 496 | + | |
| 497 | +[[package]] | |
| 498 | +name = "cpufeatures" | |
| 499 | +version = "0.2.17" | |
| 500 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 501 | +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" | |
| 502 | +dependencies = [ | |
| 503 | + "libc", | |
| 504 | +] | |
| 505 | + | |
| 506 | +[[package]] | |
| 507 | +name = "crc" | |
| 508 | +version = "3.4.0" | |
| 509 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 510 | +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" | |
| 511 | +dependencies = [ | |
| 512 | + "crc-catalog", | |
| 513 | +] | |
| 514 | + | |
| 515 | +[[package]] | |
| 516 | +name = "crc-catalog" | |
| 517 | +version = "2.5.0" | |
| 518 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 519 | +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" | |
| 520 | + | |
| 521 | +[[package]] | |
| 522 | +name = "crossbeam-queue" | |
| 523 | +version = "0.3.13" | |
| 524 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 525 | +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" | |
| 526 | +dependencies = [ | |
| 527 | + "crossbeam-utils", | |
| 528 | +] | |
| 529 | + | |
| 530 | +[[package]] | |
| 531 | +name = "crossbeam-utils" | |
| 532 | +version = "0.8.22" | |
| 533 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 534 | +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" | |
| 535 | + | |
| 536 | +[[package]] | |
| 537 | +name = "crypto-bigint" | |
| 538 | +version = "0.5.5" | |
| 539 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 540 | +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" | |
| 541 | +dependencies = [ | |
| 542 | + "generic-array", | |
| 543 | + "rand_core", | |
| 544 | + "subtle", | |
| 545 | + "zeroize", | |
| 546 | +] | |
| 547 | + | |
| 548 | +[[package]] | |
| 549 | +name = "crypto-common" | |
| 550 | +version = "0.1.6" | |
| 551 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 552 | +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" | |
| 553 | +dependencies = [ | |
| 554 | + "generic-array", | |
| 555 | + "typenum", | |
| 556 | +] | |
| 557 | + | |
| 558 | +[[package]] | |
| 559 | +name = "curve25519-dalek" | |
| 560 | +version = "4.1.3" | |
| 561 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 562 | +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" | |
| 563 | +dependencies = [ | |
| 564 | + "cfg-if", | |
| 565 | + "cpufeatures", | |
| 566 | + "curve25519-dalek-derive", | |
| 567 | + "digest 0.10.7", | |
| 568 | + "fiat-crypto", | |
| 569 | + "rustc_version", | |
| 570 | + "subtle", | |
| 571 | + "zeroize", | |
| 572 | +] | |
| 573 | + | |
| 574 | +[[package]] | |
| 575 | +name = "curve25519-dalek-derive" | |
| 576 | +version = "0.1.1" | |
| 577 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 578 | +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" | |
| 579 | +dependencies = [ | |
| 580 | + "proc-macro2", | |
| 581 | + "quote", | |
| 582 | + "syn 2.0.119", | |
| 583 | +] | |
| 584 | + | |
| 585 | +[[package]] | |
| 586 | +name = "der" | |
| 587 | +version = "0.6.1" | |
| 588 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 589 | +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" | |
| 590 | +dependencies = [ | |
| 591 | + "const-oid", | |
| 592 | +] | |
| 593 | + | |
| 594 | +[[package]] | |
| 595 | +name = "der" | |
| 596 | +version = "0.7.10" | |
| 597 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 598 | +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" | |
| 599 | +dependencies = [ | |
| 600 | + "const-oid", | |
| 601 | + "pem-rfc7468", | |
| 602 | + "zeroize", | |
| 603 | +] | |
| 604 | + | |
| 605 | +[[package]] | |
| 606 | +name = "deranged" | |
| 607 | +version = "0.5.8" | |
| 608 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 609 | +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" | |
| 610 | + | |
| 611 | +[[package]] | |
| 612 | +name = "digest" | |
| 613 | +version = "0.9.0" | |
| 614 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 615 | +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" | |
| 616 | +dependencies = [ | |
| 617 | + "generic-array", | |
| 618 | +] | |
| 619 | + | |
| 620 | +[[package]] | |
| 621 | +name = "digest" | |
| 622 | +version = "0.10.7" | |
| 623 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 624 | +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" | |
| 625 | +dependencies = [ | |
| 626 | + "block-buffer 0.10.4", | |
| 627 | + "const-oid", | |
| 628 | + "crypto-common", | |
| 629 | + "subtle", | |
| 630 | +] | |
| 631 | + | |
| 632 | +[[package]] | |
| 633 | +name = "displaydoc" | |
| 634 | +version = "0.2.7" | |
| 635 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 636 | +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" | |
| 637 | +dependencies = [ | |
| 638 | + "proc-macro2", | |
| 639 | + "quote", | |
| 640 | + "syn 3.0.3", | |
| 641 | +] | |
| 642 | + | |
| 643 | +[[package]] | |
| 644 | +name = "dotenvy" | |
| 645 | +version = "0.15.7" | |
| 646 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 647 | +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" | |
| 648 | + | |
| 649 | +[[package]] | |
| 650 | +name = "ecdsa" | |
| 651 | +version = "0.16.9" | |
| 652 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 653 | +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" | |
| 654 | +dependencies = [ | |
| 655 | + "der 0.7.10", | |
| 656 | + "digest 0.10.7", | |
| 657 | + "elliptic-curve", | |
| 658 | + "rfc6979", | |
| 659 | + "serdect", | |
| 660 | + "signature", | |
| 661 | + "spki 0.7.3", | |
| 662 | +] | |
| 663 | + | |
| 664 | +[[package]] | |
| 665 | +name = "ed25519" | |
| 666 | +version = "2.2.3" | |
| 667 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 668 | +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" | |
| 669 | +dependencies = [ | |
| 670 | + "pkcs8 0.10.2", | |
| 671 | + "signature", | |
| 672 | +] | |
| 673 | + | |
| 674 | +[[package]] | |
| 675 | +name = "ed25519-dalek" | |
| 676 | +version = "2.2.0" | |
| 677 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 678 | +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" | |
| 679 | +dependencies = [ | |
| 680 | + "curve25519-dalek", | |
| 681 | + "ed25519", | |
| 682 | + "rand_core", | |
| 683 | + "serde", | |
| 684 | + "sha2 0.10.9", | |
| 685 | + "subtle", | |
| 686 | + "zeroize", | |
| 687 | +] | |
| 688 | + | |
| 689 | +[[package]] | |
| 690 | +name = "either" | |
| 691 | +version = "1.17.0" | |
| 692 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 693 | +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" | |
| 694 | +dependencies = [ | |
| 695 | + "serde", | |
| 696 | +] | |
| 697 | + | |
| 698 | +[[package]] | |
| 699 | +name = "elliptic-curve" | |
| 700 | +version = "0.13.8" | |
| 701 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 702 | +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" | |
| 703 | +dependencies = [ | |
| 704 | + "base16ct", | |
| 705 | + "crypto-bigint", | |
| 706 | + "digest 0.10.7", | |
| 707 | + "ff", | |
| 708 | + "generic-array", | |
| 709 | + "group", | |
| 710 | + "pem-rfc7468", | |
| 711 | + "pkcs8 0.10.2", | |
| 712 | + "rand_core", | |
| 713 | + "sec1", | |
| 714 | + "serdect", | |
| 715 | + "subtle", | |
| 716 | + "zeroize", | |
| 717 | +] | |
| 718 | + | |
| 719 | +[[package]] | |
| 720 | +name = "equivalent" | |
| 721 | +version = "1.0.2" | |
| 722 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 723 | +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" | |
| 724 | + | |
| 725 | +[[package]] | |
| 726 | +name = "errno" | |
| 727 | +version = "0.3.14" | |
| 728 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 729 | +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" | |
| 730 | +dependencies = [ | |
| 731 | + "libc", | |
| 732 | + "windows-sys 0.61.2", | |
| 733 | +] | |
| 734 | + | |
| 735 | +[[package]] | |
| 736 | +name = "etcetera" | |
| 737 | +version = "0.8.0" | |
| 738 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 739 | +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" | |
| 740 | +dependencies = [ | |
| 741 | + "cfg-if", | |
| 742 | + "home", | |
| 743 | + "windows-sys 0.48.0", | |
| 744 | +] | |
| 745 | + | |
| 746 | +[[package]] | |
| 747 | +name = "event-listener" | |
| 748 | +version = "5.4.2" | |
| 749 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 750 | +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" | |
| 751 | +dependencies = [ | |
| 752 | + "parking", | |
| 753 | + "pin-project-lite", | |
| 754 | +] | |
| 755 | + | |
| 756 | +[[package]] | |
| 757 | +name = "ff" | |
| 758 | +version = "0.13.1" | |
| 759 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 760 | +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" | |
| 761 | +dependencies = [ | |
| 762 | + "rand_core", | |
| 763 | + "subtle", | |
| 764 | +] | |
| 765 | + | |
| 766 | +[[package]] | |
| 767 | +name = "fiat-crypto" | |
| 768 | +version = "0.2.9" | |
| 769 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 770 | +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" | |
| 771 | + | |
| 772 | +[[package]] | |
| 773 | +name = "find-msvc-tools" | |
| 774 | +version = "0.1.9" | |
| 775 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 776 | +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" | |
| 777 | + | |
| 778 | +[[package]] | |
| 779 | +name = "flume" | |
| 780 | +version = "0.11.1" | |
| 781 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 782 | +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" | |
| 783 | +dependencies = [ | |
| 784 | + "futures-core", | |
| 785 | + "futures-sink", | |
| 786 | + "spin", | |
| 787 | +] | |
| 788 | + | |
| 789 | +[[package]] | |
| 790 | +name = "foldhash" | |
| 791 | +version = "0.1.5" | |
| 792 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 793 | +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" | |
| 794 | + | |
| 795 | +[[package]] | |
| 796 | +name = "form_urlencoded" | |
| 797 | +version = "1.2.2" | |
| 798 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 799 | +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" | |
| 800 | +dependencies = [ | |
| 801 | + "percent-encoding", | |
| 802 | +] | |
| 803 | + | |
| 804 | +[[package]] | |
| 805 | +name = "futures-channel" | |
| 806 | +version = "0.3.33" | |
| 807 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 808 | +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" | |
| 809 | +dependencies = [ | |
| 810 | + "futures-core", | |
| 811 | + "futures-sink", | |
| 812 | +] | |
| 813 | + | |
| 814 | +[[package]] | |
| 815 | +name = "futures-core" | |
| 816 | +version = "0.3.33" | |
| 817 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 818 | +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" | |
| 819 | + | |
| 820 | +[[package]] | |
| 821 | +name = "futures-executor" | |
| 822 | +version = "0.3.33" | |
| 823 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 824 | +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" | |
| 825 | +dependencies = [ | |
| 826 | + "futures-core", | |
| 827 | + "futures-task", | |
| 828 | + "futures-util", | |
| 829 | +] | |
| 830 | + | |
| 831 | +[[package]] | |
| 832 | +name = "futures-intrusive" | |
| 833 | +version = "0.5.0" | |
| 834 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 835 | +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" | |
| 836 | +dependencies = [ | |
| 837 | + "futures-core", | |
| 838 | + "lock_api", | |
| 839 | + "parking_lot", | |
| 840 | +] | |
| 841 | + | |
| 842 | +[[package]] | |
| 843 | +name = "futures-io" | |
| 844 | +version = "0.3.33" | |
| 845 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 846 | +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" | |
| 847 | + | |
| 848 | +[[package]] | |
| 849 | +name = "futures-sink" | |
| 850 | +version = "0.3.33" | |
| 851 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 852 | +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" | |
| 853 | + | |
| 854 | +[[package]] | |
| 855 | +name = "futures-task" | |
| 856 | +version = "0.3.33" | |
| 857 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 858 | +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" | |
| 859 | + | |
| 860 | +[[package]] | |
| 861 | +name = "futures-util" | |
| 862 | +version = "0.3.33" | |
| 863 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 864 | +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" | |
| 865 | +dependencies = [ | |
| 866 | + "futures-core", | |
| 867 | + "futures-io", | |
| 868 | + "futures-sink", | |
| 869 | + "futures-task", | |
| 870 | + "memchr", | |
| 871 | + "pin-project-lite", | |
| 872 | + "slab", | |
| 873 | +] | |
| 874 | + | |
| 875 | +[[package]] | |
| 876 | +name = "generic-array" | |
| 877 | +version = "0.14.9" | |
| 878 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 879 | +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" | |
| 880 | +dependencies = [ | |
| 881 | + "typenum", | |
| 882 | + "version_check", | |
| 883 | + "zeroize", | |
| 884 | +] | |
| 885 | + | |
| 886 | +[[package]] | |
| 887 | +name = "getrandom" | |
| 888 | +version = "0.2.17" | |
| 889 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 890 | +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" | |
| 891 | +dependencies = [ | |
| 892 | + "cfg-if", | |
| 893 | + "libc", | |
| 894 | + "wasi", | |
| 895 | +] | |
| 896 | + | |
| 897 | +[[package]] | |
| 898 | +name = "getrandom" | |
| 899 | +version = "0.4.3" | |
| 900 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 901 | +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" | |
| 902 | +dependencies = [ | |
| 903 | + "cfg-if", | |
| 904 | + "libc", | |
| 905 | + "r-efi", | |
| 906 | +] | |
| 907 | + | |
| 908 | +[[package]] | |
| 909 | +name = "glob" | |
| 910 | +version = "0.3.4" | |
| 911 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 912 | +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" | |
| 913 | + | |
| 914 | +[[package]] | |
| 915 | +name = "group" | |
| 916 | +version = "0.13.0" | |
| 917 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 918 | +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" | |
| 919 | +dependencies = [ | |
| 920 | + "ff", | |
| 921 | + "rand_core", | |
| 922 | + "subtle", | |
| 923 | +] | |
| 924 | + | |
| 925 | +[[package]] | |
| 926 | +name = "hashbrown" | |
| 927 | +version = "0.15.5" | |
| 928 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 929 | +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" | |
| 930 | +dependencies = [ | |
| 931 | + "allocator-api2", | |
| 932 | + "equivalent", | |
| 933 | + "foldhash", | |
| 934 | +] | |
| 935 | + | |
| 936 | +[[package]] | |
| 937 | +name = "hashbrown" | |
| 938 | +version = "0.17.1" | |
| 939 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 940 | +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" | |
| 941 | + | |
| 942 | +[[package]] | |
| 943 | +name = "hashlink" | |
| 944 | +version = "0.10.0" | |
| 945 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 946 | +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" | |
| 947 | +dependencies = [ | |
| 948 | + "hashbrown 0.15.5", | |
| 949 | +] | |
| 950 | + | |
| 951 | +[[package]] | |
| 952 | +name = "heck" | |
| 953 | +version = "0.5.0" | |
| 954 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 955 | +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | |
| 956 | + | |
| 957 | +[[package]] | |
| 958 | +name = "hex" | |
| 959 | +version = "0.4.3" | |
| 960 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 961 | +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" | |
| 962 | + | |
| 963 | +[[package]] | |
| 964 | +name = "hkdf" | |
| 965 | +version = "0.12.4" | |
| 966 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 967 | +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" | |
| 968 | +dependencies = [ | |
| 969 | + "hmac", | |
| 970 | +] | |
| 971 | + | |
| 972 | +[[package]] | |
| 973 | +name = "hmac" | |
| 974 | +version = "0.12.1" | |
| 975 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 976 | +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" | |
| 977 | +dependencies = [ | |
| 978 | + "digest 0.10.7", | |
| 979 | +] | |
| 980 | + | |
| 981 | +[[package]] | |
| 982 | +name = "home" | |
| 983 | +version = "0.5.12" | |
| 984 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 985 | +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" | |
| 986 | +dependencies = [ | |
| 987 | + "windows-sys 0.61.2", | |
| 988 | +] | |
| 989 | + | |
| 990 | +[[package]] | |
| 991 | +name = "http" | |
| 992 | +version = "1.5.0" | |
| 993 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 994 | +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" | |
| 995 | +dependencies = [ | |
| 996 | + "bytes", | |
| 997 | + "itoa", | |
| 998 | +] | |
| 999 | + | |
| 1000 | +[[package]] | |
| 1001 | +name = "http-body" | |
| 1002 | +version = "1.1.0" | |
| 1003 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1004 | +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" | |
| 1005 | +dependencies = [ | |
| 1006 | + "bytes", | |
| 1007 | + "http", | |
| 1008 | +] | |
| 1009 | + | |
| 1010 | +[[package]] | |
| 1011 | +name = "http-body-util" | |
| 1012 | +version = "0.1.4" | |
| 1013 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1014 | +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" | |
| 1015 | +dependencies = [ | |
| 1016 | + "bytes", | |
| 1017 | + "futures-core", | |
| 1018 | + "http", | |
| 1019 | + "http-body", | |
| 1020 | + "pin-project-lite", | |
| 1021 | +] | |
| 1022 | + | |
| 1023 | +[[package]] | |
| 1024 | +name = "httparse" | |
| 1025 | +version = "1.10.1" | |
| 1026 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1027 | +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" | |
| 1028 | + | |
| 1029 | +[[package]] | |
| 1030 | +name = "httpdate" | |
| 1031 | +version = "1.0.3" | |
| 1032 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1033 | +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" | |
| 1034 | + | |
| 1035 | +[[package]] | |
| 1036 | +name = "hyper" | |
| 1037 | +version = "1.11.0" | |
| 1038 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1039 | +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" | |
| 1040 | +dependencies = [ | |
| 1041 | + "atomic-waker", | |
| 1042 | + "bytes", | |
| 1043 | + "futures-channel", | |
| 1044 | + "futures-core", | |
| 1045 | + "http", | |
| 1046 | + "http-body", | |
| 1047 | + "httparse", | |
| 1048 | + "httpdate", | |
| 1049 | + "itoa", | |
| 1050 | + "pin-project-lite", | |
| 1051 | + "smallvec", | |
| 1052 | + "tokio", | |
| 1053 | +] | |
| 1054 | + | |
| 1055 | +[[package]] | |
| 1056 | +name = "hyper-util" | |
| 1057 | +version = "0.1.20" | |
| 1058 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1059 | +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" | |
| 1060 | +dependencies = [ | |
| 1061 | + "bytes", | |
| 1062 | + "http", | |
| 1063 | + "http-body", | |
| 1064 | + "hyper", | |
| 1065 | + "pin-project-lite", | |
| 1066 | + "tokio", | |
| 1067 | + "tower-service", | |
| 1068 | +] | |
| 1069 | + | |
| 1070 | +[[package]] | |
| 1071 | +name = "iana-time-zone" | |
| 1072 | +version = "0.1.65" | |
| 1073 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1074 | +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" | |
| 1075 | +dependencies = [ | |
| 1076 | + "android_system_properties", | |
| 1077 | + "core-foundation-sys", | |
| 1078 | + "iana-time-zone-haiku", | |
| 1079 | + "js-sys", | |
| 1080 | + "log", | |
| 1081 | + "wasm-bindgen", | |
| 1082 | + "windows-core", | |
| 1083 | +] | |
| 1084 | + | |
| 1085 | +[[package]] | |
| 1086 | +name = "iana-time-zone-haiku" | |
| 1087 | +version = "0.1.2" | |
| 1088 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1089 | +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" | |
| 1090 | +dependencies = [ | |
| 1091 | + "cc", | |
| 1092 | +] | |
| 1093 | + | |
| 1094 | +[[package]] | |
| 1095 | +name = "icu_collections" | |
| 1096 | +version = "2.2.0" | |
| 1097 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1098 | +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" | |
| 1099 | +dependencies = [ | |
| 1100 | + "displaydoc", | |
| 1101 | + "potential_utf", | |
| 1102 | + "utf8_iter", | |
| 1103 | + "yoke", | |
| 1104 | + "zerofrom", | |
| 1105 | + "zerovec", | |
| 1106 | +] | |
| 1107 | + | |
| 1108 | +[[package]] | |
| 1109 | +name = "icu_locale_core" | |
| 1110 | +version = "2.2.0" | |
| 1111 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1112 | +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" | |
| 1113 | +dependencies = [ | |
| 1114 | + "displaydoc", | |
| 1115 | + "litemap", | |
| 1116 | + "tinystr", | |
| 1117 | + "writeable", | |
| 1118 | + "zerovec", | |
| 1119 | +] | |
| 1120 | + | |
| 1121 | +[[package]] | |
| 1122 | +name = "icu_normalizer" | |
| 1123 | +version = "2.2.0" | |
| 1124 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1125 | +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" | |
| 1126 | +dependencies = [ | |
| 1127 | + "icu_collections", | |
| 1128 | + "icu_normalizer_data", | |
| 1129 | + "icu_properties", | |
| 1130 | + "icu_provider", | |
| 1131 | + "smallvec", | |
| 1132 | + "zerovec", | |
| 1133 | +] | |
| 1134 | + | |
| 1135 | +[[package]] | |
| 1136 | +name = "icu_normalizer_data" | |
| 1137 | +version = "2.2.0" | |
| 1138 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1139 | +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" | |
| 1140 | + | |
| 1141 | +[[package]] | |
| 1142 | +name = "icu_properties" | |
| 1143 | +version = "2.2.0" | |
| 1144 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1145 | +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" | |
| 1146 | +dependencies = [ | |
| 1147 | + "icu_collections", | |
| 1148 | + "icu_locale_core", | |
| 1149 | + "icu_properties_data", | |
| 1150 | + "icu_provider", | |
| 1151 | + "zerotrie", | |
| 1152 | + "zerovec", | |
| 1153 | +] | |
| 1154 | + | |
| 1155 | +[[package]] | |
| 1156 | +name = "icu_properties_data" | |
| 1157 | +version = "2.2.0" | |
| 1158 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1159 | +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" | |
| 1160 | + | |
| 1161 | +[[package]] | |
| 1162 | +name = "icu_provider" | |
| 1163 | +version = "2.2.0" | |
| 1164 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1165 | +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" | |
| 1166 | +dependencies = [ | |
| 1167 | + "displaydoc", | |
| 1168 | + "icu_locale_core", | |
| 1169 | + "writeable", | |
| 1170 | + "yoke", | |
| 1171 | + "zerofrom", | |
| 1172 | + "zerotrie", | |
| 1173 | + "zerovec", | |
| 1174 | +] | |
| 1175 | + | |
| 1176 | +[[package]] | |
| 1177 | +name = "idna" | |
| 1178 | +version = "1.1.0" | |
| 1179 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1180 | +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" | |
| 1181 | +dependencies = [ | |
| 1182 | + "idna_adapter", | |
| 1183 | + "smallvec", | |
| 1184 | + "utf8_iter", | |
| 1185 | +] | |
| 1186 | + | |
| 1187 | +[[package]] | |
| 1188 | +name = "idna_adapter" | |
| 1189 | +version = "1.2.2" | |
| 1190 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1191 | +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" | |
| 1192 | +dependencies = [ | |
| 1193 | + "icu_normalizer", | |
| 1194 | + "icu_properties", | |
| 1195 | +] | |
| 1196 | + | |
| 1197 | +[[package]] | |
| 1198 | +name = "indexmap" | |
| 1199 | +version = "2.14.0" | |
| 1200 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1201 | +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" | |
| 1202 | +dependencies = [ | |
| 1203 | + "equivalent", | |
| 1204 | + "hashbrown 0.17.1", | |
| 1205 | +] | |
| 1206 | + | |
| 1207 | +[[package]] | |
| 1208 | +name = "is_terminal_polyfill" | |
| 1209 | +version = "1.70.2" | |
| 1210 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1211 | +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" | |
| 1212 | + | |
| 1213 | +[[package]] | |
| 1214 | +name = "itertools" | |
| 1215 | +version = "0.10.5" | |
| 1216 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1217 | +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" | |
| 1218 | +dependencies = [ | |
| 1219 | + "either", | |
| 1220 | +] | |
| 1221 | + | |
| 1222 | +[[package]] | |
| 1223 | +name = "itoa" | |
| 1224 | +version = "1.0.18" | |
| 1225 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1226 | +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" | |
| 1227 | + | |
| 1228 | +[[package]] | |
| 1229 | +name = "js-sys" | |
| 1230 | +version = "0.3.103" | |
| 1231 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1232 | +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" | |
| 1233 | +dependencies = [ | |
| 1234 | + "cfg-if", | |
| 1235 | + "futures-util", | |
| 1236 | + "wasm-bindgen", | |
| 1237 | +] | |
| 1238 | + | |
| 1239 | +[[package]] | |
| 1240 | +name = "lazy_static" | |
| 1241 | +version = "1.5.0" | |
| 1242 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1243 | +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" | |
| 1244 | +dependencies = [ | |
| 1245 | + "spin", | |
| 1246 | +] | |
| 1247 | + | |
| 1248 | +[[package]] | |
| 1249 | +name = "libc" | |
| 1250 | +version = "0.2.189" | |
| 1251 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1252 | +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" | |
| 1253 | + | |
| 1254 | +[[package]] | |
| 1255 | +name = "libm" | |
| 1256 | +version = "0.2.16" | |
| 1257 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1258 | +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" | |
| 1259 | + | |
| 1260 | +[[package]] | |
| 1261 | +name = "libredox" | |
| 1262 | +version = "0.1.19" | |
| 1263 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1264 | +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" | |
| 1265 | +dependencies = [ | |
| 1266 | + "bitflags", | |
| 1267 | + "libc", | |
| 1268 | + "plain", | |
| 1269 | + "redox_syscall 0.9.1", | |
| 1270 | +] | |
| 1271 | + | |
| 1272 | +[[package]] | |
| 1273 | +name = "libsqlite3-sys" | |
| 1274 | +version = "0.30.1" | |
| 1275 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1276 | +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" | |
| 1277 | +dependencies = [ | |
| 1278 | + "pkg-config", | |
| 1279 | + "vcpkg", | |
| 1280 | +] | |
| 1281 | + | |
| 1282 | +[[package]] | |
| 1283 | +name = "litemap" | |
| 1284 | +version = "0.8.2" | |
| 1285 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1286 | +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" | |
| 1287 | + | |
| 1288 | +[[package]] | |
| 1289 | +name = "lock_api" | |
| 1290 | +version = "0.4.14" | |
| 1291 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1292 | +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" | |
| 1293 | +dependencies = [ | |
| 1294 | + "scopeguard", | |
| 1295 | +] | |
| 1296 | + | |
| 1297 | +[[package]] | |
| 1298 | +name = "log" | |
| 1299 | +version = "0.4.33" | |
| 1300 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1301 | +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" | |
| 1302 | + | |
| 1303 | +[[package]] | |
| 1304 | +name = "matchers" | |
| 1305 | +version = "0.2.0" | |
| 1306 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1307 | +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" | |
| 1308 | +dependencies = [ | |
| 1309 | + "regex-automata", | |
| 1310 | +] | |
| 1311 | + | |
| 1312 | +[[package]] | |
| 1313 | +name = "matchit" | |
| 1314 | +version = "0.8.4" | |
| 1315 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1316 | +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" | |
| 1317 | + | |
| 1318 | +[[package]] | |
| 1319 | +name = "md-5" | |
| 1320 | +version = "0.10.6" | |
| 1321 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1322 | +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" | |
| 1323 | +dependencies = [ | |
| 1324 | + "cfg-if", | |
| 1325 | + "digest 0.10.7", | |
| 1326 | +] | |
| 1327 | + | |
| 1328 | +[[package]] | |
| 1329 | +name = "memchr" | |
| 1330 | +version = "2.8.3" | |
| 1331 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1332 | +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" | |
| 1333 | + | |
| 1334 | +[[package]] | |
| 1335 | +name = "mime" | |
| 1336 | +version = "0.3.17" | |
| 1337 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1338 | +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" | |
| 1339 | + | |
| 1340 | +[[package]] | |
| 1341 | +name = "minimal-lexical" | |
| 1342 | +version = "0.2.1" | |
| 1343 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1344 | +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" | |
| 1345 | + | |
| 1346 | +[[package]] | |
| 1347 | +name = "mio" | |
| 1348 | +version = "1.2.2" | |
| 1349 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1350 | +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" | |
| 1351 | +dependencies = [ | |
| 1352 | + "libc", | |
| 1353 | + "wasi", | |
| 1354 | + "windows-sys 0.61.2", | |
| 1355 | +] | |
| 1356 | + | |
| 1357 | +[[package]] | |
| 1358 | +name = "nom" | |
| 1359 | +version = "7.1.3" | |
| 1360 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1361 | +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" | |
| 1362 | +dependencies = [ | |
| 1363 | + "memchr", | |
| 1364 | + "minimal-lexical", | |
| 1365 | +] | |
| 1366 | + | |
| 1367 | +[[package]] | |
| 1368 | +name = "nu-ansi-term" | |
| 1369 | +version = "0.50.3" | |
| 1370 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1371 | +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" | |
| 1372 | +dependencies = [ | |
| 1373 | + "windows-sys 0.61.2", | |
| 1374 | +] | |
| 1375 | + | |
| 1376 | +[[package]] | |
| 1377 | +name = "num-bigint-dig" | |
| 1378 | +version = "0.8.6" | |
| 1379 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1380 | +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" | |
| 1381 | +dependencies = [ | |
| 1382 | + "lazy_static", | |
| 1383 | + "libm", | |
| 1384 | + "num-integer", | |
| 1385 | + "num-iter", | |
| 1386 | + "num-traits", | |
| 1387 | + "rand", | |
| 1388 | + "smallvec", | |
| 1389 | + "zeroize", | |
| 1390 | +] | |
| 1391 | + | |
| 1392 | +[[package]] | |
| 1393 | +name = "num-conv" | |
| 1394 | +version = "0.2.2" | |
| 1395 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1396 | +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" | |
| 1397 | + | |
| 1398 | +[[package]] | |
| 1399 | +name = "num-integer" | |
| 1400 | +version = "0.1.46" | |
| 1401 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1402 | +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" | |
| 1403 | +dependencies = [ | |
| 1404 | + "num-traits", | |
| 1405 | +] | |
| 1406 | + | |
| 1407 | +[[package]] | |
| 1408 | +name = "num-iter" | |
| 1409 | +version = "0.1.46" | |
| 1410 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1411 | +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" | |
| 1412 | +dependencies = [ | |
| 1413 | + "num-integer", | |
| 1414 | + "num-traits", | |
| 1415 | +] | |
| 1416 | + | |
| 1417 | +[[package]] | |
| 1418 | +name = "num-traits" | |
| 1419 | +version = "0.2.19" | |
| 1420 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1421 | +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" | |
| 1422 | +dependencies = [ | |
| 1423 | + "autocfg", | |
| 1424 | + "libm", | |
| 1425 | +] | |
| 1426 | + | |
| 1427 | +[[package]] | |
| 1428 | +name = "once_cell" | |
| 1429 | +version = "1.21.4" | |
| 1430 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1431 | +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" | |
| 1432 | + | |
| 1433 | +[[package]] | |
| 1434 | +name = "once_cell_polyfill" | |
| 1435 | +version = "1.70.2" | |
| 1436 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1437 | +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" | |
| 1438 | + | |
| 1439 | +[[package]] | |
| 1440 | +name = "opaque-debug" | |
| 1441 | +version = "0.3.1" | |
| 1442 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1443 | +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" | |
| 1444 | + | |
| 1445 | +[[package]] | |
| 1446 | +name = "p256" | |
| 1447 | +version = "0.13.2" | |
| 1448 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1449 | +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" | |
| 1450 | +dependencies = [ | |
| 1451 | + "ecdsa", | |
| 1452 | + "elliptic-curve", | |
| 1453 | + "primeorder", | |
| 1454 | + "sha2 0.10.9", | |
| 1455 | +] | |
| 1456 | + | |
| 1457 | +[[package]] | |
| 1458 | +name = "parking" | |
| 1459 | +version = "2.2.1" | |
| 1460 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1461 | +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" | |
| 1462 | + | |
| 1463 | +[[package]] | |
| 1464 | +name = "parking_lot" | |
| 1465 | +version = "0.12.5" | |
| 1466 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1467 | +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" | |
| 1468 | +dependencies = [ | |
| 1469 | + "lock_api", | |
| 1470 | + "parking_lot_core", | |
| 1471 | +] | |
| 1472 | + | |
| 1473 | +[[package]] | |
| 1474 | +name = "parking_lot_core" | |
| 1475 | +version = "0.9.12" | |
| 1476 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1477 | +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" | |
| 1478 | +dependencies = [ | |
| 1479 | + "cfg-if", | |
| 1480 | + "libc", | |
| 1481 | + "redox_syscall 0.5.18", | |
| 1482 | + "smallvec", | |
| 1483 | + "windows-link", | |
| 1484 | +] | |
| 1485 | + | |
| 1486 | +[[package]] | |
| 1487 | +name = "password-hash" | |
| 1488 | +version = "0.5.0" | |
| 1489 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1490 | +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" | |
| 1491 | +dependencies = [ | |
| 1492 | + "base64ct", | |
| 1493 | + "rand_core", | |
| 1494 | + "subtle", | |
| 1495 | +] | |
| 1496 | + | |
| 1497 | +[[package]] | |
| 1498 | +name = "pem-rfc7468" | |
| 1499 | +version = "0.7.0" | |
| 1500 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1501 | +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" | |
| 1502 | +dependencies = [ | |
| 1503 | + "base64ct", | |
| 1504 | +] | |
| 1505 | + | |
| 1506 | +[[package]] | |
| 1507 | +name = "percent-encoding" | |
| 1508 | +version = "2.3.2" | |
| 1509 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1510 | +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" | |
| 1511 | + | |
| 1512 | +[[package]] | |
| 1513 | +name = "phf" | |
| 1514 | +version = "0.12.1" | |
| 1515 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1516 | +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" | |
| 1517 | +dependencies = [ | |
| 1518 | + "phf_shared", | |
| 1519 | +] | |
| 1520 | + | |
| 1521 | +[[package]] | |
| 1522 | +name = "phf_shared" | |
| 1523 | +version = "0.12.1" | |
| 1524 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1525 | +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" | |
| 1526 | +dependencies = [ | |
| 1527 | + "siphasher", | |
| 1528 | +] | |
| 1529 | + | |
| 1530 | +[[package]] | |
| 1531 | +name = "pin-project-lite" | |
| 1532 | +version = "0.2.17" | |
| 1533 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1534 | +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" | |
| 1535 | + | |
| 1536 | +[[package]] | |
| 1537 | +name = "pkcs1" | |
| 1538 | +version = "0.7.5" | |
| 1539 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1540 | +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" | |
| 1541 | +dependencies = [ | |
| 1542 | + "der 0.7.10", | |
| 1543 | + "pkcs8 0.10.2", | |
| 1544 | + "spki 0.7.3", | |
| 1545 | +] | |
| 1546 | + | |
| 1547 | +[[package]] | |
| 1548 | +name = "pkcs8" | |
| 1549 | +version = "0.9.0" | |
| 1550 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1551 | +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" | |
| 1552 | +dependencies = [ | |
| 1553 | + "der 0.6.1", | |
| 1554 | + "spki 0.6.0", | |
| 1555 | +] | |
| 1556 | + | |
| 1557 | +[[package]] | |
| 1558 | +name = "pkcs8" | |
| 1559 | +version = "0.10.2" | |
| 1560 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1561 | +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" | |
| 1562 | +dependencies = [ | |
| 1563 | + "der 0.7.10", | |
| 1564 | + "spki 0.7.3", | |
| 1565 | +] | |
| 1566 | + | |
| 1567 | +[[package]] | |
| 1568 | +name = "pkg-config" | |
| 1569 | +version = "0.3.33" | |
| 1570 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1571 | +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" | |
| 1572 | + | |
| 1573 | +[[package]] | |
| 1574 | +name = "plain" | |
| 1575 | +version = "0.2.3" | |
| 1576 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1577 | +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" | |
| 1578 | + | |
| 1579 | +[[package]] | |
| 1580 | +name = "potential_utf" | |
| 1581 | +version = "0.1.5" | |
| 1582 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1583 | +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" | |
| 1584 | +dependencies = [ | |
| 1585 | + "zerovec", | |
| 1586 | +] | |
| 1587 | + | |
| 1588 | +[[package]] | |
| 1589 | +name = "powerfmt" | |
| 1590 | +version = "0.2.0" | |
| 1591 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1592 | +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" | |
| 1593 | + | |
| 1594 | +[[package]] | |
| 1595 | +name = "ppv-lite86" | |
| 1596 | +version = "0.2.21" | |
| 1597 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1598 | +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" | |
| 1599 | +dependencies = [ | |
| 1600 | + "zerocopy", | |
| 1601 | +] | |
| 1602 | + | |
| 1603 | +[[package]] | |
| 1604 | +name = "primeorder" | |
| 1605 | +version = "0.13.6" | |
| 1606 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1607 | +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" | |
| 1608 | +dependencies = [ | |
| 1609 | + "elliptic-curve", | |
| 1610 | +] | |
| 1611 | + | |
| 1612 | +[[package]] | |
| 1613 | +name = "proc-macro-error-attr2" | |
| 1614 | +version = "2.0.0" | |
| 1615 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1616 | +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" | |
| 1617 | +dependencies = [ | |
| 1618 | + "proc-macro2", | |
| 1619 | + "quote", | |
| 1620 | +] | |
| 1621 | + | |
| 1622 | +[[package]] | |
| 1623 | +name = "proc-macro-error2" | |
| 1624 | +version = "2.0.1" | |
| 1625 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1626 | +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" | |
| 1627 | +dependencies = [ | |
| 1628 | + "proc-macro-error-attr2", | |
| 1629 | + "proc-macro2", | |
| 1630 | + "quote", | |
| 1631 | + "syn 2.0.119", | |
| 1632 | +] | |
| 1633 | + | |
| 1634 | +[[package]] | |
| 1635 | +name = "proc-macro2" | |
| 1636 | +version = "1.0.107" | |
| 1637 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1638 | +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" | |
| 1639 | +dependencies = [ | |
| 1640 | + "unicode-ident", | |
| 1641 | +] | |
| 1642 | + | |
| 1643 | +[[package]] | |
| 1644 | +name = "prost" | |
| 1645 | +version = "0.10.4" | |
| 1646 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1647 | +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" | |
| 1648 | +dependencies = [ | |
| 1649 | + "bytes", | |
| 1650 | + "prost-derive", | |
| 1651 | +] | |
| 1652 | + | |
| 1653 | +[[package]] | |
| 1654 | +name = "prost-derive" | |
| 1655 | +version = "0.10.1" | |
| 1656 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1657 | +checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" | |
| 1658 | +dependencies = [ | |
| 1659 | + "anyhow", | |
| 1660 | + "itertools", | |
| 1661 | + "proc-macro2", | |
| 1662 | + "quote", | |
| 1663 | + "syn 1.0.109", | |
| 1664 | +] | |
| 1665 | + | |
| 1666 | +[[package]] | |
| 1667 | +name = "prost-types" | |
| 1668 | +version = "0.10.1" | |
| 1669 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1670 | +checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" | |
| 1671 | +dependencies = [ | |
| 1672 | + "bytes", | |
| 1673 | + "prost", | |
| 1674 | +] | |
| 1675 | + | |
| 1676 | +[[package]] | |
| 1677 | +name = "quote" | |
| 1678 | +version = "1.0.47" | |
| 1679 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1680 | +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" | |
| 1681 | +dependencies = [ | |
| 1682 | + "proc-macro2", | |
| 1683 | +] | |
| 1684 | + | |
| 1685 | +[[package]] | |
| 1686 | +name = "r-efi" | |
| 1687 | +version = "6.0.0" | |
| 1688 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1689 | +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" | |
| 1690 | + | |
| 1691 | +[[package]] | |
| 1692 | +name = "rand" | |
| 1693 | +version = "0.8.7" | |
| 1694 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1695 | +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" | |
| 1696 | +dependencies = [ | |
| 1697 | + "libc", | |
| 1698 | + "rand_chacha", | |
| 1699 | + "rand_core", | |
| 1700 | +] | |
| 1701 | + | |
| 1702 | +[[package]] | |
| 1703 | +name = "rand_chacha" | |
| 1704 | +version = "0.3.1" | |
| 1705 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1706 | +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" | |
| 1707 | +dependencies = [ | |
| 1708 | + "ppv-lite86", | |
| 1709 | + "rand_core", | |
| 1710 | +] | |
| 1711 | + | |
| 1712 | +[[package]] | |
| 1713 | +name = "rand_core" | |
| 1714 | +version = "0.6.4" | |
| 1715 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1716 | +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" | |
| 1717 | +dependencies = [ | |
| 1718 | + "getrandom 0.2.17", | |
| 1719 | +] | |
| 1720 | + | |
| 1721 | +[[package]] | |
| 1722 | +name = "redox_syscall" | |
| 1723 | +version = "0.5.18" | |
| 1724 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1725 | +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" | |
| 1726 | +dependencies = [ | |
| 1727 | + "bitflags", | |
| 1728 | +] | |
| 1729 | + | |
| 1730 | +[[package]] | |
| 1731 | +name = "redox_syscall" | |
| 1732 | +version = "0.9.1" | |
| 1733 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1734 | +checksum = "07507be7b4a5f9f26eeb41eeaebb1f5a7ff29dfb29739facc21d35bf8b11c21e" | |
| 1735 | +dependencies = [ | |
| 1736 | + "bitflags", | |
| 1737 | +] | |
| 1738 | + | |
| 1739 | +[[package]] | |
| 1740 | +name = "regex" | |
| 1741 | +version = "1.13.1" | |
| 1742 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1743 | +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" | |
| 1744 | +dependencies = [ | |
| 1745 | + "aho-corasick", | |
| 1746 | + "memchr", | |
| 1747 | + "regex-automata", | |
| 1748 | + "regex-syntax", | |
| 1749 | +] | |
| 1750 | + | |
| 1751 | +[[package]] | |
| 1752 | +name = "regex-automata" | |
| 1753 | +version = "0.4.18" | |
| 1754 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1755 | +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" | |
| 1756 | +dependencies = [ | |
| 1757 | + "aho-corasick", | |
| 1758 | + "memchr", | |
| 1759 | + "regex-syntax", | |
| 1760 | +] | |
| 1761 | + | |
| 1762 | +[[package]] | |
| 1763 | +name = "regex-syntax" | |
| 1764 | +version = "0.8.11" | |
| 1765 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1766 | +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" | |
| 1767 | + | |
| 1768 | +[[package]] | |
| 1769 | +name = "rfc6979" | |
| 1770 | +version = "0.4.0" | |
| 1771 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1772 | +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" | |
| 1773 | +dependencies = [ | |
| 1774 | + "hmac", | |
| 1775 | + "subtle", | |
| 1776 | +] | |
| 1777 | + | |
| 1778 | +[[package]] | |
| 1779 | +name = "ring" | |
| 1780 | +version = "0.17.14" | |
| 1781 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1782 | +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" | |
| 1783 | +dependencies = [ | |
| 1784 | + "cc", | |
| 1785 | + "cfg-if", | |
| 1786 | + "getrandom 0.2.17", | |
| 1787 | + "libc", | |
| 1788 | + "untrusted", | |
| 1789 | + "windows-sys 0.52.0", | |
| 1790 | +] | |
| 1791 | + | |
| 1792 | +[[package]] | |
| 1793 | +name = "rpassword" | |
| 1794 | +version = "7.5.4" | |
| 1795 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1796 | +checksum = "2da316a15f47e3d053de9cb2c439650bd8fa4aaeb9365f2e5f27f492ff73c196" | |
| 1797 | +dependencies = [ | |
| 1798 | + "libc", | |
| 1799 | + "rtoolbox", | |
| 1800 | + "windows-sys 0.61.2", | |
| 1801 | +] | |
| 1802 | + | |
| 1803 | +[[package]] | |
| 1804 | +name = "rsa" | |
| 1805 | +version = "0.9.10" | |
| 1806 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1807 | +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" | |
| 1808 | +dependencies = [ | |
| 1809 | + "const-oid", | |
| 1810 | + "digest 0.10.7", | |
| 1811 | + "num-bigint-dig", | |
| 1812 | + "num-integer", | |
| 1813 | + "num-traits", | |
| 1814 | + "pkcs1", | |
| 1815 | + "pkcs8 0.10.2", | |
| 1816 | + "rand_core", | |
| 1817 | + "signature", | |
| 1818 | + "spki 0.7.3", | |
| 1819 | + "subtle", | |
| 1820 | + "zeroize", | |
| 1821 | +] | |
| 1822 | + | |
| 1823 | +[[package]] | |
| 1824 | +name = "rtoolbox" | |
| 1825 | +version = "0.0.5" | |
| 1826 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1827 | +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" | |
| 1828 | +dependencies = [ | |
| 1829 | + "libc", | |
| 1830 | + "windows-sys 0.59.0", | |
| 1831 | +] | |
| 1832 | + | |
| 1833 | +[[package]] | |
| 1834 | +name = "rustc-hash" | |
| 1835 | +version = "2.1.3" | |
| 1836 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1837 | +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" | |
| 1838 | + | |
| 1839 | +[[package]] | |
| 1840 | +name = "rustc_version" | |
| 1841 | +version = "0.4.1" | |
| 1842 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1843 | +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" | |
| 1844 | +dependencies = [ | |
| 1845 | + "semver", | |
| 1846 | +] | |
| 1847 | + | |
| 1848 | +[[package]] | |
| 1849 | +name = "rustls" | |
| 1850 | +version = "0.23.43" | |
| 1851 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1852 | +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" | |
| 1853 | +dependencies = [ | |
| 1854 | + "once_cell", | |
| 1855 | + "ring", | |
| 1856 | + "rustls-pki-types", | |
| 1857 | + "rustls-webpki", | |
| 1858 | + "subtle", | |
| 1859 | + "zeroize", | |
| 1860 | +] | |
| 1861 | + | |
| 1862 | +[[package]] | |
| 1863 | +name = "rustls-pki-types" | |
| 1864 | +version = "1.15.1" | |
| 1865 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1866 | +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" | |
| 1867 | +dependencies = [ | |
| 1868 | + "zeroize", | |
| 1869 | +] | |
| 1870 | + | |
| 1871 | +[[package]] | |
| 1872 | +name = "rustls-webpki" | |
| 1873 | +version = "0.103.13" | |
| 1874 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1875 | +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" | |
| 1876 | +dependencies = [ | |
| 1877 | + "ring", | |
| 1878 | + "rustls-pki-types", | |
| 1879 | + "untrusted", | |
| 1880 | +] | |
| 1881 | + | |
| 1882 | +[[package]] | |
| 1883 | +name = "rustversion" | |
| 1884 | +version = "1.0.23" | |
| 1885 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1886 | +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" | |
| 1887 | + | |
| 1888 | +[[package]] | |
| 1889 | +name = "ryu" | |
| 1890 | +version = "1.0.23" | |
| 1891 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1892 | +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" | |
| 1893 | + | |
| 1894 | +[[package]] | |
| 1895 | +name = "scopeguard" | |
| 1896 | +version = "1.2.0" | |
| 1897 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1898 | +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" | |
| 1899 | + | |
| 1900 | +[[package]] | |
| 1901 | +name = "sec1" | |
| 1902 | +version = "0.7.3" | |
| 1903 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1904 | +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" | |
| 1905 | +dependencies = [ | |
| 1906 | + "base16ct", | |
| 1907 | + "der 0.7.10", | |
| 1908 | + "generic-array", | |
| 1909 | + "pkcs8 0.10.2", | |
| 1910 | + "serdect", | |
| 1911 | + "subtle", | |
| 1912 | + "zeroize", | |
| 1913 | +] | |
| 1914 | + | |
| 1915 | +[[package]] | |
| 1916 | +name = "semver" | |
| 1917 | +version = "1.0.28" | |
| 1918 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1919 | +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" | |
| 1920 | + | |
| 1921 | +[[package]] | |
| 1922 | +name = "serde" | |
| 1923 | +version = "1.0.229" | |
| 1924 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1925 | +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" | |
| 1926 | +dependencies = [ | |
| 1927 | + "serde_core", | |
| 1928 | + "serde_derive", | |
| 1929 | +] | |
| 1930 | + | |
| 1931 | +[[package]] | |
| 1932 | +name = "serde_core" | |
| 1933 | +version = "1.0.229" | |
| 1934 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1935 | +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" | |
| 1936 | +dependencies = [ | |
| 1937 | + "serde_derive", | |
| 1938 | +] | |
| 1939 | + | |
| 1940 | +[[package]] | |
| 1941 | +name = "serde_derive" | |
| 1942 | +version = "1.0.229" | |
| 1943 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1944 | +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" | |
| 1945 | +dependencies = [ | |
| 1946 | + "proc-macro2", | |
| 1947 | + "quote", | |
| 1948 | + "syn 3.0.3", | |
| 1949 | +] | |
| 1950 | + | |
| 1951 | +[[package]] | |
| 1952 | +name = "serde_json" | |
| 1953 | +version = "1.0.151" | |
| 1954 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1955 | +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" | |
| 1956 | +dependencies = [ | |
| 1957 | + "itoa", | |
| 1958 | + "memchr", | |
| 1959 | + "serde", | |
| 1960 | + "serde_core", | |
| 1961 | + "zmij", | |
| 1962 | +] | |
| 1963 | + | |
| 1964 | +[[package]] | |
| 1965 | +name = "serde_path_to_error" | |
| 1966 | +version = "0.1.20" | |
| 1967 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1968 | +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" | |
| 1969 | +dependencies = [ | |
| 1970 | + "itoa", | |
| 1971 | + "serde", | |
| 1972 | + "serde_core", | |
| 1973 | +] | |
| 1974 | + | |
| 1975 | +[[package]] | |
| 1976 | +name = "serde_urlencoded" | |
| 1977 | +version = "0.7.1" | |
| 1978 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1979 | +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" | |
| 1980 | +dependencies = [ | |
| 1981 | + "form_urlencoded", | |
| 1982 | + "itoa", | |
| 1983 | + "ryu", | |
| 1984 | + "serde", | |
| 1985 | +] | |
| 1986 | + | |
| 1987 | +[[package]] | |
| 1988 | +name = "serdect" | |
| 1989 | +version = "0.2.0" | |
| 1990 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1991 | +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" | |
| 1992 | +dependencies = [ | |
| 1993 | + "base16ct", | |
| 1994 | + "serde", | |
| 1995 | +] | |
| 1996 | + | |
| 1997 | +[[package]] | |
| 1998 | +name = "sha1" | |
| 1999 | +version = "0.10.7" | |
| 2000 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2001 | +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" | |
| 2002 | +dependencies = [ | |
| 2003 | + "cfg-if", | |
| 2004 | + "cpufeatures", | |
| 2005 | + "digest 0.10.7", | |
| 2006 | +] | |
| 2007 | + | |
| 2008 | +[[package]] | |
| 2009 | +name = "sha2" | |
| 2010 | +version = "0.9.9" | |
| 2011 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2012 | +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" | |
| 2013 | +dependencies = [ | |
| 2014 | + "block-buffer 0.9.0", | |
| 2015 | + "cfg-if", | |
| 2016 | + "cpufeatures", | |
| 2017 | + "digest 0.9.0", | |
| 2018 | + "opaque-debug", | |
| 2019 | +] | |
| 2020 | + | |
| 2021 | +[[package]] | |
| 2022 | +name = "sha2" | |
| 2023 | +version = "0.10.9" | |
| 2024 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2025 | +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" | |
| 2026 | +dependencies = [ | |
| 2027 | + "cfg-if", | |
| 2028 | + "cpufeatures", | |
| 2029 | + "digest 0.10.7", | |
| 2030 | +] | |
| 2031 | + | |
| 2032 | +[[package]] | |
| 2033 | +name = "sharded-slab" | |
| 2034 | +version = "0.1.7" | |
| 2035 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2036 | +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" | |
| 2037 | +dependencies = [ | |
| 2038 | + "lazy_static", | |
| 2039 | +] | |
| 2040 | + | |
| 2041 | +[[package]] | |
| 2042 | +name = "shlex" | |
| 2043 | +version = "2.0.1" | |
| 2044 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2045 | +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" | |
| 2046 | + | |
| 2047 | +[[package]] | |
| 2048 | +name = "signal-hook-registry" | |
| 2049 | +version = "1.4.8" | |
| 2050 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2051 | +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" | |
| 2052 | +dependencies = [ | |
| 2053 | + "errno", | |
| 2054 | + "libc", | |
| 2055 | +] | |
| 2056 | + | |
| 2057 | +[[package]] | |
| 2058 | +name = "signature" | |
| 2059 | +version = "2.2.0" | |
| 2060 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2061 | +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" | |
| 2062 | +dependencies = [ | |
| 2063 | + "digest 0.10.7", | |
| 2064 | + "rand_core", | |
| 2065 | +] | |
| 2066 | + | |
| 2067 | +[[package]] | |
| 2068 | +name = "siphasher" | |
| 2069 | +version = "1.0.3" | |
| 2070 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2071 | +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" | |
| 2072 | + | |
| 2073 | +[[package]] | |
| 2074 | +name = "slab" | |
| 2075 | +version = "0.4.12" | |
| 2076 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2077 | +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" | |
| 2078 | + | |
| 2079 | +[[package]] | |
| 2080 | +name = "smallvec" | |
| 2081 | +version = "1.15.2" | |
| 2082 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2083 | +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" | |
| 2084 | +dependencies = [ | |
| 2085 | + "serde", | |
| 2086 | +] | |
| 2087 | + | |
| 2088 | +[[package]] | |
| 2089 | +name = "socket2" | |
| 2090 | +version = "0.6.5" | |
| 2091 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2092 | +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" | |
| 2093 | +dependencies = [ | |
| 2094 | + "libc", | |
| 2095 | + "windows-sys 0.61.2", | |
| 2096 | +] | |
| 2097 | + | |
| 2098 | +[[package]] | |
| 2099 | +name = "spin" | |
| 2100 | +version = "0.9.9" | |
| 2101 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2102 | +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" | |
| 2103 | +dependencies = [ | |
| 2104 | + "lock_api", | |
| 2105 | +] | |
| 2106 | + | |
| 2107 | +[[package]] | |
| 2108 | +name = "spki" | |
| 2109 | +version = "0.6.0" | |
| 2110 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2111 | +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" | |
| 2112 | +dependencies = [ | |
| 2113 | + "der 0.6.1", | |
| 2114 | +] | |
| 2115 | + | |
| 2116 | +[[package]] | |
| 2117 | +name = "spki" | |
| 2118 | +version = "0.7.3" | |
| 2119 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2120 | +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" | |
| 2121 | +dependencies = [ | |
| 2122 | + "base64ct", | |
| 2123 | + "der 0.7.10", | |
| 2124 | +] | |
| 2125 | + | |
| 2126 | +[[package]] | |
| 2127 | +name = "sqlx" | |
| 2128 | +version = "0.8.6" | |
| 2129 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2130 | +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" | |
| 2131 | +dependencies = [ | |
| 2132 | + "sqlx-core", | |
| 2133 | + "sqlx-macros", | |
| 2134 | + "sqlx-mysql", | |
| 2135 | + "sqlx-postgres", | |
| 2136 | + "sqlx-sqlite", | |
| 2137 | +] | |
| 2138 | + | |
| 2139 | +[[package]] | |
| 2140 | +name = "sqlx-core" | |
| 2141 | +version = "0.8.6" | |
| 2142 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2143 | +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" | |
| 2144 | +dependencies = [ | |
| 2145 | + "base64 0.22.1", | |
| 2146 | + "bytes", | |
| 2147 | + "chrono", | |
| 2148 | + "crc", | |
| 2149 | + "crossbeam-queue", | |
| 2150 | + "either", | |
| 2151 | + "event-listener", | |
| 2152 | + "futures-core", | |
| 2153 | + "futures-intrusive", | |
| 2154 | + "futures-io", | |
| 2155 | + "futures-util", | |
| 2156 | + "hashbrown 0.15.5", | |
| 2157 | + "hashlink", | |
| 2158 | + "indexmap", | |
| 2159 | + "log", | |
| 2160 | + "memchr", | |
| 2161 | + "once_cell", | |
| 2162 | + "percent-encoding", | |
| 2163 | + "rustls", | |
| 2164 | + "serde", | |
| 2165 | + "serde_json", | |
| 2166 | + "sha2 0.10.9", | |
| 2167 | + "smallvec", | |
| 2168 | + "thiserror 2.0.19", | |
| 2169 | + "tokio", | |
| 2170 | + "tokio-stream", | |
| 2171 | + "tracing", | |
| 2172 | + "url", | |
| 2173 | + "uuid", | |
| 2174 | + "webpki-roots 0.26.11", | |
| 2175 | +] | |
| 2176 | + | |
| 2177 | +[[package]] | |
| 2178 | +name = "sqlx-macros" | |
| 2179 | +version = "0.8.6" | |
| 2180 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2181 | +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" | |
| 2182 | +dependencies = [ | |
| 2183 | + "proc-macro2", | |
| 2184 | + "quote", | |
| 2185 | + "sqlx-core", | |
| 2186 | + "sqlx-macros-core", | |
| 2187 | + "syn 2.0.119", | |
| 2188 | +] | |
| 2189 | + | |
| 2190 | +[[package]] | |
| 2191 | +name = "sqlx-macros-core" | |
| 2192 | +version = "0.8.6" | |
| 2193 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2194 | +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" | |
| 2195 | +dependencies = [ | |
| 2196 | + "dotenvy", | |
| 2197 | + "either", | |
| 2198 | + "heck", | |
| 2199 | + "hex", | |
| 2200 | + "once_cell", | |
| 2201 | + "proc-macro2", | |
| 2202 | + "quote", | |
| 2203 | + "serde", | |
| 2204 | + "serde_json", | |
| 2205 | + "sha2 0.10.9", | |
| 2206 | + "sqlx-core", | |
| 2207 | + "sqlx-mysql", | |
| 2208 | + "sqlx-postgres", | |
| 2209 | + "sqlx-sqlite", | |
| 2210 | + "syn 2.0.119", | |
| 2211 | + "tokio", | |
| 2212 | + "url", | |
| 2213 | +] | |
| 2214 | + | |
| 2215 | +[[package]] | |
| 2216 | +name = "sqlx-mysql" | |
| 2217 | +version = "0.8.6" | |
| 2218 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2219 | +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" | |
| 2220 | +dependencies = [ | |
| 2221 | + "atoi", | |
| 2222 | + "base64 0.22.1", | |
| 2223 | + "bitflags", | |
| 2224 | + "byteorder", | |
| 2225 | + "bytes", | |
| 2226 | + "chrono", | |
| 2227 | + "crc", | |
| 2228 | + "digest 0.10.7", | |
| 2229 | + "dotenvy", | |
| 2230 | + "either", | |
| 2231 | + "futures-channel", | |
| 2232 | + "futures-core", | |
| 2233 | + "futures-io", | |
| 2234 | + "futures-util", | |
| 2235 | + "generic-array", | |
| 2236 | + "hex", | |
| 2237 | + "hkdf", | |
| 2238 | + "hmac", | |
| 2239 | + "itoa", | |
| 2240 | + "log", | |
| 2241 | + "md-5", | |
| 2242 | + "memchr", | |
| 2243 | + "once_cell", | |
| 2244 | + "percent-encoding", | |
| 2245 | + "rand", | |
| 2246 | + "rsa", | |
| 2247 | + "serde", | |
| 2248 | + "sha1", | |
| 2249 | + "sha2 0.10.9", | |
| 2250 | + "smallvec", | |
| 2251 | + "sqlx-core", | |
| 2252 | + "stringprep", | |
| 2253 | + "thiserror 2.0.19", | |
| 2254 | + "tracing", | |
| 2255 | + "uuid", | |
| 2256 | + "whoami", | |
| 2257 | +] | |
| 2258 | + | |
| 2259 | +[[package]] | |
| 2260 | +name = "sqlx-postgres" | |
| 2261 | +version = "0.8.6" | |
| 2262 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2263 | +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" | |
| 2264 | +dependencies = [ | |
| 2265 | + "atoi", | |
| 2266 | + "base64 0.22.1", | |
| 2267 | + "bitflags", | |
| 2268 | + "byteorder", | |
| 2269 | + "chrono", | |
| 2270 | + "crc", | |
| 2271 | + "dotenvy", | |
| 2272 | + "etcetera", | |
| 2273 | + "futures-channel", | |
| 2274 | + "futures-core", | |
| 2275 | + "futures-util", | |
| 2276 | + "hex", | |
| 2277 | + "hkdf", | |
| 2278 | + "hmac", | |
| 2279 | + "home", | |
| 2280 | + "itoa", | |
| 2281 | + "log", | |
| 2282 | + "md-5", | |
| 2283 | + "memchr", | |
| 2284 | + "once_cell", | |
| 2285 | + "rand", | |
| 2286 | + "serde", | |
| 2287 | + "serde_json", | |
| 2288 | + "sha2 0.10.9", | |
| 2289 | + "smallvec", | |
| 2290 | + "sqlx-core", | |
| 2291 | + "stringprep", | |
| 2292 | + "thiserror 2.0.19", | |
| 2293 | + "tracing", | |
| 2294 | + "uuid", | |
| 2295 | + "whoami", | |
| 2296 | +] | |
| 2297 | + | |
| 2298 | +[[package]] | |
| 2299 | +name = "sqlx-sqlite" | |
| 2300 | +version = "0.8.6" | |
| 2301 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2302 | +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" | |
| 2303 | +dependencies = [ | |
| 2304 | + "atoi", | |
| 2305 | + "chrono", | |
| 2306 | + "flume", | |
| 2307 | + "futures-channel", | |
| 2308 | + "futures-core", | |
| 2309 | + "futures-executor", | |
| 2310 | + "futures-intrusive", | |
| 2311 | + "futures-util", | |
| 2312 | + "libsqlite3-sys", | |
| 2313 | + "log", | |
| 2314 | + "percent-encoding", | |
| 2315 | + "serde", | |
| 2316 | + "serde_urlencoded", | |
| 2317 | + "sqlx-core", | |
| 2318 | + "thiserror 2.0.19", | |
| 2319 | + "tracing", | |
| 2320 | + "url", | |
| 2321 | + "uuid", | |
| 2322 | +] | |
| 2323 | + | |
| 2324 | +[[package]] | |
| 2325 | +name = "stable_deref_trait" | |
| 2326 | +version = "1.2.1" | |
| 2327 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2328 | +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" | |
| 2329 | + | |
| 2330 | +[[package]] | |
| 2331 | +name = "stringprep" | |
| 2332 | +version = "0.1.5" | |
| 2333 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2334 | +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" | |
| 2335 | +dependencies = [ | |
| 2336 | + "unicode-bidi", | |
| 2337 | + "unicode-normalization", | |
| 2338 | + "unicode-properties", | |
| 2339 | +] | |
| 2340 | + | |
| 2341 | +[[package]] | |
| 2342 | +name = "strsim" | |
| 2343 | +version = "0.11.1" | |
| 2344 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2345 | +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" | |
| 2346 | + | |
| 2347 | +[[package]] | |
| 2348 | +name = "subtle" | |
| 2349 | +version = "2.6.1" | |
| 2350 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2351 | +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" | |
| 2352 | + | |
| 2353 | +[[package]] | |
| 2354 | +name = "syn" | |
| 2355 | +version = "1.0.109" | |
| 2356 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2357 | +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" | |
| 2358 | +dependencies = [ | |
| 2359 | + "proc-macro2", | |
| 2360 | + "quote", | |
| 2361 | + "unicode-ident", | |
| 2362 | +] | |
| 2363 | + | |
| 2364 | +[[package]] | |
| 2365 | +name = "syn" | |
| 2366 | +version = "2.0.119" | |
| 2367 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2368 | +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" | |
| 2369 | +dependencies = [ | |
| 2370 | + "proc-macro2", | |
| 2371 | + "quote", | |
| 2372 | + "unicode-ident", | |
| 2373 | +] | |
| 2374 | + | |
| 2375 | +[[package]] | |
| 2376 | +name = "syn" | |
| 2377 | +version = "3.0.3" | |
| 2378 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2379 | +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" | |
| 2380 | +dependencies = [ | |
| 2381 | + "proc-macro2", | |
| 2382 | + "quote", | |
| 2383 | + "unicode-ident", | |
| 2384 | +] | |
| 2385 | + | |
| 2386 | +[[package]] | |
| 2387 | +name = "sync_wrapper" | |
| 2388 | +version = "1.0.2" | |
| 2389 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2390 | +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" | |
| 2391 | + | |
| 2392 | +[[package]] | |
| 2393 | +name = "synstructure" | |
| 2394 | +version = "0.13.2" | |
| 2395 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2396 | +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" | |
| 2397 | +dependencies = [ | |
| 2398 | + "proc-macro2", | |
| 2399 | + "quote", | |
| 2400 | + "syn 2.0.119", | |
| 2401 | +] | |
| 2402 | + | |
| 2403 | +[[package]] | |
| 2404 | +name = "thiserror" | |
| 2405 | +version = "1.0.69" | |
| 2406 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2407 | +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" | |
| 2408 | +dependencies = [ | |
| 2409 | + "thiserror-impl 1.0.69", | |
| 2410 | +] | |
| 2411 | + | |
| 2412 | +[[package]] | |
| 2413 | +name = "thiserror" | |
| 2414 | +version = "2.0.19" | |
| 2415 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2416 | +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" | |
| 2417 | +dependencies = [ | |
| 2418 | + "thiserror-impl 2.0.19", | |
| 2419 | +] | |
| 2420 | + | |
| 2421 | +[[package]] | |
| 2422 | +name = "thiserror-impl" | |
| 2423 | +version = "1.0.69" | |
| 2424 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2425 | +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" | |
| 2426 | +dependencies = [ | |
| 2427 | + "proc-macro2", | |
| 2428 | + "quote", | |
| 2429 | + "syn 2.0.119", | |
| 2430 | +] | |
| 2431 | + | |
| 2432 | +[[package]] | |
| 2433 | +name = "thiserror-impl" | |
| 2434 | +version = "2.0.19" | |
| 2435 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2436 | +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" | |
| 2437 | +dependencies = [ | |
| 2438 | + "proc-macro2", | |
| 2439 | + "quote", | |
| 2440 | + "syn 3.0.3", | |
| 2441 | +] | |
| 2442 | + | |
| 2443 | +[[package]] | |
| 2444 | +name = "thread_local" | |
| 2445 | +version = "1.1.10" | |
| 2446 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2447 | +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" | |
| 2448 | +dependencies = [ | |
| 2449 | + "cfg-if", | |
| 2450 | +] | |
| 2451 | + | |
| 2452 | +[[package]] | |
| 2453 | +name = "time" | |
| 2454 | +version = "0.3.55" | |
| 2455 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2456 | +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" | |
| 2457 | +dependencies = [ | |
| 2458 | + "deranged", | |
| 2459 | + "num-conv", | |
| 2460 | + "powerfmt", | |
| 2461 | + "serde_core", | |
| 2462 | + "time-core", | |
| 2463 | + "time-macros", | |
| 2464 | +] | |
| 2465 | + | |
| 2466 | +[[package]] | |
| 2467 | +name = "time-core" | |
| 2468 | +version = "0.1.9" | |
| 2469 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2470 | +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" | |
| 2471 | + | |
| 2472 | +[[package]] | |
| 2473 | +name = "time-macros" | |
| 2474 | +version = "0.2.32" | |
| 2475 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2476 | +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" | |
| 2477 | +dependencies = [ | |
| 2478 | + "num-conv", | |
| 2479 | + "time-core", | |
| 2480 | +] | |
| 2481 | + | |
| 2482 | +[[package]] | |
| 2483 | +name = "time-time" | |
| 2484 | +version = "0.1.0" | |
| 2485 | +dependencies = [ | |
| 2486 | + "anyhow", | |
| 2487 | + "argon2", | |
| 2488 | + "askama", | |
| 2489 | + "axum", | |
| 2490 | + "axum-extra", | |
| 2491 | + "biscuit-auth", | |
| 2492 | + "chrono", | |
| 2493 | + "chrono-tz", | |
| 2494 | + "clap", | |
| 2495 | + "hex", | |
| 2496 | + "hmac", | |
| 2497 | + "rpassword", | |
| 2498 | + "serde", | |
| 2499 | + "sha2 0.10.9", | |
| 2500 | + "sqlx", | |
| 2501 | + "subtle", | |
| 2502 | + "thiserror 2.0.19", | |
| 2503 | + "time", | |
| 2504 | + "tokio", | |
| 2505 | + "tracing", | |
| 2506 | + "tracing-subscriber", | |
| 2507 | + "uuid", | |
| 2508 | +] | |
| 2509 | + | |
| 2510 | +[[package]] | |
| 2511 | +name = "tinystr" | |
| 2512 | +version = "0.8.3" | |
| 2513 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2514 | +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" | |
| 2515 | +dependencies = [ | |
| 2516 | + "displaydoc", | |
| 2517 | + "zerovec", | |
| 2518 | +] | |
| 2519 | + | |
| 2520 | +[[package]] | |
| 2521 | +name = "tinyvec" | |
| 2522 | +version = "1.12.0" | |
| 2523 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2524 | +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" | |
| 2525 | +dependencies = [ | |
| 2526 | + "tinyvec_macros", | |
| 2527 | +] | |
| 2528 | + | |
| 2529 | +[[package]] | |
| 2530 | +name = "tinyvec_macros" | |
| 2531 | +version = "0.1.1" | |
| 2532 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2533 | +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" | |
| 2534 | + | |
| 2535 | +[[package]] | |
| 2536 | +name = "tokio" | |
| 2537 | +version = "1.53.1" | |
| 2538 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2539 | +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" | |
| 2540 | +dependencies = [ | |
| 2541 | + "bytes", | |
| 2542 | + "libc", | |
| 2543 | + "mio", | |
| 2544 | + "pin-project-lite", | |
| 2545 | + "signal-hook-registry", | |
| 2546 | + "socket2", | |
| 2547 | + "tokio-macros", | |
| 2548 | + "windows-sys 0.61.2", | |
| 2549 | +] | |
| 2550 | + | |
| 2551 | +[[package]] | |
| 2552 | +name = "tokio-macros" | |
| 2553 | +version = "2.7.2" | |
| 2554 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2555 | +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" | |
| 2556 | +dependencies = [ | |
| 2557 | + "proc-macro2", | |
| 2558 | + "quote", | |
| 2559 | + "syn 3.0.3", | |
| 2560 | +] | |
| 2561 | + | |
| 2562 | +[[package]] | |
| 2563 | +name = "tokio-stream" | |
| 2564 | +version = "0.1.19" | |
| 2565 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2566 | +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" | |
| 2567 | +dependencies = [ | |
| 2568 | + "futures-core", | |
| 2569 | + "pin-project-lite", | |
| 2570 | + "tokio", | |
| 2571 | +] | |
| 2572 | + | |
| 2573 | +[[package]] | |
| 2574 | +name = "tower" | |
| 2575 | +version = "0.5.3" | |
| 2576 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2577 | +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" | |
| 2578 | +dependencies = [ | |
| 2579 | + "futures-core", | |
| 2580 | + "futures-util", | |
| 2581 | + "pin-project-lite", | |
| 2582 | + "sync_wrapper", | |
| 2583 | + "tokio", | |
| 2584 | + "tower-layer", | |
| 2585 | + "tower-service", | |
| 2586 | + "tracing", | |
| 2587 | +] | |
| 2588 | + | |
| 2589 | +[[package]] | |
| 2590 | +name = "tower-layer" | |
| 2591 | +version = "0.3.3" | |
| 2592 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2593 | +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" | |
| 2594 | + | |
| 2595 | +[[package]] | |
| 2596 | +name = "tower-service" | |
| 2597 | +version = "0.3.3" | |
| 2598 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2599 | +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" | |
| 2600 | + | |
| 2601 | +[[package]] | |
| 2602 | +name = "tracing" | |
| 2603 | +version = "0.1.44" | |
| 2604 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2605 | +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" | |
| 2606 | +dependencies = [ | |
| 2607 | + "log", | |
| 2608 | + "pin-project-lite", | |
| 2609 | + "tracing-attributes", | |
| 2610 | + "tracing-core", | |
| 2611 | +] | |
| 2612 | + | |
| 2613 | +[[package]] | |
| 2614 | +name = "tracing-attributes" | |
| 2615 | +version = "0.1.31" | |
| 2616 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2617 | +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" | |
| 2618 | +dependencies = [ | |
| 2619 | + "proc-macro2", | |
| 2620 | + "quote", | |
| 2621 | + "syn 2.0.119", | |
| 2622 | +] | |
| 2623 | + | |
| 2624 | +[[package]] | |
| 2625 | +name = "tracing-core" | |
| 2626 | +version = "0.1.36" | |
| 2627 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2628 | +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" | |
| 2629 | +dependencies = [ | |
| 2630 | + "once_cell", | |
| 2631 | + "valuable", | |
| 2632 | +] | |
| 2633 | + | |
| 2634 | +[[package]] | |
| 2635 | +name = "tracing-log" | |
| 2636 | +version = "0.2.0" | |
| 2637 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2638 | +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" | |
| 2639 | +dependencies = [ | |
| 2640 | + "log", | |
| 2641 | + "once_cell", | |
| 2642 | + "tracing-core", | |
| 2643 | +] | |
| 2644 | + | |
| 2645 | +[[package]] | |
| 2646 | +name = "tracing-subscriber" | |
| 2647 | +version = "0.3.23" | |
| 2648 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2649 | +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" | |
| 2650 | +dependencies = [ | |
| 2651 | + "matchers", | |
| 2652 | + "nu-ansi-term", | |
| 2653 | + "once_cell", | |
| 2654 | + "regex-automata", | |
| 2655 | + "sharded-slab", | |
| 2656 | + "smallvec", | |
| 2657 | + "thread_local", | |
| 2658 | + "tracing", | |
| 2659 | + "tracing-core", | |
| 2660 | + "tracing-log", | |
| 2661 | +] | |
| 2662 | + | |
| 2663 | +[[package]] | |
| 2664 | +name = "typenum" | |
| 2665 | +version = "1.20.1" | |
| 2666 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2667 | +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" | |
| 2668 | + | |
| 2669 | +[[package]] | |
| 2670 | +name = "unicode-bidi" | |
| 2671 | +version = "0.3.18" | |
| 2672 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2673 | +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" | |
| 2674 | + | |
| 2675 | +[[package]] | |
| 2676 | +name = "unicode-ident" | |
| 2677 | +version = "1.0.24" | |
| 2678 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2679 | +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" | |
| 2680 | + | |
| 2681 | +[[package]] | |
| 2682 | +name = "unicode-normalization" | |
| 2683 | +version = "0.1.25" | |
| 2684 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2685 | +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" | |
| 2686 | +dependencies = [ | |
| 2687 | + "tinyvec", | |
| 2688 | +] | |
| 2689 | + | |
| 2690 | +[[package]] | |
| 2691 | +name = "unicode-properties" | |
| 2692 | +version = "0.1.4" | |
| 2693 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2694 | +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" | |
| 2695 | + | |
| 2696 | +[[package]] | |
| 2697 | +name = "untrusted" | |
| 2698 | +version = "0.9.0" | |
| 2699 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2700 | +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" | |
| 2701 | + | |
| 2702 | +[[package]] | |
| 2703 | +name = "url" | |
| 2704 | +version = "2.5.8" | |
| 2705 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2706 | +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" | |
| 2707 | +dependencies = [ | |
| 2708 | + "form_urlencoded", | |
| 2709 | + "idna", | |
| 2710 | + "percent-encoding", | |
| 2711 | + "serde", | |
| 2712 | +] | |
| 2713 | + | |
| 2714 | +[[package]] | |
| 2715 | +name = "utf8_iter" | |
| 2716 | +version = "1.0.4" | |
| 2717 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2718 | +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" | |
| 2719 | + | |
| 2720 | +[[package]] | |
| 2721 | +name = "utf8parse" | |
| 2722 | +version = "0.2.2" | |
| 2723 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2724 | +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" | |
| 2725 | + | |
| 2726 | +[[package]] | |
| 2727 | +name = "uuid" | |
| 2728 | +version = "1.24.0" | |
| 2729 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2730 | +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" | |
| 2731 | +dependencies = [ | |
| 2732 | + "getrandom 0.4.3", | |
| 2733 | + "js-sys", | |
| 2734 | + "serde_core", | |
| 2735 | + "wasm-bindgen", | |
| 2736 | +] | |
| 2737 | + | |
| 2738 | +[[package]] | |
| 2739 | +name = "valuable" | |
| 2740 | +version = "0.1.1" | |
| 2741 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2742 | +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" | |
| 2743 | + | |
| 2744 | +[[package]] | |
| 2745 | +name = "vcpkg" | |
| 2746 | +version = "0.2.15" | |
| 2747 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2748 | +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" | |
| 2749 | + | |
| 2750 | +[[package]] | |
| 2751 | +name = "version_check" | |
| 2752 | +version = "0.9.5" | |
| 2753 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2754 | +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" | |
| 2755 | + | |
| 2756 | +[[package]] | |
| 2757 | +name = "wasi" | |
| 2758 | +version = "0.11.1+wasi-snapshot-preview1" | |
| 2759 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2760 | +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" | |
| 2761 | + | |
| 2762 | +[[package]] | |
| 2763 | +name = "wasite" | |
| 2764 | +version = "0.1.0" | |
| 2765 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2766 | +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" | |
| 2767 | + | |
| 2768 | +[[package]] | |
| 2769 | +name = "wasm-bindgen" | |
| 2770 | +version = "0.2.126" | |
| 2771 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2772 | +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" | |
| 2773 | +dependencies = [ | |
| 2774 | + "cfg-if", | |
| 2775 | + "once_cell", | |
| 2776 | + "rustversion", | |
| 2777 | + "wasm-bindgen-macro", | |
| 2778 | + "wasm-bindgen-shared", | |
| 2779 | +] | |
| 2780 | + | |
| 2781 | +[[package]] | |
| 2782 | +name = "wasm-bindgen-macro" | |
| 2783 | +version = "0.2.126" | |
| 2784 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2785 | +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" | |
| 2786 | +dependencies = [ | |
| 2787 | + "quote", | |
| 2788 | + "wasm-bindgen-macro-support", | |
| 2789 | +] | |
| 2790 | + | |
| 2791 | +[[package]] | |
| 2792 | +name = "wasm-bindgen-macro-support" | |
| 2793 | +version = "0.2.126" | |
| 2794 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2795 | +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" | |
| 2796 | +dependencies = [ | |
| 2797 | + "bumpalo", | |
| 2798 | + "proc-macro2", | |
| 2799 | + "quote", | |
| 2800 | + "syn 2.0.119", | |
| 2801 | + "wasm-bindgen-shared", | |
| 2802 | +] | |
| 2803 | + | |
| 2804 | +[[package]] | |
| 2805 | +name = "wasm-bindgen-shared" | |
| 2806 | +version = "0.2.126" | |
| 2807 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2808 | +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" | |
| 2809 | +dependencies = [ | |
| 2810 | + "unicode-ident", | |
| 2811 | +] | |
| 2812 | + | |
| 2813 | +[[package]] | |
| 2814 | +name = "webpki-roots" | |
| 2815 | +version = "0.26.11" | |
| 2816 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2817 | +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" | |
| 2818 | +dependencies = [ | |
| 2819 | + "webpki-roots 1.0.9", | |
| 2820 | +] | |
| 2821 | + | |
| 2822 | +[[package]] | |
| 2823 | +name = "webpki-roots" | |
| 2824 | +version = "1.0.9" | |
| 2825 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2826 | +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" | |
| 2827 | +dependencies = [ | |
| 2828 | + "rustls-pki-types", | |
| 2829 | +] | |
| 2830 | + | |
| 2831 | +[[package]] | |
| 2832 | +name = "whoami" | |
| 2833 | +version = "1.6.1" | |
| 2834 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2835 | +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" | |
| 2836 | +dependencies = [ | |
| 2837 | + "libredox", | |
| 2838 | + "wasite", | |
| 2839 | +] | |
| 2840 | + | |
| 2841 | +[[package]] | |
| 2842 | +name = "windows-core" | |
| 2843 | +version = "0.62.2" | |
| 2844 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2845 | +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" | |
| 2846 | +dependencies = [ | |
| 2847 | + "windows-implement", | |
| 2848 | + "windows-interface", | |
| 2849 | + "windows-link", | |
| 2850 | + "windows-result", | |
| 2851 | + "windows-strings", | |
| 2852 | +] | |
| 2853 | + | |
| 2854 | +[[package]] | |
| 2855 | +name = "windows-implement" | |
| 2856 | +version = "0.60.2" | |
| 2857 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2858 | +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" | |
| 2859 | +dependencies = [ | |
| 2860 | + "proc-macro2", | |
| 2861 | + "quote", | |
| 2862 | + "syn 2.0.119", | |
| 2863 | +] | |
| 2864 | + | |
| 2865 | +[[package]] | |
| 2866 | +name = "windows-interface" | |
| 2867 | +version = "0.59.3" | |
| 2868 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2869 | +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" | |
| 2870 | +dependencies = [ | |
| 2871 | + "proc-macro2", | |
| 2872 | + "quote", | |
| 2873 | + "syn 2.0.119", | |
| 2874 | +] | |
| 2875 | + | |
| 2876 | +[[package]] | |
| 2877 | +name = "windows-link" | |
| 2878 | +version = "0.2.1" | |
| 2879 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2880 | +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" | |
| 2881 | + | |
| 2882 | +[[package]] | |
| 2883 | +name = "windows-result" | |
| 2884 | +version = "0.4.1" | |
| 2885 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2886 | +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" | |
| 2887 | +dependencies = [ | |
| 2888 | + "windows-link", | |
| 2889 | +] | |
| 2890 | + | |
| 2891 | +[[package]] | |
| 2892 | +name = "windows-strings" | |
| 2893 | +version = "0.5.1" | |
| 2894 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2895 | +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" | |
| 2896 | +dependencies = [ | |
| 2897 | + "windows-link", | |
| 2898 | +] | |
| 2899 | + | |
| 2900 | +[[package]] | |
| 2901 | +name = "windows-sys" | |
| 2902 | +version = "0.48.0" | |
| 2903 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2904 | +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" | |
| 2905 | +dependencies = [ | |
| 2906 | + "windows-targets 0.48.5", | |
| 2907 | +] | |
| 2908 | + | |
| 2909 | +[[package]] | |
| 2910 | +name = "windows-sys" | |
| 2911 | +version = "0.52.0" | |
| 2912 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2913 | +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" | |
| 2914 | +dependencies = [ | |
| 2915 | + "windows-targets 0.52.6", | |
| 2916 | +] | |
| 2917 | + | |
| 2918 | +[[package]] | |
| 2919 | +name = "windows-sys" | |
| 2920 | +version = "0.59.0" | |
| 2921 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2922 | +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" | |
| 2923 | +dependencies = [ | |
| 2924 | + "windows-targets 0.52.6", | |
| 2925 | +] | |
| 2926 | + | |
| 2927 | +[[package]] | |
| 2928 | +name = "windows-sys" | |
| 2929 | +version = "0.61.2" | |
| 2930 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2931 | +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" | |
| 2932 | +dependencies = [ | |
| 2933 | + "windows-link", | |
| 2934 | +] | |
| 2935 | + | |
| 2936 | +[[package]] | |
| 2937 | +name = "windows-targets" | |
| 2938 | +version = "0.48.5" | |
| 2939 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2940 | +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" | |
| 2941 | +dependencies = [ | |
| 2942 | + "windows_aarch64_gnullvm 0.48.5", | |
| 2943 | + "windows_aarch64_msvc 0.48.5", | |
| 2944 | + "windows_i686_gnu 0.48.5", | |
| 2945 | + "windows_i686_msvc 0.48.5", | |
| 2946 | + "windows_x86_64_gnu 0.48.5", | |
| 2947 | + "windows_x86_64_gnullvm 0.48.5", | |
| 2948 | + "windows_x86_64_msvc 0.48.5", | |
| 2949 | +] | |
| 2950 | + | |
| 2951 | +[[package]] | |
| 2952 | +name = "windows-targets" | |
| 2953 | +version = "0.52.6" | |
| 2954 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2955 | +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" | |
| 2956 | +dependencies = [ | |
| 2957 | + "windows_aarch64_gnullvm 0.52.6", | |
| 2958 | + "windows_aarch64_msvc 0.52.6", | |
| 2959 | + "windows_i686_gnu 0.52.6", | |
| 2960 | + "windows_i686_gnullvm", | |
| 2961 | + "windows_i686_msvc 0.52.6", | |
| 2962 | + "windows_x86_64_gnu 0.52.6", | |
| 2963 | + "windows_x86_64_gnullvm 0.52.6", | |
| 2964 | + "windows_x86_64_msvc 0.52.6", | |
| 2965 | +] | |
| 2966 | + | |
| 2967 | +[[package]] | |
| 2968 | +name = "windows_aarch64_gnullvm" | |
| 2969 | +version = "0.48.5" | |
| 2970 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2971 | +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" | |
| 2972 | + | |
| 2973 | +[[package]] | |
| 2974 | +name = "windows_aarch64_gnullvm" | |
| 2975 | +version = "0.52.6" | |
| 2976 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2977 | +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" | |
| 2978 | + | |
| 2979 | +[[package]] | |
| 2980 | +name = "windows_aarch64_msvc" | |
| 2981 | +version = "0.48.5" | |
| 2982 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2983 | +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" | |
| 2984 | + | |
| 2985 | +[[package]] | |
| 2986 | +name = "windows_aarch64_msvc" | |
| 2987 | +version = "0.52.6" | |
| 2988 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2989 | +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" | |
| 2990 | + | |
| 2991 | +[[package]] | |
| 2992 | +name = "windows_i686_gnu" | |
| 2993 | +version = "0.48.5" | |
| 2994 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2995 | +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" | |
| 2996 | + | |
| 2997 | +[[package]] | |
| 2998 | +name = "windows_i686_gnu" | |
| 2999 | +version = "0.52.6" | |
| 3000 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3001 | +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" | |
| 3002 | + | |
| 3003 | +[[package]] | |
| 3004 | +name = "windows_i686_gnullvm" | |
| 3005 | +version = "0.52.6" | |
| 3006 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3007 | +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" | |
| 3008 | + | |
| 3009 | +[[package]] | |
| 3010 | +name = "windows_i686_msvc" | |
| 3011 | +version = "0.48.5" | |
| 3012 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3013 | +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" | |
| 3014 | + | |
| 3015 | +[[package]] | |
| 3016 | +name = "windows_i686_msvc" | |
| 3017 | +version = "0.52.6" | |
| 3018 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3019 | +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" | |
| 3020 | + | |
| 3021 | +[[package]] | |
| 3022 | +name = "windows_x86_64_gnu" | |
| 3023 | +version = "0.48.5" | |
| 3024 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3025 | +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" | |
| 3026 | + | |
| 3027 | +[[package]] | |
| 3028 | +name = "windows_x86_64_gnu" | |
| 3029 | +version = "0.52.6" | |
| 3030 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3031 | +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" | |
| 3032 | + | |
| 3033 | +[[package]] | |
| 3034 | +name = "windows_x86_64_gnullvm" | |
| 3035 | +version = "0.48.5" | |
| 3036 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3037 | +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" | |
| 3038 | + | |
| 3039 | +[[package]] | |
| 3040 | +name = "windows_x86_64_gnullvm" | |
| 3041 | +version = "0.52.6" | |
| 3042 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3043 | +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" | |
| 3044 | + | |
| 3045 | +[[package]] | |
| 3046 | +name = "windows_x86_64_msvc" | |
| 3047 | +version = "0.48.5" | |
| 3048 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3049 | +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" | |
| 3050 | + | |
| 3051 | +[[package]] | |
| 3052 | +name = "windows_x86_64_msvc" | |
| 3053 | +version = "0.52.6" | |
| 3054 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3055 | +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" | |
| 3056 | + | |
| 3057 | +[[package]] | |
| 3058 | +name = "winnow" | |
| 3059 | +version = "1.0.4" | |
| 3060 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3061 | +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" | |
| 3062 | +dependencies = [ | |
| 3063 | + "memchr", | |
| 3064 | +] | |
| 3065 | + | |
| 3066 | +[[package]] | |
| 3067 | +name = "writeable" | |
| 3068 | +version = "0.6.3" | |
| 3069 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3070 | +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" | |
| 3071 | + | |
| 3072 | +[[package]] | |
| 3073 | +name = "yoke" | |
| 3074 | +version = "0.8.3" | |
| 3075 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3076 | +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" | |
| 3077 | +dependencies = [ | |
| 3078 | + "stable_deref_trait", | |
| 3079 | + "yoke-derive", | |
| 3080 | + "zerofrom", | |
| 3081 | +] | |
| 3082 | + | |
| 3083 | +[[package]] | |
| 3084 | +name = "yoke-derive" | |
| 3085 | +version = "0.8.2" | |
| 3086 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3087 | +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" | |
| 3088 | +dependencies = [ | |
| 3089 | + "proc-macro2", | |
| 3090 | + "quote", | |
| 3091 | + "syn 2.0.119", | |
| 3092 | + "synstructure", | |
| 3093 | +] | |
| 3094 | + | |
| 3095 | +[[package]] | |
| 3096 | +name = "zerocopy" | |
| 3097 | +version = "0.8.56" | |
| 3098 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3099 | +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" | |
| 3100 | +dependencies = [ | |
| 3101 | + "zerocopy-derive", | |
| 3102 | +] | |
| 3103 | + | |
| 3104 | +[[package]] | |
| 3105 | +name = "zerocopy-derive" | |
| 3106 | +version = "0.8.56" | |
| 3107 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3108 | +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" | |
| 3109 | +dependencies = [ | |
| 3110 | + "proc-macro2", | |
| 3111 | + "quote", | |
| 3112 | + "syn 2.0.119", | |
| 3113 | +] | |
| 3114 | + | |
| 3115 | +[[package]] | |
| 3116 | +name = "zerofrom" | |
| 3117 | +version = "0.1.8" | |
| 3118 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3119 | +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" | |
| 3120 | +dependencies = [ | |
| 3121 | + "zerofrom-derive", | |
| 3122 | +] | |
| 3123 | + | |
| 3124 | +[[package]] | |
| 3125 | +name = "zerofrom-derive" | |
| 3126 | +version = "0.1.7" | |
| 3127 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3128 | +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" | |
| 3129 | +dependencies = [ | |
| 3130 | + "proc-macro2", | |
| 3131 | + "quote", | |
| 3132 | + "syn 2.0.119", | |
| 3133 | + "synstructure", | |
| 3134 | +] | |
| 3135 | + | |
| 3136 | +[[package]] | |
| 3137 | +name = "zeroize" | |
| 3138 | +version = "1.9.0" | |
| 3139 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3140 | +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" | |
| 3141 | + | |
| 3142 | +[[package]] | |
| 3143 | +name = "zerotrie" | |
| 3144 | +version = "0.2.4" | |
| 3145 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3146 | +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" | |
| 3147 | +dependencies = [ | |
| 3148 | + "displaydoc", | |
| 3149 | + "yoke", | |
| 3150 | + "zerofrom", | |
| 3151 | +] | |
| 3152 | + | |
| 3153 | +[[package]] | |
| 3154 | +name = "zerovec" | |
| 3155 | +version = "0.11.6" | |
| 3156 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3157 | +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" | |
| 3158 | +dependencies = [ | |
| 3159 | + "yoke", | |
| 3160 | + "zerofrom", | |
| 3161 | + "zerovec-derive", | |
| 3162 | +] | |
| 3163 | + | |
| 3164 | +[[package]] | |
| 3165 | +name = "zerovec-derive" | |
| 3166 | +version = "0.11.3" | |
| 3167 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3168 | +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" | |
| 3169 | +dependencies = [ | |
| 3170 | + "proc-macro2", | |
| 3171 | + "quote", | |
| 3172 | + "syn 2.0.119", | |
| 3173 | +] | |
| 3174 | + | |
| 3175 | +[[package]] | |
| 3176 | +name = "zmij" | |
| 3177 | +version = "1.0.23" | |
| 3178 | +source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3179 | +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" | |
| new file mode 100644 | |||
| @@ -0,0 +1,3179 @@ | |||
| 1 | +# This file is automatically @generated by Cargo. | ||
| 2 | +# It is not intended for manual editing. | ||
| 3 | +version = 4 | ||
| 4 | + | ||
| 5 | +[[package]] | ||
| 6 | +name = "aho-corasick" | ||
| 7 | +version = "1.1.5" | ||
| 8 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 9 | +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" | ||
| 10 | +dependencies = [ | ||
| 11 | + "memchr", | ||
| 12 | +] | ||
| 13 | + | ||
| 14 | +[[package]] | ||
| 15 | +name = "allocator-api2" | ||
| 16 | +version = "0.2.21" | ||
| 17 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 18 | +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" | ||
| 19 | + | ||
| 20 | +[[package]] | ||
| 21 | +name = "android_system_properties" | ||
| 22 | +version = "0.1.6" | ||
| 23 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 24 | +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" | ||
| 25 | +dependencies = [ | ||
| 26 | + "libc", | ||
| 27 | +] | ||
| 28 | + | ||
| 29 | +[[package]] | ||
| 30 | +name = "anstream" | ||
| 31 | +version = "1.0.0" | ||
| 32 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 33 | +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" | ||
| 34 | +dependencies = [ | ||
| 35 | + "anstyle", | ||
| 36 | + "anstyle-parse", | ||
| 37 | + "anstyle-query", | ||
| 38 | + "anstyle-wincon", | ||
| 39 | + "colorchoice", | ||
| 40 | + "is_terminal_polyfill", | ||
| 41 | + "utf8parse", | ||
| 42 | +] | ||
| 43 | + | ||
| 44 | +[[package]] | ||
| 45 | +name = "anstyle" | ||
| 46 | +version = "1.0.14" | ||
| 47 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 48 | +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" | ||
| 49 | + | ||
| 50 | +[[package]] | ||
| 51 | +name = "anstyle-parse" | ||
| 52 | +version = "1.0.0" | ||
| 53 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 54 | +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" | ||
| 55 | +dependencies = [ | ||
| 56 | + "utf8parse", | ||
| 57 | +] | ||
| 58 | + | ||
| 59 | +[[package]] | ||
| 60 | +name = "anstyle-query" | ||
| 61 | +version = "1.1.5" | ||
| 62 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 63 | +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" | ||
| 64 | +dependencies = [ | ||
| 65 | + "windows-sys 0.61.2", | ||
| 66 | +] | ||
| 67 | + | ||
| 68 | +[[package]] | ||
| 69 | +name = "anstyle-wincon" | ||
| 70 | +version = "3.0.11" | ||
| 71 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 72 | +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" | ||
| 73 | +dependencies = [ | ||
| 74 | + "anstyle", | ||
| 75 | + "once_cell_polyfill", | ||
| 76 | + "windows-sys 0.61.2", | ||
| 77 | +] | ||
| 78 | + | ||
| 79 | +[[package]] | ||
| 80 | +name = "anyhow" | ||
| 81 | +version = "1.0.104" | ||
| 82 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 83 | +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" | ||
| 84 | + | ||
| 85 | +[[package]] | ||
| 86 | +name = "argon2" | ||
| 87 | +version = "0.5.3" | ||
| 88 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 89 | +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" | ||
| 90 | +dependencies = [ | ||
| 91 | + "base64ct", | ||
| 92 | + "blake2", | ||
| 93 | + "cpufeatures", | ||
| 94 | + "password-hash", | ||
| 95 | +] | ||
| 96 | + | ||
| 97 | +[[package]] | ||
| 98 | +name = "askama" | ||
| 99 | +version = "0.16.0" | ||
| 100 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 101 | +checksum = "f1bf825125edd887a019d0a3a837dcc5499a68b0d034cc3eb594070c3e18addc" | ||
| 102 | +dependencies = [ | ||
| 103 | + "askama_macros", | ||
| 104 | + "itoa", | ||
| 105 | + "percent-encoding", | ||
| 106 | + "serde", | ||
| 107 | + "serde_json", | ||
| 108 | +] | ||
| 109 | + | ||
| 110 | +[[package]] | ||
| 111 | +name = "askama_derive" | ||
| 112 | +version = "0.16.0" | ||
| 113 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 114 | +checksum = "e1c7065972a130eafa84215f21352ae15b4a7393da48c1f5e103904490736738" | ||
| 115 | +dependencies = [ | ||
| 116 | + "askama_parser", | ||
| 117 | + "basic-toml", | ||
| 118 | + "glob", | ||
| 119 | + "memchr", | ||
| 120 | + "proc-macro2", | ||
| 121 | + "quote", | ||
| 122 | + "rustc-hash", | ||
| 123 | + "serde", | ||
| 124 | + "serde_derive", | ||
| 125 | + "syn 2.0.119", | ||
| 126 | +] | ||
| 127 | + | ||
| 128 | +[[package]] | ||
| 129 | +name = "askama_macros" | ||
| 130 | +version = "0.16.0" | ||
| 131 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 132 | +checksum = "0e23b1d2c4bd39a41971f6124cef4cc6fd0540913ecb90919b69ab3bbe44ae1a" | ||
| 133 | +dependencies = [ | ||
| 134 | + "askama_derive", | ||
| 135 | +] | ||
| 136 | + | ||
| 137 | +[[package]] | ||
| 138 | +name = "askama_parser" | ||
| 139 | +version = "0.16.0" | ||
| 140 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 141 | +checksum = "7db09fde9143e7ac4513358fb32ee32847125b63b18ea715afd487956da715da" | ||
| 142 | +dependencies = [ | ||
| 143 | + "rustc-hash", | ||
| 144 | + "serde", | ||
| 145 | + "serde_derive", | ||
| 146 | + "unicode-ident", | ||
| 147 | + "winnow", | ||
| 148 | +] | ||
| 149 | + | ||
| 150 | +[[package]] | ||
| 151 | +name = "atoi" | ||
| 152 | +version = "2.0.0" | ||
| 153 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 154 | +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" | ||
| 155 | +dependencies = [ | ||
| 156 | + "num-traits", | ||
| 157 | +] | ||
| 158 | + | ||
| 159 | +[[package]] | ||
| 160 | +name = "atomic-waker" | ||
| 161 | +version = "1.1.2" | ||
| 162 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 163 | +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" | ||
| 164 | + | ||
| 165 | +[[package]] | ||
| 166 | +name = "autocfg" | ||
| 167 | +version = "1.5.1" | ||
| 168 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 169 | +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" | ||
| 170 | + | ||
| 171 | +[[package]] | ||
| 172 | +name = "axum" | ||
| 173 | +version = "0.8.9" | ||
| 174 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 175 | +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" | ||
| 176 | +dependencies = [ | ||
| 177 | + "axum-core", | ||
| 178 | + "bytes", | ||
| 179 | + "form_urlencoded", | ||
| 180 | + "futures-util", | ||
| 181 | + "http", | ||
| 182 | + "http-body", | ||
| 183 | + "http-body-util", | ||
| 184 | + "hyper", | ||
| 185 | + "hyper-util", | ||
| 186 | + "itoa", | ||
| 187 | + "matchit", | ||
| 188 | + "memchr", | ||
| 189 | + "mime", | ||
| 190 | + "percent-encoding", | ||
| 191 | + "pin-project-lite", | ||
| 192 | + "serde_core", | ||
| 193 | + "serde_json", | ||
| 194 | + "serde_path_to_error", | ||
| 195 | + "serde_urlencoded", | ||
| 196 | + "sync_wrapper", | ||
| 197 | + "tokio", | ||
| 198 | + "tower", | ||
| 199 | + "tower-layer", | ||
| 200 | + "tower-service", | ||
| 201 | + "tracing", | ||
| 202 | +] | ||
| 203 | + | ||
| 204 | +[[package]] | ||
| 205 | +name = "axum-core" | ||
| 206 | +version = "0.5.6" | ||
| 207 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 208 | +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" | ||
| 209 | +dependencies = [ | ||
| 210 | + "bytes", | ||
| 211 | + "futures-core", | ||
| 212 | + "http", | ||
| 213 | + "http-body", | ||
| 214 | + "http-body-util", | ||
| 215 | + "mime", | ||
| 216 | + "pin-project-lite", | ||
| 217 | + "sync_wrapper", | ||
| 218 | + "tower-layer", | ||
| 219 | + "tower-service", | ||
| 220 | + "tracing", | ||
| 221 | +] | ||
| 222 | + | ||
| 223 | +[[package]] | ||
| 224 | +name = "axum-extra" | ||
| 225 | +version = "0.12.6" | ||
| 226 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 227 | +checksum = "be44683b41ccb9ab2d23a5230015c9c3c55be97a25e4428366de8873103f7970" | ||
| 228 | +dependencies = [ | ||
| 229 | + "axum", | ||
| 230 | + "axum-core", | ||
| 231 | + "bytes", | ||
| 232 | + "cookie", | ||
| 233 | + "futures-core", | ||
| 234 | + "futures-util", | ||
| 235 | + "http", | ||
| 236 | + "http-body", | ||
| 237 | + "http-body-util", | ||
| 238 | + "mime", | ||
| 239 | + "pin-project-lite", | ||
| 240 | + "tower-layer", | ||
| 241 | + "tower-service", | ||
| 242 | + "tracing", | ||
| 243 | +] | ||
| 244 | + | ||
| 245 | +[[package]] | ||
| 246 | +name = "base16ct" | ||
| 247 | +version = "0.2.0" | ||
| 248 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 249 | +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" | ||
| 250 | + | ||
| 251 | +[[package]] | ||
| 252 | +name = "base64" | ||
| 253 | +version = "0.13.1" | ||
| 254 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 255 | +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" | ||
| 256 | + | ||
| 257 | +[[package]] | ||
| 258 | +name = "base64" | ||
| 259 | +version = "0.22.1" | ||
| 260 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 261 | +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" | ||
| 262 | + | ||
| 263 | +[[package]] | ||
| 264 | +name = "base64ct" | ||
| 265 | +version = "1.8.3" | ||
| 266 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 267 | +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" | ||
| 268 | + | ||
| 269 | +[[package]] | ||
| 270 | +name = "basic-toml" | ||
| 271 | +version = "0.1.10" | ||
| 272 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 273 | +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" | ||
| 274 | +dependencies = [ | ||
| 275 | + "serde", | ||
| 276 | +] | ||
| 277 | + | ||
| 278 | +[[package]] | ||
| 279 | +name = "biscuit-auth" | ||
| 280 | +version = "6.0.0" | ||
| 281 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 282 | +checksum = "d5884fc86b3e21f5649ef4326e17ef729b3096e6502deaf13db7b7fb05bb992b" | ||
| 283 | +dependencies = [ | ||
| 284 | + "base64 0.13.1", | ||
| 285 | + "biscuit-parser", | ||
| 286 | + "biscuit-quote", | ||
| 287 | + "ecdsa", | ||
| 288 | + "ed25519-dalek", | ||
| 289 | + "elliptic-curve", | ||
| 290 | + "getrandom 0.2.17", | ||
| 291 | + "hex", | ||
| 292 | + "nom", | ||
| 293 | + "p256", | ||
| 294 | + "pkcs8 0.9.0", | ||
| 295 | + "prost", | ||
| 296 | + "prost-types", | ||
| 297 | + "rand", | ||
| 298 | + "rand_core", | ||
| 299 | + "regex", | ||
| 300 | + "serde_json", | ||
| 301 | + "sha2 0.9.9", | ||
| 302 | + "thiserror 1.0.69", | ||
| 303 | + "time", | ||
| 304 | + "zeroize", | ||
| 305 | +] | ||
| 306 | + | ||
| 307 | +[[package]] | ||
| 308 | +name = "biscuit-parser" | ||
| 309 | +version = "0.2.0" | ||
| 310 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 311 | +checksum = "9d7cafdbc8c30e1f0fb87df7161bec77f6f00da652cc33f102b0f95bd1cbc0fa" | ||
| 312 | +dependencies = [ | ||
| 313 | + "hex", | ||
| 314 | + "nom", | ||
| 315 | + "proc-macro2", | ||
| 316 | + "quote", | ||
| 317 | + "thiserror 1.0.69", | ||
| 318 | + "time", | ||
| 319 | +] | ||
| 320 | + | ||
| 321 | +[[package]] | ||
| 322 | +name = "biscuit-quote" | ||
| 323 | +version = "0.3.0" | ||
| 324 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 325 | +checksum = "49d2332c742a07a846f1fb2760e58a0ee60f2bc30987046fcea816b40630335a" | ||
| 326 | +dependencies = [ | ||
| 327 | + "biscuit-parser", | ||
| 328 | + "proc-macro-error2", | ||
| 329 | + "proc-macro2", | ||
| 330 | + "quote", | ||
| 331 | + "syn 1.0.109", | ||
| 332 | +] | ||
| 333 | + | ||
| 334 | +[[package]] | ||
| 335 | +name = "bitflags" | ||
| 336 | +version = "2.13.1" | ||
| 337 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 338 | +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" | ||
| 339 | +dependencies = [ | ||
| 340 | + "serde_core", | ||
| 341 | +] | ||
| 342 | + | ||
| 343 | +[[package]] | ||
| 344 | +name = "blake2" | ||
| 345 | +version = "0.10.6" | ||
| 346 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 347 | +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" | ||
| 348 | +dependencies = [ | ||
| 349 | + "digest 0.10.7", | ||
| 350 | +] | ||
| 351 | + | ||
| 352 | +[[package]] | ||
| 353 | +name = "block-buffer" | ||
| 354 | +version = "0.9.0" | ||
| 355 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 356 | +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" | ||
| 357 | +dependencies = [ | ||
| 358 | + "generic-array", | ||
| 359 | +] | ||
| 360 | + | ||
| 361 | +[[package]] | ||
| 362 | +name = "block-buffer" | ||
| 363 | +version = "0.10.4" | ||
| 364 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 365 | +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" | ||
| 366 | +dependencies = [ | ||
| 367 | + "generic-array", | ||
| 368 | +] | ||
| 369 | + | ||
| 370 | +[[package]] | ||
| 371 | +name = "bumpalo" | ||
| 372 | +version = "3.20.3" | ||
| 373 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 374 | +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" | ||
| 375 | + | ||
| 376 | +[[package]] | ||
| 377 | +name = "byteorder" | ||
| 378 | +version = "1.5.0" | ||
| 379 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 380 | +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" | ||
| 381 | + | ||
| 382 | +[[package]] | ||
| 383 | +name = "bytes" | ||
| 384 | +version = "1.12.1" | ||
| 385 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 386 | +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" | ||
| 387 | + | ||
| 388 | +[[package]] | ||
| 389 | +name = "cc" | ||
| 390 | +version = "1.4.0" | ||
| 391 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 392 | +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" | ||
| 393 | +dependencies = [ | ||
| 394 | + "find-msvc-tools", | ||
| 395 | + "shlex", | ||
| 396 | +] | ||
| 397 | + | ||
| 398 | +[[package]] | ||
| 399 | +name = "cfg-if" | ||
| 400 | +version = "1.0.4" | ||
| 401 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 402 | +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" | ||
| 403 | + | ||
| 404 | +[[package]] | ||
| 405 | +name = "chrono" | ||
| 406 | +version = "0.4.45" | ||
| 407 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 408 | +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" | ||
| 409 | +dependencies = [ | ||
| 410 | + "iana-time-zone", | ||
| 411 | + "js-sys", | ||
| 412 | + "num-traits", | ||
| 413 | + "serde", | ||
| 414 | + "wasm-bindgen", | ||
| 415 | + "windows-link", | ||
| 416 | +] | ||
| 417 | + | ||
| 418 | +[[package]] | ||
| 419 | +name = "chrono-tz" | ||
| 420 | +version = "0.10.4" | ||
| 421 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 422 | +checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3" | ||
| 423 | +dependencies = [ | ||
| 424 | + "chrono", | ||
| 425 | + "phf", | ||
| 426 | +] | ||
| 427 | + | ||
| 428 | +[[package]] | ||
| 429 | +name = "clap" | ||
| 430 | +version = "4.6.6" | ||
| 431 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 432 | +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" | ||
| 433 | +dependencies = [ | ||
| 434 | + "clap_builder", | ||
| 435 | + "clap_derive", | ||
| 436 | +] | ||
| 437 | + | ||
| 438 | +[[package]] | ||
| 439 | +name = "clap_builder" | ||
| 440 | +version = "4.6.6" | ||
| 441 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 442 | +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" | ||
| 443 | +dependencies = [ | ||
| 444 | + "anstream", | ||
| 445 | + "anstyle", | ||
| 446 | + "clap_lex", | ||
| 447 | + "strsim", | ||
| 448 | +] | ||
| 449 | + | ||
| 450 | +[[package]] | ||
| 451 | +name = "clap_derive" | ||
| 452 | +version = "4.6.4" | ||
| 453 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 454 | +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" | ||
| 455 | +dependencies = [ | ||
| 456 | + "heck", | ||
| 457 | + "proc-macro2", | ||
| 458 | + "quote", | ||
| 459 | + "syn 3.0.3", | ||
| 460 | +] | ||
| 461 | + | ||
| 462 | +[[package]] | ||
| 463 | +name = "clap_lex" | ||
| 464 | +version = "1.1.0" | ||
| 465 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 466 | +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" | ||
| 467 | + | ||
| 468 | +[[package]] | ||
| 469 | +name = "colorchoice" | ||
| 470 | +version = "1.0.5" | ||
| 471 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 472 | +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" | ||
| 473 | + | ||
| 474 | +[[package]] | ||
| 475 | +name = "const-oid" | ||
| 476 | +version = "0.9.6" | ||
| 477 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 478 | +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" | ||
| 479 | + | ||
| 480 | +[[package]] | ||
| 481 | +name = "cookie" | ||
| 482 | +version = "0.18.1" | ||
| 483 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 484 | +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" | ||
| 485 | +dependencies = [ | ||
| 486 | + "percent-encoding", | ||
| 487 | + "time", | ||
| 488 | + "version_check", | ||
| 489 | +] | ||
| 490 | + | ||
| 491 | +[[package]] | ||
| 492 | +name = "core-foundation-sys" | ||
| 493 | +version = "0.8.7" | ||
| 494 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 495 | +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" | ||
| 496 | + | ||
| 497 | +[[package]] | ||
| 498 | +name = "cpufeatures" | ||
| 499 | +version = "0.2.17" | ||
| 500 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 501 | +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" | ||
| 502 | +dependencies = [ | ||
| 503 | + "libc", | ||
| 504 | +] | ||
| 505 | + | ||
| 506 | +[[package]] | ||
| 507 | +name = "crc" | ||
| 508 | +version = "3.4.0" | ||
| 509 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 510 | +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" | ||
| 511 | +dependencies = [ | ||
| 512 | + "crc-catalog", | ||
| 513 | +] | ||
| 514 | + | ||
| 515 | +[[package]] | ||
| 516 | +name = "crc-catalog" | ||
| 517 | +version = "2.5.0" | ||
| 518 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 519 | +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" | ||
| 520 | + | ||
| 521 | +[[package]] | ||
| 522 | +name = "crossbeam-queue" | ||
| 523 | +version = "0.3.13" | ||
| 524 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 525 | +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" | ||
| 526 | +dependencies = [ | ||
| 527 | + "crossbeam-utils", | ||
| 528 | +] | ||
| 529 | + | ||
| 530 | +[[package]] | ||
| 531 | +name = "crossbeam-utils" | ||
| 532 | +version = "0.8.22" | ||
| 533 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 534 | +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" | ||
| 535 | + | ||
| 536 | +[[package]] | ||
| 537 | +name = "crypto-bigint" | ||
| 538 | +version = "0.5.5" | ||
| 539 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 540 | +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" | ||
| 541 | +dependencies = [ | ||
| 542 | + "generic-array", | ||
| 543 | + "rand_core", | ||
| 544 | + "subtle", | ||
| 545 | + "zeroize", | ||
| 546 | +] | ||
| 547 | + | ||
| 548 | +[[package]] | ||
| 549 | +name = "crypto-common" | ||
| 550 | +version = "0.1.6" | ||
| 551 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 552 | +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" | ||
| 553 | +dependencies = [ | ||
| 554 | + "generic-array", | ||
| 555 | + "typenum", | ||
| 556 | +] | ||
| 557 | + | ||
| 558 | +[[package]] | ||
| 559 | +name = "curve25519-dalek" | ||
| 560 | +version = "4.1.3" | ||
| 561 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 562 | +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" | ||
| 563 | +dependencies = [ | ||
| 564 | + "cfg-if", | ||
| 565 | + "cpufeatures", | ||
| 566 | + "curve25519-dalek-derive", | ||
| 567 | + "digest 0.10.7", | ||
| 568 | + "fiat-crypto", | ||
| 569 | + "rustc_version", | ||
| 570 | + "subtle", | ||
| 571 | + "zeroize", | ||
| 572 | +] | ||
| 573 | + | ||
| 574 | +[[package]] | ||
| 575 | +name = "curve25519-dalek-derive" | ||
| 576 | +version = "0.1.1" | ||
| 577 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 578 | +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" | ||
| 579 | +dependencies = [ | ||
| 580 | + "proc-macro2", | ||
| 581 | + "quote", | ||
| 582 | + "syn 2.0.119", | ||
| 583 | +] | ||
| 584 | + | ||
| 585 | +[[package]] | ||
| 586 | +name = "der" | ||
| 587 | +version = "0.6.1" | ||
| 588 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 589 | +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" | ||
| 590 | +dependencies = [ | ||
| 591 | + "const-oid", | ||
| 592 | +] | ||
| 593 | + | ||
| 594 | +[[package]] | ||
| 595 | +name = "der" | ||
| 596 | +version = "0.7.10" | ||
| 597 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 598 | +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" | ||
| 599 | +dependencies = [ | ||
| 600 | + "const-oid", | ||
| 601 | + "pem-rfc7468", | ||
| 602 | + "zeroize", | ||
| 603 | +] | ||
| 604 | + | ||
| 605 | +[[package]] | ||
| 606 | +name = "deranged" | ||
| 607 | +version = "0.5.8" | ||
| 608 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 609 | +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" | ||
| 610 | + | ||
| 611 | +[[package]] | ||
| 612 | +name = "digest" | ||
| 613 | +version = "0.9.0" | ||
| 614 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 615 | +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" | ||
| 616 | +dependencies = [ | ||
| 617 | + "generic-array", | ||
| 618 | +] | ||
| 619 | + | ||
| 620 | +[[package]] | ||
| 621 | +name = "digest" | ||
| 622 | +version = "0.10.7" | ||
| 623 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 624 | +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" | ||
| 625 | +dependencies = [ | ||
| 626 | + "block-buffer 0.10.4", | ||
| 627 | + "const-oid", | ||
| 628 | + "crypto-common", | ||
| 629 | + "subtle", | ||
| 630 | +] | ||
| 631 | + | ||
| 632 | +[[package]] | ||
| 633 | +name = "displaydoc" | ||
| 634 | +version = "0.2.7" | ||
| 635 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 636 | +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" | ||
| 637 | +dependencies = [ | ||
| 638 | + "proc-macro2", | ||
| 639 | + "quote", | ||
| 640 | + "syn 3.0.3", | ||
| 641 | +] | ||
| 642 | + | ||
| 643 | +[[package]] | ||
| 644 | +name = "dotenvy" | ||
| 645 | +version = "0.15.7" | ||
| 646 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 647 | +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" | ||
| 648 | + | ||
| 649 | +[[package]] | ||
| 650 | +name = "ecdsa" | ||
| 651 | +version = "0.16.9" | ||
| 652 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 653 | +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" | ||
| 654 | +dependencies = [ | ||
| 655 | + "der 0.7.10", | ||
| 656 | + "digest 0.10.7", | ||
| 657 | + "elliptic-curve", | ||
| 658 | + "rfc6979", | ||
| 659 | + "serdect", | ||
| 660 | + "signature", | ||
| 661 | + "spki 0.7.3", | ||
| 662 | +] | ||
| 663 | + | ||
| 664 | +[[package]] | ||
| 665 | +name = "ed25519" | ||
| 666 | +version = "2.2.3" | ||
| 667 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 668 | +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" | ||
| 669 | +dependencies = [ | ||
| 670 | + "pkcs8 0.10.2", | ||
| 671 | + "signature", | ||
| 672 | +] | ||
| 673 | + | ||
| 674 | +[[package]] | ||
| 675 | +name = "ed25519-dalek" | ||
| 676 | +version = "2.2.0" | ||
| 677 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 678 | +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" | ||
| 679 | +dependencies = [ | ||
| 680 | + "curve25519-dalek", | ||
| 681 | + "ed25519", | ||
| 682 | + "rand_core", | ||
| 683 | + "serde", | ||
| 684 | + "sha2 0.10.9", | ||
| 685 | + "subtle", | ||
| 686 | + "zeroize", | ||
| 687 | +] | ||
| 688 | + | ||
| 689 | +[[package]] | ||
| 690 | +name = "either" | ||
| 691 | +version = "1.17.0" | ||
| 692 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 693 | +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" | ||
| 694 | +dependencies = [ | ||
| 695 | + "serde", | ||
| 696 | +] | ||
| 697 | + | ||
| 698 | +[[package]] | ||
| 699 | +name = "elliptic-curve" | ||
| 700 | +version = "0.13.8" | ||
| 701 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 702 | +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" | ||
| 703 | +dependencies = [ | ||
| 704 | + "base16ct", | ||
| 705 | + "crypto-bigint", | ||
| 706 | + "digest 0.10.7", | ||
| 707 | + "ff", | ||
| 708 | + "generic-array", | ||
| 709 | + "group", | ||
| 710 | + "pem-rfc7468", | ||
| 711 | + "pkcs8 0.10.2", | ||
| 712 | + "rand_core", | ||
| 713 | + "sec1", | ||
| 714 | + "serdect", | ||
| 715 | + "subtle", | ||
| 716 | + "zeroize", | ||
| 717 | +] | ||
| 718 | + | ||
| 719 | +[[package]] | ||
| 720 | +name = "equivalent" | ||
| 721 | +version = "1.0.2" | ||
| 722 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 723 | +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" | ||
| 724 | + | ||
| 725 | +[[package]] | ||
| 726 | +name = "errno" | ||
| 727 | +version = "0.3.14" | ||
| 728 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 729 | +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" | ||
| 730 | +dependencies = [ | ||
| 731 | + "libc", | ||
| 732 | + "windows-sys 0.61.2", | ||
| 733 | +] | ||
| 734 | + | ||
| 735 | +[[package]] | ||
| 736 | +name = "etcetera" | ||
| 737 | +version = "0.8.0" | ||
| 738 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 739 | +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" | ||
| 740 | +dependencies = [ | ||
| 741 | + "cfg-if", | ||
| 742 | + "home", | ||
| 743 | + "windows-sys 0.48.0", | ||
| 744 | +] | ||
| 745 | + | ||
| 746 | +[[package]] | ||
| 747 | +name = "event-listener" | ||
| 748 | +version = "5.4.2" | ||
| 749 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 750 | +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" | ||
| 751 | +dependencies = [ | ||
| 752 | + "parking", | ||
| 753 | + "pin-project-lite", | ||
| 754 | +] | ||
| 755 | + | ||
| 756 | +[[package]] | ||
| 757 | +name = "ff" | ||
| 758 | +version = "0.13.1" | ||
| 759 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 760 | +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" | ||
| 761 | +dependencies = [ | ||
| 762 | + "rand_core", | ||
| 763 | + "subtle", | ||
| 764 | +] | ||
| 765 | + | ||
| 766 | +[[package]] | ||
| 767 | +name = "fiat-crypto" | ||
| 768 | +version = "0.2.9" | ||
| 769 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 770 | +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" | ||
| 771 | + | ||
| 772 | +[[package]] | ||
| 773 | +name = "find-msvc-tools" | ||
| 774 | +version = "0.1.9" | ||
| 775 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 776 | +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" | ||
| 777 | + | ||
| 778 | +[[package]] | ||
| 779 | +name = "flume" | ||
| 780 | +version = "0.11.1" | ||
| 781 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 782 | +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" | ||
| 783 | +dependencies = [ | ||
| 784 | + "futures-core", | ||
| 785 | + "futures-sink", | ||
| 786 | + "spin", | ||
| 787 | +] | ||
| 788 | + | ||
| 789 | +[[package]] | ||
| 790 | +name = "foldhash" | ||
| 791 | +version = "0.1.5" | ||
| 792 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 793 | +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" | ||
| 794 | + | ||
| 795 | +[[package]] | ||
| 796 | +name = "form_urlencoded" | ||
| 797 | +version = "1.2.2" | ||
| 798 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 799 | +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" | ||
| 800 | +dependencies = [ | ||
| 801 | + "percent-encoding", | ||
| 802 | +] | ||
| 803 | + | ||
| 804 | +[[package]] | ||
| 805 | +name = "futures-channel" | ||
| 806 | +version = "0.3.33" | ||
| 807 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 808 | +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" | ||
| 809 | +dependencies = [ | ||
| 810 | + "futures-core", | ||
| 811 | + "futures-sink", | ||
| 812 | +] | ||
| 813 | + | ||
| 814 | +[[package]] | ||
| 815 | +name = "futures-core" | ||
| 816 | +version = "0.3.33" | ||
| 817 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 818 | +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" | ||
| 819 | + | ||
| 820 | +[[package]] | ||
| 821 | +name = "futures-executor" | ||
| 822 | +version = "0.3.33" | ||
| 823 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 824 | +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" | ||
| 825 | +dependencies = [ | ||
| 826 | + "futures-core", | ||
| 827 | + "futures-task", | ||
| 828 | + "futures-util", | ||
| 829 | +] | ||
| 830 | + | ||
| 831 | +[[package]] | ||
| 832 | +name = "futures-intrusive" | ||
| 833 | +version = "0.5.0" | ||
| 834 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 835 | +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" | ||
| 836 | +dependencies = [ | ||
| 837 | + "futures-core", | ||
| 838 | + "lock_api", | ||
| 839 | + "parking_lot", | ||
| 840 | +] | ||
| 841 | + | ||
| 842 | +[[package]] | ||
| 843 | +name = "futures-io" | ||
| 844 | +version = "0.3.33" | ||
| 845 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 846 | +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" | ||
| 847 | + | ||
| 848 | +[[package]] | ||
| 849 | +name = "futures-sink" | ||
| 850 | +version = "0.3.33" | ||
| 851 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 852 | +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" | ||
| 853 | + | ||
| 854 | +[[package]] | ||
| 855 | +name = "futures-task" | ||
| 856 | +version = "0.3.33" | ||
| 857 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 858 | +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" | ||
| 859 | + | ||
| 860 | +[[package]] | ||
| 861 | +name = "futures-util" | ||
| 862 | +version = "0.3.33" | ||
| 863 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 864 | +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" | ||
| 865 | +dependencies = [ | ||
| 866 | + "futures-core", | ||
| 867 | + "futures-io", | ||
| 868 | + "futures-sink", | ||
| 869 | + "futures-task", | ||
| 870 | + "memchr", | ||
| 871 | + "pin-project-lite", | ||
| 872 | + "slab", | ||
| 873 | +] | ||
| 874 | + | ||
| 875 | +[[package]] | ||
| 876 | +name = "generic-array" | ||
| 877 | +version = "0.14.9" | ||
| 878 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 879 | +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" | ||
| 880 | +dependencies = [ | ||
| 881 | + "typenum", | ||
| 882 | + "version_check", | ||
| 883 | + "zeroize", | ||
| 884 | +] | ||
| 885 | + | ||
| 886 | +[[package]] | ||
| 887 | +name = "getrandom" | ||
| 888 | +version = "0.2.17" | ||
| 889 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 890 | +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" | ||
| 891 | +dependencies = [ | ||
| 892 | + "cfg-if", | ||
| 893 | + "libc", | ||
| 894 | + "wasi", | ||
| 895 | +] | ||
| 896 | + | ||
| 897 | +[[package]] | ||
| 898 | +name = "getrandom" | ||
| 899 | +version = "0.4.3" | ||
| 900 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 901 | +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" | ||
| 902 | +dependencies = [ | ||
| 903 | + "cfg-if", | ||
| 904 | + "libc", | ||
| 905 | + "r-efi", | ||
| 906 | +] | ||
| 907 | + | ||
| 908 | +[[package]] | ||
| 909 | +name = "glob" | ||
| 910 | +version = "0.3.4" | ||
| 911 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 912 | +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" | ||
| 913 | + | ||
| 914 | +[[package]] | ||
| 915 | +name = "group" | ||
| 916 | +version = "0.13.0" | ||
| 917 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 918 | +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" | ||
| 919 | +dependencies = [ | ||
| 920 | + "ff", | ||
| 921 | + "rand_core", | ||
| 922 | + "subtle", | ||
| 923 | +] | ||
| 924 | + | ||
| 925 | +[[package]] | ||
| 926 | +name = "hashbrown" | ||
| 927 | +version = "0.15.5" | ||
| 928 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 929 | +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" | ||
| 930 | +dependencies = [ | ||
| 931 | + "allocator-api2", | ||
| 932 | + "equivalent", | ||
| 933 | + "foldhash", | ||
| 934 | +] | ||
| 935 | + | ||
| 936 | +[[package]] | ||
| 937 | +name = "hashbrown" | ||
| 938 | +version = "0.17.1" | ||
| 939 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 940 | +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" | ||
| 941 | + | ||
| 942 | +[[package]] | ||
| 943 | +name = "hashlink" | ||
| 944 | +version = "0.10.0" | ||
| 945 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 946 | +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" | ||
| 947 | +dependencies = [ | ||
| 948 | + "hashbrown 0.15.5", | ||
| 949 | +] | ||
| 950 | + | ||
| 951 | +[[package]] | ||
| 952 | +name = "heck" | ||
| 953 | +version = "0.5.0" | ||
| 954 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 955 | +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | ||
| 956 | + | ||
| 957 | +[[package]] | ||
| 958 | +name = "hex" | ||
| 959 | +version = "0.4.3" | ||
| 960 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 961 | +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" | ||
| 962 | + | ||
| 963 | +[[package]] | ||
| 964 | +name = "hkdf" | ||
| 965 | +version = "0.12.4" | ||
| 966 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 967 | +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" | ||
| 968 | +dependencies = [ | ||
| 969 | + "hmac", | ||
| 970 | +] | ||
| 971 | + | ||
| 972 | +[[package]] | ||
| 973 | +name = "hmac" | ||
| 974 | +version = "0.12.1" | ||
| 975 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 976 | +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" | ||
| 977 | +dependencies = [ | ||
| 978 | + "digest 0.10.7", | ||
| 979 | +] | ||
| 980 | + | ||
| 981 | +[[package]] | ||
| 982 | +name = "home" | ||
| 983 | +version = "0.5.12" | ||
| 984 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 985 | +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" | ||
| 986 | +dependencies = [ | ||
| 987 | + "windows-sys 0.61.2", | ||
| 988 | +] | ||
| 989 | + | ||
| 990 | +[[package]] | ||
| 991 | +name = "http" | ||
| 992 | +version = "1.5.0" | ||
| 993 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 994 | +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" | ||
| 995 | +dependencies = [ | ||
| 996 | + "bytes", | ||
| 997 | + "itoa", | ||
| 998 | +] | ||
| 999 | + | ||
| 1000 | +[[package]] | ||
| 1001 | +name = "http-body" | ||
| 1002 | +version = "1.1.0" | ||
| 1003 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1004 | +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" | ||
| 1005 | +dependencies = [ | ||
| 1006 | + "bytes", | ||
| 1007 | + "http", | ||
| 1008 | +] | ||
| 1009 | + | ||
| 1010 | +[[package]] | ||
| 1011 | +name = "http-body-util" | ||
| 1012 | +version = "0.1.4" | ||
| 1013 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1014 | +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" | ||
| 1015 | +dependencies = [ | ||
| 1016 | + "bytes", | ||
| 1017 | + "futures-core", | ||
| 1018 | + "http", | ||
| 1019 | + "http-body", | ||
| 1020 | + "pin-project-lite", | ||
| 1021 | +] | ||
| 1022 | + | ||
| 1023 | +[[package]] | ||
| 1024 | +name = "httparse" | ||
| 1025 | +version = "1.10.1" | ||
| 1026 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1027 | +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" | ||
| 1028 | + | ||
| 1029 | +[[package]] | ||
| 1030 | +name = "httpdate" | ||
| 1031 | +version = "1.0.3" | ||
| 1032 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1033 | +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" | ||
| 1034 | + | ||
| 1035 | +[[package]] | ||
| 1036 | +name = "hyper" | ||
| 1037 | +version = "1.11.0" | ||
| 1038 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1039 | +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" | ||
| 1040 | +dependencies = [ | ||
| 1041 | + "atomic-waker", | ||
| 1042 | + "bytes", | ||
| 1043 | + "futures-channel", | ||
| 1044 | + "futures-core", | ||
| 1045 | + "http", | ||
| 1046 | + "http-body", | ||
| 1047 | + "httparse", | ||
| 1048 | + "httpdate", | ||
| 1049 | + "itoa", | ||
| 1050 | + "pin-project-lite", | ||
| 1051 | + "smallvec", | ||
| 1052 | + "tokio", | ||
| 1053 | +] | ||
| 1054 | + | ||
| 1055 | +[[package]] | ||
| 1056 | +name = "hyper-util" | ||
| 1057 | +version = "0.1.20" | ||
| 1058 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1059 | +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" | ||
| 1060 | +dependencies = [ | ||
| 1061 | + "bytes", | ||
| 1062 | + "http", | ||
| 1063 | + "http-body", | ||
| 1064 | + "hyper", | ||
| 1065 | + "pin-project-lite", | ||
| 1066 | + "tokio", | ||
| 1067 | + "tower-service", | ||
| 1068 | +] | ||
| 1069 | + | ||
| 1070 | +[[package]] | ||
| 1071 | +name = "iana-time-zone" | ||
| 1072 | +version = "0.1.65" | ||
| 1073 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1074 | +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" | ||
| 1075 | +dependencies = [ | ||
| 1076 | + "android_system_properties", | ||
| 1077 | + "core-foundation-sys", | ||
| 1078 | + "iana-time-zone-haiku", | ||
| 1079 | + "js-sys", | ||
| 1080 | + "log", | ||
| 1081 | + "wasm-bindgen", | ||
| 1082 | + "windows-core", | ||
| 1083 | +] | ||
| 1084 | + | ||
| 1085 | +[[package]] | ||
| 1086 | +name = "iana-time-zone-haiku" | ||
| 1087 | +version = "0.1.2" | ||
| 1088 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1089 | +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" | ||
| 1090 | +dependencies = [ | ||
| 1091 | + "cc", | ||
| 1092 | +] | ||
| 1093 | + | ||
| 1094 | +[[package]] | ||
| 1095 | +name = "icu_collections" | ||
| 1096 | +version = "2.2.0" | ||
| 1097 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1098 | +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" | ||
| 1099 | +dependencies = [ | ||
| 1100 | + "displaydoc", | ||
| 1101 | + "potential_utf", | ||
| 1102 | + "utf8_iter", | ||
| 1103 | + "yoke", | ||
| 1104 | + "zerofrom", | ||
| 1105 | + "zerovec", | ||
| 1106 | +] | ||
| 1107 | + | ||
| 1108 | +[[package]] | ||
| 1109 | +name = "icu_locale_core" | ||
| 1110 | +version = "2.2.0" | ||
| 1111 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1112 | +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" | ||
| 1113 | +dependencies = [ | ||
| 1114 | + "displaydoc", | ||
| 1115 | + "litemap", | ||
| 1116 | + "tinystr", | ||
| 1117 | + "writeable", | ||
| 1118 | + "zerovec", | ||
| 1119 | +] | ||
| 1120 | + | ||
| 1121 | +[[package]] | ||
| 1122 | +name = "icu_normalizer" | ||
| 1123 | +version = "2.2.0" | ||
| 1124 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1125 | +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" | ||
| 1126 | +dependencies = [ | ||
| 1127 | + "icu_collections", | ||
| 1128 | + "icu_normalizer_data", | ||
| 1129 | + "icu_properties", | ||
| 1130 | + "icu_provider", | ||
| 1131 | + "smallvec", | ||
| 1132 | + "zerovec", | ||
| 1133 | +] | ||
| 1134 | + | ||
| 1135 | +[[package]] | ||
| 1136 | +name = "icu_normalizer_data" | ||
| 1137 | +version = "2.2.0" | ||
| 1138 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1139 | +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" | ||
| 1140 | + | ||
| 1141 | +[[package]] | ||
| 1142 | +name = "icu_properties" | ||
| 1143 | +version = "2.2.0" | ||
| 1144 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1145 | +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" | ||
| 1146 | +dependencies = [ | ||
| 1147 | + "icu_collections", | ||
| 1148 | + "icu_locale_core", | ||
| 1149 | + "icu_properties_data", | ||
| 1150 | + "icu_provider", | ||
| 1151 | + "zerotrie", | ||
| 1152 | + "zerovec", | ||
| 1153 | +] | ||
| 1154 | + | ||
| 1155 | +[[package]] | ||
| 1156 | +name = "icu_properties_data" | ||
| 1157 | +version = "2.2.0" | ||
| 1158 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1159 | +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" | ||
| 1160 | + | ||
| 1161 | +[[package]] | ||
| 1162 | +name = "icu_provider" | ||
| 1163 | +version = "2.2.0" | ||
| 1164 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1165 | +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" | ||
| 1166 | +dependencies = [ | ||
| 1167 | + "displaydoc", | ||
| 1168 | + "icu_locale_core", | ||
| 1169 | + "writeable", | ||
| 1170 | + "yoke", | ||
| 1171 | + "zerofrom", | ||
| 1172 | + "zerotrie", | ||
| 1173 | + "zerovec", | ||
| 1174 | +] | ||
| 1175 | + | ||
| 1176 | +[[package]] | ||
| 1177 | +name = "idna" | ||
| 1178 | +version = "1.1.0" | ||
| 1179 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1180 | +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" | ||
| 1181 | +dependencies = [ | ||
| 1182 | + "idna_adapter", | ||
| 1183 | + "smallvec", | ||
| 1184 | + "utf8_iter", | ||
| 1185 | +] | ||
| 1186 | + | ||
| 1187 | +[[package]] | ||
| 1188 | +name = "idna_adapter" | ||
| 1189 | +version = "1.2.2" | ||
| 1190 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1191 | +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" | ||
| 1192 | +dependencies = [ | ||
| 1193 | + "icu_normalizer", | ||
| 1194 | + "icu_properties", | ||
| 1195 | +] | ||
| 1196 | + | ||
| 1197 | +[[package]] | ||
| 1198 | +name = "indexmap" | ||
| 1199 | +version = "2.14.0" | ||
| 1200 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1201 | +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" | ||
| 1202 | +dependencies = [ | ||
| 1203 | + "equivalent", | ||
| 1204 | + "hashbrown 0.17.1", | ||
| 1205 | +] | ||
| 1206 | + | ||
| 1207 | +[[package]] | ||
| 1208 | +name = "is_terminal_polyfill" | ||
| 1209 | +version = "1.70.2" | ||
| 1210 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1211 | +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" | ||
| 1212 | + | ||
| 1213 | +[[package]] | ||
| 1214 | +name = "itertools" | ||
| 1215 | +version = "0.10.5" | ||
| 1216 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1217 | +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" | ||
| 1218 | +dependencies = [ | ||
| 1219 | + "either", | ||
| 1220 | +] | ||
| 1221 | + | ||
| 1222 | +[[package]] | ||
| 1223 | +name = "itoa" | ||
| 1224 | +version = "1.0.18" | ||
| 1225 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1226 | +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" | ||
| 1227 | + | ||
| 1228 | +[[package]] | ||
| 1229 | +name = "js-sys" | ||
| 1230 | +version = "0.3.103" | ||
| 1231 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1232 | +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" | ||
| 1233 | +dependencies = [ | ||
| 1234 | + "cfg-if", | ||
| 1235 | + "futures-util", | ||
| 1236 | + "wasm-bindgen", | ||
| 1237 | +] | ||
| 1238 | + | ||
| 1239 | +[[package]] | ||
| 1240 | +name = "lazy_static" | ||
| 1241 | +version = "1.5.0" | ||
| 1242 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1243 | +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" | ||
| 1244 | +dependencies = [ | ||
| 1245 | + "spin", | ||
| 1246 | +] | ||
| 1247 | + | ||
| 1248 | +[[package]] | ||
| 1249 | +name = "libc" | ||
| 1250 | +version = "0.2.189" | ||
| 1251 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1252 | +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" | ||
| 1253 | + | ||
| 1254 | +[[package]] | ||
| 1255 | +name = "libm" | ||
| 1256 | +version = "0.2.16" | ||
| 1257 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1258 | +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" | ||
| 1259 | + | ||
| 1260 | +[[package]] | ||
| 1261 | +name = "libredox" | ||
| 1262 | +version = "0.1.19" | ||
| 1263 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1264 | +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" | ||
| 1265 | +dependencies = [ | ||
| 1266 | + "bitflags", | ||
| 1267 | + "libc", | ||
| 1268 | + "plain", | ||
| 1269 | + "redox_syscall 0.9.1", | ||
| 1270 | +] | ||
| 1271 | + | ||
| 1272 | +[[package]] | ||
| 1273 | +name = "libsqlite3-sys" | ||
| 1274 | +version = "0.30.1" | ||
| 1275 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1276 | +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" | ||
| 1277 | +dependencies = [ | ||
| 1278 | + "pkg-config", | ||
| 1279 | + "vcpkg", | ||
| 1280 | +] | ||
| 1281 | + | ||
| 1282 | +[[package]] | ||
| 1283 | +name = "litemap" | ||
| 1284 | +version = "0.8.2" | ||
| 1285 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1286 | +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" | ||
| 1287 | + | ||
| 1288 | +[[package]] | ||
| 1289 | +name = "lock_api" | ||
| 1290 | +version = "0.4.14" | ||
| 1291 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1292 | +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" | ||
| 1293 | +dependencies = [ | ||
| 1294 | + "scopeguard", | ||
| 1295 | +] | ||
| 1296 | + | ||
| 1297 | +[[package]] | ||
| 1298 | +name = "log" | ||
| 1299 | +version = "0.4.33" | ||
| 1300 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1301 | +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" | ||
| 1302 | + | ||
| 1303 | +[[package]] | ||
| 1304 | +name = "matchers" | ||
| 1305 | +version = "0.2.0" | ||
| 1306 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1307 | +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" | ||
| 1308 | +dependencies = [ | ||
| 1309 | + "regex-automata", | ||
| 1310 | +] | ||
| 1311 | + | ||
| 1312 | +[[package]] | ||
| 1313 | +name = "matchit" | ||
| 1314 | +version = "0.8.4" | ||
| 1315 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1316 | +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" | ||
| 1317 | + | ||
| 1318 | +[[package]] | ||
| 1319 | +name = "md-5" | ||
| 1320 | +version = "0.10.6" | ||
| 1321 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1322 | +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" | ||
| 1323 | +dependencies = [ | ||
| 1324 | + "cfg-if", | ||
| 1325 | + "digest 0.10.7", | ||
| 1326 | +] | ||
| 1327 | + | ||
| 1328 | +[[package]] | ||
| 1329 | +name = "memchr" | ||
| 1330 | +version = "2.8.3" | ||
| 1331 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1332 | +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" | ||
| 1333 | + | ||
| 1334 | +[[package]] | ||
| 1335 | +name = "mime" | ||
| 1336 | +version = "0.3.17" | ||
| 1337 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1338 | +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" | ||
| 1339 | + | ||
| 1340 | +[[package]] | ||
| 1341 | +name = "minimal-lexical" | ||
| 1342 | +version = "0.2.1" | ||
| 1343 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1344 | +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" | ||
| 1345 | + | ||
| 1346 | +[[package]] | ||
| 1347 | +name = "mio" | ||
| 1348 | +version = "1.2.2" | ||
| 1349 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1350 | +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" | ||
| 1351 | +dependencies = [ | ||
| 1352 | + "libc", | ||
| 1353 | + "wasi", | ||
| 1354 | + "windows-sys 0.61.2", | ||
| 1355 | +] | ||
| 1356 | + | ||
| 1357 | +[[package]] | ||
| 1358 | +name = "nom" | ||
| 1359 | +version = "7.1.3" | ||
| 1360 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1361 | +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" | ||
| 1362 | +dependencies = [ | ||
| 1363 | + "memchr", | ||
| 1364 | + "minimal-lexical", | ||
| 1365 | +] | ||
| 1366 | + | ||
| 1367 | +[[package]] | ||
| 1368 | +name = "nu-ansi-term" | ||
| 1369 | +version = "0.50.3" | ||
| 1370 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1371 | +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" | ||
| 1372 | +dependencies = [ | ||
| 1373 | + "windows-sys 0.61.2", | ||
| 1374 | +] | ||
| 1375 | + | ||
| 1376 | +[[package]] | ||
| 1377 | +name = "num-bigint-dig" | ||
| 1378 | +version = "0.8.6" | ||
| 1379 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1380 | +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" | ||
| 1381 | +dependencies = [ | ||
| 1382 | + "lazy_static", | ||
| 1383 | + "libm", | ||
| 1384 | + "num-integer", | ||
| 1385 | + "num-iter", | ||
| 1386 | + "num-traits", | ||
| 1387 | + "rand", | ||
| 1388 | + "smallvec", | ||
| 1389 | + "zeroize", | ||
| 1390 | +] | ||
| 1391 | + | ||
| 1392 | +[[package]] | ||
| 1393 | +name = "num-conv" | ||
| 1394 | +version = "0.2.2" | ||
| 1395 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1396 | +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" | ||
| 1397 | + | ||
| 1398 | +[[package]] | ||
| 1399 | +name = "num-integer" | ||
| 1400 | +version = "0.1.46" | ||
| 1401 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1402 | +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" | ||
| 1403 | +dependencies = [ | ||
| 1404 | + "num-traits", | ||
| 1405 | +] | ||
| 1406 | + | ||
| 1407 | +[[package]] | ||
| 1408 | +name = "num-iter" | ||
| 1409 | +version = "0.1.46" | ||
| 1410 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1411 | +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" | ||
| 1412 | +dependencies = [ | ||
| 1413 | + "num-integer", | ||
| 1414 | + "num-traits", | ||
| 1415 | +] | ||
| 1416 | + | ||
| 1417 | +[[package]] | ||
| 1418 | +name = "num-traits" | ||
| 1419 | +version = "0.2.19" | ||
| 1420 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1421 | +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" | ||
| 1422 | +dependencies = [ | ||
| 1423 | + "autocfg", | ||
| 1424 | + "libm", | ||
| 1425 | +] | ||
| 1426 | + | ||
| 1427 | +[[package]] | ||
| 1428 | +name = "once_cell" | ||
| 1429 | +version = "1.21.4" | ||
| 1430 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1431 | +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" | ||
| 1432 | + | ||
| 1433 | +[[package]] | ||
| 1434 | +name = "once_cell_polyfill" | ||
| 1435 | +version = "1.70.2" | ||
| 1436 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1437 | +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" | ||
| 1438 | + | ||
| 1439 | +[[package]] | ||
| 1440 | +name = "opaque-debug" | ||
| 1441 | +version = "0.3.1" | ||
| 1442 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1443 | +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" | ||
| 1444 | + | ||
| 1445 | +[[package]] | ||
| 1446 | +name = "p256" | ||
| 1447 | +version = "0.13.2" | ||
| 1448 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1449 | +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" | ||
| 1450 | +dependencies = [ | ||
| 1451 | + "ecdsa", | ||
| 1452 | + "elliptic-curve", | ||
| 1453 | + "primeorder", | ||
| 1454 | + "sha2 0.10.9", | ||
| 1455 | +] | ||
| 1456 | + | ||
| 1457 | +[[package]] | ||
| 1458 | +name = "parking" | ||
| 1459 | +version = "2.2.1" | ||
| 1460 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1461 | +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" | ||
| 1462 | + | ||
| 1463 | +[[package]] | ||
| 1464 | +name = "parking_lot" | ||
| 1465 | +version = "0.12.5" | ||
| 1466 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1467 | +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" | ||
| 1468 | +dependencies = [ | ||
| 1469 | + "lock_api", | ||
| 1470 | + "parking_lot_core", | ||
| 1471 | +] | ||
| 1472 | + | ||
| 1473 | +[[package]] | ||
| 1474 | +name = "parking_lot_core" | ||
| 1475 | +version = "0.9.12" | ||
| 1476 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1477 | +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" | ||
| 1478 | +dependencies = [ | ||
| 1479 | + "cfg-if", | ||
| 1480 | + "libc", | ||
| 1481 | + "redox_syscall 0.5.18", | ||
| 1482 | + "smallvec", | ||
| 1483 | + "windows-link", | ||
| 1484 | +] | ||
| 1485 | + | ||
| 1486 | +[[package]] | ||
| 1487 | +name = "password-hash" | ||
| 1488 | +version = "0.5.0" | ||
| 1489 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1490 | +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" | ||
| 1491 | +dependencies = [ | ||
| 1492 | + "base64ct", | ||
| 1493 | + "rand_core", | ||
| 1494 | + "subtle", | ||
| 1495 | +] | ||
| 1496 | + | ||
| 1497 | +[[package]] | ||
| 1498 | +name = "pem-rfc7468" | ||
| 1499 | +version = "0.7.0" | ||
| 1500 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1501 | +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" | ||
| 1502 | +dependencies = [ | ||
| 1503 | + "base64ct", | ||
| 1504 | +] | ||
| 1505 | + | ||
| 1506 | +[[package]] | ||
| 1507 | +name = "percent-encoding" | ||
| 1508 | +version = "2.3.2" | ||
| 1509 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1510 | +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" | ||
| 1511 | + | ||
| 1512 | +[[package]] | ||
| 1513 | +name = "phf" | ||
| 1514 | +version = "0.12.1" | ||
| 1515 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1516 | +checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7" | ||
| 1517 | +dependencies = [ | ||
| 1518 | + "phf_shared", | ||
| 1519 | +] | ||
| 1520 | + | ||
| 1521 | +[[package]] | ||
| 1522 | +name = "phf_shared" | ||
| 1523 | +version = "0.12.1" | ||
| 1524 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1525 | +checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981" | ||
| 1526 | +dependencies = [ | ||
| 1527 | + "siphasher", | ||
| 1528 | +] | ||
| 1529 | + | ||
| 1530 | +[[package]] | ||
| 1531 | +name = "pin-project-lite" | ||
| 1532 | +version = "0.2.17" | ||
| 1533 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1534 | +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" | ||
| 1535 | + | ||
| 1536 | +[[package]] | ||
| 1537 | +name = "pkcs1" | ||
| 1538 | +version = "0.7.5" | ||
| 1539 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1540 | +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" | ||
| 1541 | +dependencies = [ | ||
| 1542 | + "der 0.7.10", | ||
| 1543 | + "pkcs8 0.10.2", | ||
| 1544 | + "spki 0.7.3", | ||
| 1545 | +] | ||
| 1546 | + | ||
| 1547 | +[[package]] | ||
| 1548 | +name = "pkcs8" | ||
| 1549 | +version = "0.9.0" | ||
| 1550 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1551 | +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" | ||
| 1552 | +dependencies = [ | ||
| 1553 | + "der 0.6.1", | ||
| 1554 | + "spki 0.6.0", | ||
| 1555 | +] | ||
| 1556 | + | ||
| 1557 | +[[package]] | ||
| 1558 | +name = "pkcs8" | ||
| 1559 | +version = "0.10.2" | ||
| 1560 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1561 | +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" | ||
| 1562 | +dependencies = [ | ||
| 1563 | + "der 0.7.10", | ||
| 1564 | + "spki 0.7.3", | ||
| 1565 | +] | ||
| 1566 | + | ||
| 1567 | +[[package]] | ||
| 1568 | +name = "pkg-config" | ||
| 1569 | +version = "0.3.33" | ||
| 1570 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1571 | +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" | ||
| 1572 | + | ||
| 1573 | +[[package]] | ||
| 1574 | +name = "plain" | ||
| 1575 | +version = "0.2.3" | ||
| 1576 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1577 | +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" | ||
| 1578 | + | ||
| 1579 | +[[package]] | ||
| 1580 | +name = "potential_utf" | ||
| 1581 | +version = "0.1.5" | ||
| 1582 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1583 | +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" | ||
| 1584 | +dependencies = [ | ||
| 1585 | + "zerovec", | ||
| 1586 | +] | ||
| 1587 | + | ||
| 1588 | +[[package]] | ||
| 1589 | +name = "powerfmt" | ||
| 1590 | +version = "0.2.0" | ||
| 1591 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1592 | +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" | ||
| 1593 | + | ||
| 1594 | +[[package]] | ||
| 1595 | +name = "ppv-lite86" | ||
| 1596 | +version = "0.2.21" | ||
| 1597 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1598 | +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" | ||
| 1599 | +dependencies = [ | ||
| 1600 | + "zerocopy", | ||
| 1601 | +] | ||
| 1602 | + | ||
| 1603 | +[[package]] | ||
| 1604 | +name = "primeorder" | ||
| 1605 | +version = "0.13.6" | ||
| 1606 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1607 | +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" | ||
| 1608 | +dependencies = [ | ||
| 1609 | + "elliptic-curve", | ||
| 1610 | +] | ||
| 1611 | + | ||
| 1612 | +[[package]] | ||
| 1613 | +name = "proc-macro-error-attr2" | ||
| 1614 | +version = "2.0.0" | ||
| 1615 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1616 | +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" | ||
| 1617 | +dependencies = [ | ||
| 1618 | + "proc-macro2", | ||
| 1619 | + "quote", | ||
| 1620 | +] | ||
| 1621 | + | ||
| 1622 | +[[package]] | ||
| 1623 | +name = "proc-macro-error2" | ||
| 1624 | +version = "2.0.1" | ||
| 1625 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1626 | +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" | ||
| 1627 | +dependencies = [ | ||
| 1628 | + "proc-macro-error-attr2", | ||
| 1629 | + "proc-macro2", | ||
| 1630 | + "quote", | ||
| 1631 | + "syn 2.0.119", | ||
| 1632 | +] | ||
| 1633 | + | ||
| 1634 | +[[package]] | ||
| 1635 | +name = "proc-macro2" | ||
| 1636 | +version = "1.0.107" | ||
| 1637 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1638 | +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" | ||
| 1639 | +dependencies = [ | ||
| 1640 | + "unicode-ident", | ||
| 1641 | +] | ||
| 1642 | + | ||
| 1643 | +[[package]] | ||
| 1644 | +name = "prost" | ||
| 1645 | +version = "0.10.4" | ||
| 1646 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1647 | +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" | ||
| 1648 | +dependencies = [ | ||
| 1649 | + "bytes", | ||
| 1650 | + "prost-derive", | ||
| 1651 | +] | ||
| 1652 | + | ||
| 1653 | +[[package]] | ||
| 1654 | +name = "prost-derive" | ||
| 1655 | +version = "0.10.1" | ||
| 1656 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1657 | +checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" | ||
| 1658 | +dependencies = [ | ||
| 1659 | + "anyhow", | ||
| 1660 | + "itertools", | ||
| 1661 | + "proc-macro2", | ||
| 1662 | + "quote", | ||
| 1663 | + "syn 1.0.109", | ||
| 1664 | +] | ||
| 1665 | + | ||
| 1666 | +[[package]] | ||
| 1667 | +name = "prost-types" | ||
| 1668 | +version = "0.10.1" | ||
| 1669 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1670 | +checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" | ||
| 1671 | +dependencies = [ | ||
| 1672 | + "bytes", | ||
| 1673 | + "prost", | ||
| 1674 | +] | ||
| 1675 | + | ||
| 1676 | +[[package]] | ||
| 1677 | +name = "quote" | ||
| 1678 | +version = "1.0.47" | ||
| 1679 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1680 | +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" | ||
| 1681 | +dependencies = [ | ||
| 1682 | + "proc-macro2", | ||
| 1683 | +] | ||
| 1684 | + | ||
| 1685 | +[[package]] | ||
| 1686 | +name = "r-efi" | ||
| 1687 | +version = "6.0.0" | ||
| 1688 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1689 | +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" | ||
| 1690 | + | ||
| 1691 | +[[package]] | ||
| 1692 | +name = "rand" | ||
| 1693 | +version = "0.8.7" | ||
| 1694 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1695 | +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" | ||
| 1696 | +dependencies = [ | ||
| 1697 | + "libc", | ||
| 1698 | + "rand_chacha", | ||
| 1699 | + "rand_core", | ||
| 1700 | +] | ||
| 1701 | + | ||
| 1702 | +[[package]] | ||
| 1703 | +name = "rand_chacha" | ||
| 1704 | +version = "0.3.1" | ||
| 1705 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1706 | +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" | ||
| 1707 | +dependencies = [ | ||
| 1708 | + "ppv-lite86", | ||
| 1709 | + "rand_core", | ||
| 1710 | +] | ||
| 1711 | + | ||
| 1712 | +[[package]] | ||
| 1713 | +name = "rand_core" | ||
| 1714 | +version = "0.6.4" | ||
| 1715 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1716 | +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" | ||
| 1717 | +dependencies = [ | ||
| 1718 | + "getrandom 0.2.17", | ||
| 1719 | +] | ||
| 1720 | + | ||
| 1721 | +[[package]] | ||
| 1722 | +name = "redox_syscall" | ||
| 1723 | +version = "0.5.18" | ||
| 1724 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1725 | +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" | ||
| 1726 | +dependencies = [ | ||
| 1727 | + "bitflags", | ||
| 1728 | +] | ||
| 1729 | + | ||
| 1730 | +[[package]] | ||
| 1731 | +name = "redox_syscall" | ||
| 1732 | +version = "0.9.1" | ||
| 1733 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1734 | +checksum = "07507be7b4a5f9f26eeb41eeaebb1f5a7ff29dfb29739facc21d35bf8b11c21e" | ||
| 1735 | +dependencies = [ | ||
| 1736 | + "bitflags", | ||
| 1737 | +] | ||
| 1738 | + | ||
| 1739 | +[[package]] | ||
| 1740 | +name = "regex" | ||
| 1741 | +version = "1.13.1" | ||
| 1742 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1743 | +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" | ||
| 1744 | +dependencies = [ | ||
| 1745 | + "aho-corasick", | ||
| 1746 | + "memchr", | ||
| 1747 | + "regex-automata", | ||
| 1748 | + "regex-syntax", | ||
| 1749 | +] | ||
| 1750 | + | ||
| 1751 | +[[package]] | ||
| 1752 | +name = "regex-automata" | ||
| 1753 | +version = "0.4.18" | ||
| 1754 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1755 | +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" | ||
| 1756 | +dependencies = [ | ||
| 1757 | + "aho-corasick", | ||
| 1758 | + "memchr", | ||
| 1759 | + "regex-syntax", | ||
| 1760 | +] | ||
| 1761 | + | ||
| 1762 | +[[package]] | ||
| 1763 | +name = "regex-syntax" | ||
| 1764 | +version = "0.8.11" | ||
| 1765 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1766 | +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" | ||
| 1767 | + | ||
| 1768 | +[[package]] | ||
| 1769 | +name = "rfc6979" | ||
| 1770 | +version = "0.4.0" | ||
| 1771 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1772 | +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" | ||
| 1773 | +dependencies = [ | ||
| 1774 | + "hmac", | ||
| 1775 | + "subtle", | ||
| 1776 | +] | ||
| 1777 | + | ||
| 1778 | +[[package]] | ||
| 1779 | +name = "ring" | ||
| 1780 | +version = "0.17.14" | ||
| 1781 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1782 | +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" | ||
| 1783 | +dependencies = [ | ||
| 1784 | + "cc", | ||
| 1785 | + "cfg-if", | ||
| 1786 | + "getrandom 0.2.17", | ||
| 1787 | + "libc", | ||
| 1788 | + "untrusted", | ||
| 1789 | + "windows-sys 0.52.0", | ||
| 1790 | +] | ||
| 1791 | + | ||
| 1792 | +[[package]] | ||
| 1793 | +name = "rpassword" | ||
| 1794 | +version = "7.5.4" | ||
| 1795 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1796 | +checksum = "2da316a15f47e3d053de9cb2c439650bd8fa4aaeb9365f2e5f27f492ff73c196" | ||
| 1797 | +dependencies = [ | ||
| 1798 | + "libc", | ||
| 1799 | + "rtoolbox", | ||
| 1800 | + "windows-sys 0.61.2", | ||
| 1801 | +] | ||
| 1802 | + | ||
| 1803 | +[[package]] | ||
| 1804 | +name = "rsa" | ||
| 1805 | +version = "0.9.10" | ||
| 1806 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1807 | +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" | ||
| 1808 | +dependencies = [ | ||
| 1809 | + "const-oid", | ||
| 1810 | + "digest 0.10.7", | ||
| 1811 | + "num-bigint-dig", | ||
| 1812 | + "num-integer", | ||
| 1813 | + "num-traits", | ||
| 1814 | + "pkcs1", | ||
| 1815 | + "pkcs8 0.10.2", | ||
| 1816 | + "rand_core", | ||
| 1817 | + "signature", | ||
| 1818 | + "spki 0.7.3", | ||
| 1819 | + "subtle", | ||
| 1820 | + "zeroize", | ||
| 1821 | +] | ||
| 1822 | + | ||
| 1823 | +[[package]] | ||
| 1824 | +name = "rtoolbox" | ||
| 1825 | +version = "0.0.5" | ||
| 1826 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1827 | +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" | ||
| 1828 | +dependencies = [ | ||
| 1829 | + "libc", | ||
| 1830 | + "windows-sys 0.59.0", | ||
| 1831 | +] | ||
| 1832 | + | ||
| 1833 | +[[package]] | ||
| 1834 | +name = "rustc-hash" | ||
| 1835 | +version = "2.1.3" | ||
| 1836 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1837 | +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" | ||
| 1838 | + | ||
| 1839 | +[[package]] | ||
| 1840 | +name = "rustc_version" | ||
| 1841 | +version = "0.4.1" | ||
| 1842 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1843 | +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" | ||
| 1844 | +dependencies = [ | ||
| 1845 | + "semver", | ||
| 1846 | +] | ||
| 1847 | + | ||
| 1848 | +[[package]] | ||
| 1849 | +name = "rustls" | ||
| 1850 | +version = "0.23.43" | ||
| 1851 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1852 | +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" | ||
| 1853 | +dependencies = [ | ||
| 1854 | + "once_cell", | ||
| 1855 | + "ring", | ||
| 1856 | + "rustls-pki-types", | ||
| 1857 | + "rustls-webpki", | ||
| 1858 | + "subtle", | ||
| 1859 | + "zeroize", | ||
| 1860 | +] | ||
| 1861 | + | ||
| 1862 | +[[package]] | ||
| 1863 | +name = "rustls-pki-types" | ||
| 1864 | +version = "1.15.1" | ||
| 1865 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1866 | +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" | ||
| 1867 | +dependencies = [ | ||
| 1868 | + "zeroize", | ||
| 1869 | +] | ||
| 1870 | + | ||
| 1871 | +[[package]] | ||
| 1872 | +name = "rustls-webpki" | ||
| 1873 | +version = "0.103.13" | ||
| 1874 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1875 | +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" | ||
| 1876 | +dependencies = [ | ||
| 1877 | + "ring", | ||
| 1878 | + "rustls-pki-types", | ||
| 1879 | + "untrusted", | ||
| 1880 | +] | ||
| 1881 | + | ||
| 1882 | +[[package]] | ||
| 1883 | +name = "rustversion" | ||
| 1884 | +version = "1.0.23" | ||
| 1885 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1886 | +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" | ||
| 1887 | + | ||
| 1888 | +[[package]] | ||
| 1889 | +name = "ryu" | ||
| 1890 | +version = "1.0.23" | ||
| 1891 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1892 | +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" | ||
| 1893 | + | ||
| 1894 | +[[package]] | ||
| 1895 | +name = "scopeguard" | ||
| 1896 | +version = "1.2.0" | ||
| 1897 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1898 | +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" | ||
| 1899 | + | ||
| 1900 | +[[package]] | ||
| 1901 | +name = "sec1" | ||
| 1902 | +version = "0.7.3" | ||
| 1903 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1904 | +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" | ||
| 1905 | +dependencies = [ | ||
| 1906 | + "base16ct", | ||
| 1907 | + "der 0.7.10", | ||
| 1908 | + "generic-array", | ||
| 1909 | + "pkcs8 0.10.2", | ||
| 1910 | + "serdect", | ||
| 1911 | + "subtle", | ||
| 1912 | + "zeroize", | ||
| 1913 | +] | ||
| 1914 | + | ||
| 1915 | +[[package]] | ||
| 1916 | +name = "semver" | ||
| 1917 | +version = "1.0.28" | ||
| 1918 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1919 | +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" | ||
| 1920 | + | ||
| 1921 | +[[package]] | ||
| 1922 | +name = "serde" | ||
| 1923 | +version = "1.0.229" | ||
| 1924 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1925 | +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" | ||
| 1926 | +dependencies = [ | ||
| 1927 | + "serde_core", | ||
| 1928 | + "serde_derive", | ||
| 1929 | +] | ||
| 1930 | + | ||
| 1931 | +[[package]] | ||
| 1932 | +name = "serde_core" | ||
| 1933 | +version = "1.0.229" | ||
| 1934 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1935 | +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" | ||
| 1936 | +dependencies = [ | ||
| 1937 | + "serde_derive", | ||
| 1938 | +] | ||
| 1939 | + | ||
| 1940 | +[[package]] | ||
| 1941 | +name = "serde_derive" | ||
| 1942 | +version = "1.0.229" | ||
| 1943 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1944 | +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" | ||
| 1945 | +dependencies = [ | ||
| 1946 | + "proc-macro2", | ||
| 1947 | + "quote", | ||
| 1948 | + "syn 3.0.3", | ||
| 1949 | +] | ||
| 1950 | + | ||
| 1951 | +[[package]] | ||
| 1952 | +name = "serde_json" | ||
| 1953 | +version = "1.0.151" | ||
| 1954 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1955 | +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" | ||
| 1956 | +dependencies = [ | ||
| 1957 | + "itoa", | ||
| 1958 | + "memchr", | ||
| 1959 | + "serde", | ||
| 1960 | + "serde_core", | ||
| 1961 | + "zmij", | ||
| 1962 | +] | ||
| 1963 | + | ||
| 1964 | +[[package]] | ||
| 1965 | +name = "serde_path_to_error" | ||
| 1966 | +version = "0.1.20" | ||
| 1967 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1968 | +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" | ||
| 1969 | +dependencies = [ | ||
| 1970 | + "itoa", | ||
| 1971 | + "serde", | ||
| 1972 | + "serde_core", | ||
| 1973 | +] | ||
| 1974 | + | ||
| 1975 | +[[package]] | ||
| 1976 | +name = "serde_urlencoded" | ||
| 1977 | +version = "0.7.1" | ||
| 1978 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1979 | +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" | ||
| 1980 | +dependencies = [ | ||
| 1981 | + "form_urlencoded", | ||
| 1982 | + "itoa", | ||
| 1983 | + "ryu", | ||
| 1984 | + "serde", | ||
| 1985 | +] | ||
| 1986 | + | ||
| 1987 | +[[package]] | ||
| 1988 | +name = "serdect" | ||
| 1989 | +version = "0.2.0" | ||
| 1990 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1991 | +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" | ||
| 1992 | +dependencies = [ | ||
| 1993 | + "base16ct", | ||
| 1994 | + "serde", | ||
| 1995 | +] | ||
| 1996 | + | ||
| 1997 | +[[package]] | ||
| 1998 | +name = "sha1" | ||
| 1999 | +version = "0.10.7" | ||
| 2000 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2001 | +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" | ||
| 2002 | +dependencies = [ | ||
| 2003 | + "cfg-if", | ||
| 2004 | + "cpufeatures", | ||
| 2005 | + "digest 0.10.7", | ||
| 2006 | +] | ||
| 2007 | + | ||
| 2008 | +[[package]] | ||
| 2009 | +name = "sha2" | ||
| 2010 | +version = "0.9.9" | ||
| 2011 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2012 | +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" | ||
| 2013 | +dependencies = [ | ||
| 2014 | + "block-buffer 0.9.0", | ||
| 2015 | + "cfg-if", | ||
| 2016 | + "cpufeatures", | ||
| 2017 | + "digest 0.9.0", | ||
| 2018 | + "opaque-debug", | ||
| 2019 | +] | ||
| 2020 | + | ||
| 2021 | +[[package]] | ||
| 2022 | +name = "sha2" | ||
| 2023 | +version = "0.10.9" | ||
| 2024 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2025 | +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" | ||
| 2026 | +dependencies = [ | ||
| 2027 | + "cfg-if", | ||
| 2028 | + "cpufeatures", | ||
| 2029 | + "digest 0.10.7", | ||
| 2030 | +] | ||
| 2031 | + | ||
| 2032 | +[[package]] | ||
| 2033 | +name = "sharded-slab" | ||
| 2034 | +version = "0.1.7" | ||
| 2035 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2036 | +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" | ||
| 2037 | +dependencies = [ | ||
| 2038 | + "lazy_static", | ||
| 2039 | +] | ||
| 2040 | + | ||
| 2041 | +[[package]] | ||
| 2042 | +name = "shlex" | ||
| 2043 | +version = "2.0.1" | ||
| 2044 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2045 | +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" | ||
| 2046 | + | ||
| 2047 | +[[package]] | ||
| 2048 | +name = "signal-hook-registry" | ||
| 2049 | +version = "1.4.8" | ||
| 2050 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2051 | +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" | ||
| 2052 | +dependencies = [ | ||
| 2053 | + "errno", | ||
| 2054 | + "libc", | ||
| 2055 | +] | ||
| 2056 | + | ||
| 2057 | +[[package]] | ||
| 2058 | +name = "signature" | ||
| 2059 | +version = "2.2.0" | ||
| 2060 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2061 | +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" | ||
| 2062 | +dependencies = [ | ||
| 2063 | + "digest 0.10.7", | ||
| 2064 | + "rand_core", | ||
| 2065 | +] | ||
| 2066 | + | ||
| 2067 | +[[package]] | ||
| 2068 | +name = "siphasher" | ||
| 2069 | +version = "1.0.3" | ||
| 2070 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2071 | +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" | ||
| 2072 | + | ||
| 2073 | +[[package]] | ||
| 2074 | +name = "slab" | ||
| 2075 | +version = "0.4.12" | ||
| 2076 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2077 | +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" | ||
| 2078 | + | ||
| 2079 | +[[package]] | ||
| 2080 | +name = "smallvec" | ||
| 2081 | +version = "1.15.2" | ||
| 2082 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2083 | +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" | ||
| 2084 | +dependencies = [ | ||
| 2085 | + "serde", | ||
| 2086 | +] | ||
| 2087 | + | ||
| 2088 | +[[package]] | ||
| 2089 | +name = "socket2" | ||
| 2090 | +version = "0.6.5" | ||
| 2091 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2092 | +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" | ||
| 2093 | +dependencies = [ | ||
| 2094 | + "libc", | ||
| 2095 | + "windows-sys 0.61.2", | ||
| 2096 | +] | ||
| 2097 | + | ||
| 2098 | +[[package]] | ||
| 2099 | +name = "spin" | ||
| 2100 | +version = "0.9.9" | ||
| 2101 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2102 | +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" | ||
| 2103 | +dependencies = [ | ||
| 2104 | + "lock_api", | ||
| 2105 | +] | ||
| 2106 | + | ||
| 2107 | +[[package]] | ||
| 2108 | +name = "spki" | ||
| 2109 | +version = "0.6.0" | ||
| 2110 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2111 | +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" | ||
| 2112 | +dependencies = [ | ||
| 2113 | + "der 0.6.1", | ||
| 2114 | +] | ||
| 2115 | + | ||
| 2116 | +[[package]] | ||
| 2117 | +name = "spki" | ||
| 2118 | +version = "0.7.3" | ||
| 2119 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2120 | +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" | ||
| 2121 | +dependencies = [ | ||
| 2122 | + "base64ct", | ||
| 2123 | + "der 0.7.10", | ||
| 2124 | +] | ||
| 2125 | + | ||
| 2126 | +[[package]] | ||
| 2127 | +name = "sqlx" | ||
| 2128 | +version = "0.8.6" | ||
| 2129 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2130 | +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" | ||
| 2131 | +dependencies = [ | ||
| 2132 | + "sqlx-core", | ||
| 2133 | + "sqlx-macros", | ||
| 2134 | + "sqlx-mysql", | ||
| 2135 | + "sqlx-postgres", | ||
| 2136 | + "sqlx-sqlite", | ||
| 2137 | +] | ||
| 2138 | + | ||
| 2139 | +[[package]] | ||
| 2140 | +name = "sqlx-core" | ||
| 2141 | +version = "0.8.6" | ||
| 2142 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2143 | +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" | ||
| 2144 | +dependencies = [ | ||
| 2145 | + "base64 0.22.1", | ||
| 2146 | + "bytes", | ||
| 2147 | + "chrono", | ||
| 2148 | + "crc", | ||
| 2149 | + "crossbeam-queue", | ||
| 2150 | + "either", | ||
| 2151 | + "event-listener", | ||
| 2152 | + "futures-core", | ||
| 2153 | + "futures-intrusive", | ||
| 2154 | + "futures-io", | ||
| 2155 | + "futures-util", | ||
| 2156 | + "hashbrown 0.15.5", | ||
| 2157 | + "hashlink", | ||
| 2158 | + "indexmap", | ||
| 2159 | + "log", | ||
| 2160 | + "memchr", | ||
| 2161 | + "once_cell", | ||
| 2162 | + "percent-encoding", | ||
| 2163 | + "rustls", | ||
| 2164 | + "serde", | ||
| 2165 | + "serde_json", | ||
| 2166 | + "sha2 0.10.9", | ||
| 2167 | + "smallvec", | ||
| 2168 | + "thiserror 2.0.19", | ||
| 2169 | + "tokio", | ||
| 2170 | + "tokio-stream", | ||
| 2171 | + "tracing", | ||
| 2172 | + "url", | ||
| 2173 | + "uuid", | ||
| 2174 | + "webpki-roots 0.26.11", | ||
| 2175 | +] | ||
| 2176 | + | ||
| 2177 | +[[package]] | ||
| 2178 | +name = "sqlx-macros" | ||
| 2179 | +version = "0.8.6" | ||
| 2180 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2181 | +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" | ||
| 2182 | +dependencies = [ | ||
| 2183 | + "proc-macro2", | ||
| 2184 | + "quote", | ||
| 2185 | + "sqlx-core", | ||
| 2186 | + "sqlx-macros-core", | ||
| 2187 | + "syn 2.0.119", | ||
| 2188 | +] | ||
| 2189 | + | ||
| 2190 | +[[package]] | ||
| 2191 | +name = "sqlx-macros-core" | ||
| 2192 | +version = "0.8.6" | ||
| 2193 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2194 | +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" | ||
| 2195 | +dependencies = [ | ||
| 2196 | + "dotenvy", | ||
| 2197 | + "either", | ||
| 2198 | + "heck", | ||
| 2199 | + "hex", | ||
| 2200 | + "once_cell", | ||
| 2201 | + "proc-macro2", | ||
| 2202 | + "quote", | ||
| 2203 | + "serde", | ||
| 2204 | + "serde_json", | ||
| 2205 | + "sha2 0.10.9", | ||
| 2206 | + "sqlx-core", | ||
| 2207 | + "sqlx-mysql", | ||
| 2208 | + "sqlx-postgres", | ||
| 2209 | + "sqlx-sqlite", | ||
| 2210 | + "syn 2.0.119", | ||
| 2211 | + "tokio", | ||
| 2212 | + "url", | ||
| 2213 | +] | ||
| 2214 | + | ||
| 2215 | +[[package]] | ||
| 2216 | +name = "sqlx-mysql" | ||
| 2217 | +version = "0.8.6" | ||
| 2218 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2219 | +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" | ||
| 2220 | +dependencies = [ | ||
| 2221 | + "atoi", | ||
| 2222 | + "base64 0.22.1", | ||
| 2223 | + "bitflags", | ||
| 2224 | + "byteorder", | ||
| 2225 | + "bytes", | ||
| 2226 | + "chrono", | ||
| 2227 | + "crc", | ||
| 2228 | + "digest 0.10.7", | ||
| 2229 | + "dotenvy", | ||
| 2230 | + "either", | ||
| 2231 | + "futures-channel", | ||
| 2232 | + "futures-core", | ||
| 2233 | + "futures-io", | ||
| 2234 | + "futures-util", | ||
| 2235 | + "generic-array", | ||
| 2236 | + "hex", | ||
| 2237 | + "hkdf", | ||
| 2238 | + "hmac", | ||
| 2239 | + "itoa", | ||
| 2240 | + "log", | ||
| 2241 | + "md-5", | ||
| 2242 | + "memchr", | ||
| 2243 | + "once_cell", | ||
| 2244 | + "percent-encoding", | ||
| 2245 | + "rand", | ||
| 2246 | + "rsa", | ||
| 2247 | + "serde", | ||
| 2248 | + "sha1", | ||
| 2249 | + "sha2 0.10.9", | ||
| 2250 | + "smallvec", | ||
| 2251 | + "sqlx-core", | ||
| 2252 | + "stringprep", | ||
| 2253 | + "thiserror 2.0.19", | ||
| 2254 | + "tracing", | ||
| 2255 | + "uuid", | ||
| 2256 | + "whoami", | ||
| 2257 | +] | ||
| 2258 | + | ||
| 2259 | +[[package]] | ||
| 2260 | +name = "sqlx-postgres" | ||
| 2261 | +version = "0.8.6" | ||
| 2262 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2263 | +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" | ||
| 2264 | +dependencies = [ | ||
| 2265 | + "atoi", | ||
| 2266 | + "base64 0.22.1", | ||
| 2267 | + "bitflags", | ||
| 2268 | + "byteorder", | ||
| 2269 | + "chrono", | ||
| 2270 | + "crc", | ||
| 2271 | + "dotenvy", | ||
| 2272 | + "etcetera", | ||
| 2273 | + "futures-channel", | ||
| 2274 | + "futures-core", | ||
| 2275 | + "futures-util", | ||
| 2276 | + "hex", | ||
| 2277 | + "hkdf", | ||
| 2278 | + "hmac", | ||
| 2279 | + "home", | ||
| 2280 | + "itoa", | ||
| 2281 | + "log", | ||
| 2282 | + "md-5", | ||
| 2283 | + "memchr", | ||
| 2284 | + "once_cell", | ||
| 2285 | + "rand", | ||
| 2286 | + "serde", | ||
| 2287 | + "serde_json", | ||
| 2288 | + "sha2 0.10.9", | ||
| 2289 | + "smallvec", | ||
| 2290 | + "sqlx-core", | ||
| 2291 | + "stringprep", | ||
| 2292 | + "thiserror 2.0.19", | ||
| 2293 | + "tracing", | ||
| 2294 | + "uuid", | ||
| 2295 | + "whoami", | ||
| 2296 | +] | ||
| 2297 | + | ||
| 2298 | +[[package]] | ||
| 2299 | +name = "sqlx-sqlite" | ||
| 2300 | +version = "0.8.6" | ||
| 2301 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2302 | +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" | ||
| 2303 | +dependencies = [ | ||
| 2304 | + "atoi", | ||
| 2305 | + "chrono", | ||
| 2306 | + "flume", | ||
| 2307 | + "futures-channel", | ||
| 2308 | + "futures-core", | ||
| 2309 | + "futures-executor", | ||
| 2310 | + "futures-intrusive", | ||
| 2311 | + "futures-util", | ||
| 2312 | + "libsqlite3-sys", | ||
| 2313 | + "log", | ||
| 2314 | + "percent-encoding", | ||
| 2315 | + "serde", | ||
| 2316 | + "serde_urlencoded", | ||
| 2317 | + "sqlx-core", | ||
| 2318 | + "thiserror 2.0.19", | ||
| 2319 | + "tracing", | ||
| 2320 | + "url", | ||
| 2321 | + "uuid", | ||
| 2322 | +] | ||
| 2323 | + | ||
| 2324 | +[[package]] | ||
| 2325 | +name = "stable_deref_trait" | ||
| 2326 | +version = "1.2.1" | ||
| 2327 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2328 | +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" | ||
| 2329 | + | ||
| 2330 | +[[package]] | ||
| 2331 | +name = "stringprep" | ||
| 2332 | +version = "0.1.5" | ||
| 2333 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2334 | +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" | ||
| 2335 | +dependencies = [ | ||
| 2336 | + "unicode-bidi", | ||
| 2337 | + "unicode-normalization", | ||
| 2338 | + "unicode-properties", | ||
| 2339 | +] | ||
| 2340 | + | ||
| 2341 | +[[package]] | ||
| 2342 | +name = "strsim" | ||
| 2343 | +version = "0.11.1" | ||
| 2344 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2345 | +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" | ||
| 2346 | + | ||
| 2347 | +[[package]] | ||
| 2348 | +name = "subtle" | ||
| 2349 | +version = "2.6.1" | ||
| 2350 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2351 | +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" | ||
| 2352 | + | ||
| 2353 | +[[package]] | ||
| 2354 | +name = "syn" | ||
| 2355 | +version = "1.0.109" | ||
| 2356 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2357 | +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" | ||
| 2358 | +dependencies = [ | ||
| 2359 | + "proc-macro2", | ||
| 2360 | + "quote", | ||
| 2361 | + "unicode-ident", | ||
| 2362 | +] | ||
| 2363 | + | ||
| 2364 | +[[package]] | ||
| 2365 | +name = "syn" | ||
| 2366 | +version = "2.0.119" | ||
| 2367 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2368 | +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" | ||
| 2369 | +dependencies = [ | ||
| 2370 | + "proc-macro2", | ||
| 2371 | + "quote", | ||
| 2372 | + "unicode-ident", | ||
| 2373 | +] | ||
| 2374 | + | ||
| 2375 | +[[package]] | ||
| 2376 | +name = "syn" | ||
| 2377 | +version = "3.0.3" | ||
| 2378 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2379 | +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" | ||
| 2380 | +dependencies = [ | ||
| 2381 | + "proc-macro2", | ||
| 2382 | + "quote", | ||
| 2383 | + "unicode-ident", | ||
| 2384 | +] | ||
| 2385 | + | ||
| 2386 | +[[package]] | ||
| 2387 | +name = "sync_wrapper" | ||
| 2388 | +version = "1.0.2" | ||
| 2389 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2390 | +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" | ||
| 2391 | + | ||
| 2392 | +[[package]] | ||
| 2393 | +name = "synstructure" | ||
| 2394 | +version = "0.13.2" | ||
| 2395 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2396 | +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" | ||
| 2397 | +dependencies = [ | ||
| 2398 | + "proc-macro2", | ||
| 2399 | + "quote", | ||
| 2400 | + "syn 2.0.119", | ||
| 2401 | +] | ||
| 2402 | + | ||
| 2403 | +[[package]] | ||
| 2404 | +name = "thiserror" | ||
| 2405 | +version = "1.0.69" | ||
| 2406 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2407 | +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" | ||
| 2408 | +dependencies = [ | ||
| 2409 | + "thiserror-impl 1.0.69", | ||
| 2410 | +] | ||
| 2411 | + | ||
| 2412 | +[[package]] | ||
| 2413 | +name = "thiserror" | ||
| 2414 | +version = "2.0.19" | ||
| 2415 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2416 | +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" | ||
| 2417 | +dependencies = [ | ||
| 2418 | + "thiserror-impl 2.0.19", | ||
| 2419 | +] | ||
| 2420 | + | ||
| 2421 | +[[package]] | ||
| 2422 | +name = "thiserror-impl" | ||
| 2423 | +version = "1.0.69" | ||
| 2424 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2425 | +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" | ||
| 2426 | +dependencies = [ | ||
| 2427 | + "proc-macro2", | ||
| 2428 | + "quote", | ||
| 2429 | + "syn 2.0.119", | ||
| 2430 | +] | ||
| 2431 | + | ||
| 2432 | +[[package]] | ||
| 2433 | +name = "thiserror-impl" | ||
| 2434 | +version = "2.0.19" | ||
| 2435 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2436 | +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" | ||
| 2437 | +dependencies = [ | ||
| 2438 | + "proc-macro2", | ||
| 2439 | + "quote", | ||
| 2440 | + "syn 3.0.3", | ||
| 2441 | +] | ||
| 2442 | + | ||
| 2443 | +[[package]] | ||
| 2444 | +name = "thread_local" | ||
| 2445 | +version = "1.1.10" | ||
| 2446 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2447 | +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" | ||
| 2448 | +dependencies = [ | ||
| 2449 | + "cfg-if", | ||
| 2450 | +] | ||
| 2451 | + | ||
| 2452 | +[[package]] | ||
| 2453 | +name = "time" | ||
| 2454 | +version = "0.3.55" | ||
| 2455 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2456 | +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" | ||
| 2457 | +dependencies = [ | ||
| 2458 | + "deranged", | ||
| 2459 | + "num-conv", | ||
| 2460 | + "powerfmt", | ||
| 2461 | + "serde_core", | ||
| 2462 | + "time-core", | ||
| 2463 | + "time-macros", | ||
| 2464 | +] | ||
| 2465 | + | ||
| 2466 | +[[package]] | ||
| 2467 | +name = "time-core" | ||
| 2468 | +version = "0.1.9" | ||
| 2469 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2470 | +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" | ||
| 2471 | + | ||
| 2472 | +[[package]] | ||
| 2473 | +name = "time-macros" | ||
| 2474 | +version = "0.2.32" | ||
| 2475 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2476 | +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" | ||
| 2477 | +dependencies = [ | ||
| 2478 | + "num-conv", | ||
| 2479 | + "time-core", | ||
| 2480 | +] | ||
| 2481 | + | ||
| 2482 | +[[package]] | ||
| 2483 | +name = "time-time" | ||
| 2484 | +version = "0.1.0" | ||
| 2485 | +dependencies = [ | ||
| 2486 | + "anyhow", | ||
| 2487 | + "argon2", | ||
| 2488 | + "askama", | ||
| 2489 | + "axum", | ||
| 2490 | + "axum-extra", | ||
| 2491 | + "biscuit-auth", | ||
| 2492 | + "chrono", | ||
| 2493 | + "chrono-tz", | ||
| 2494 | + "clap", | ||
| 2495 | + "hex", | ||
| 2496 | + "hmac", | ||
| 2497 | + "rpassword", | ||
| 2498 | + "serde", | ||
| 2499 | + "sha2 0.10.9", | ||
| 2500 | + "sqlx", | ||
| 2501 | + "subtle", | ||
| 2502 | + "thiserror 2.0.19", | ||
| 2503 | + "time", | ||
| 2504 | + "tokio", | ||
| 2505 | + "tracing", | ||
| 2506 | + "tracing-subscriber", | ||
| 2507 | + "uuid", | ||
| 2508 | +] | ||
| 2509 | + | ||
| 2510 | +[[package]] | ||
| 2511 | +name = "tinystr" | ||
| 2512 | +version = "0.8.3" | ||
| 2513 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2514 | +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" | ||
| 2515 | +dependencies = [ | ||
| 2516 | + "displaydoc", | ||
| 2517 | + "zerovec", | ||
| 2518 | +] | ||
| 2519 | + | ||
| 2520 | +[[package]] | ||
| 2521 | +name = "tinyvec" | ||
| 2522 | +version = "1.12.0" | ||
| 2523 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2524 | +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" | ||
| 2525 | +dependencies = [ | ||
| 2526 | + "tinyvec_macros", | ||
| 2527 | +] | ||
| 2528 | + | ||
| 2529 | +[[package]] | ||
| 2530 | +name = "tinyvec_macros" | ||
| 2531 | +version = "0.1.1" | ||
| 2532 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2533 | +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" | ||
| 2534 | + | ||
| 2535 | +[[package]] | ||
| 2536 | +name = "tokio" | ||
| 2537 | +version = "1.53.1" | ||
| 2538 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2539 | +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" | ||
| 2540 | +dependencies = [ | ||
| 2541 | + "bytes", | ||
| 2542 | + "libc", | ||
| 2543 | + "mio", | ||
| 2544 | + "pin-project-lite", | ||
| 2545 | + "signal-hook-registry", | ||
| 2546 | + "socket2", | ||
| 2547 | + "tokio-macros", | ||
| 2548 | + "windows-sys 0.61.2", | ||
| 2549 | +] | ||
| 2550 | + | ||
| 2551 | +[[package]] | ||
| 2552 | +name = "tokio-macros" | ||
| 2553 | +version = "2.7.2" | ||
| 2554 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2555 | +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" | ||
| 2556 | +dependencies = [ | ||
| 2557 | + "proc-macro2", | ||
| 2558 | + "quote", | ||
| 2559 | + "syn 3.0.3", | ||
| 2560 | +] | ||
| 2561 | + | ||
| 2562 | +[[package]] | ||
| 2563 | +name = "tokio-stream" | ||
| 2564 | +version = "0.1.19" | ||
| 2565 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2566 | +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" | ||
| 2567 | +dependencies = [ | ||
| 2568 | + "futures-core", | ||
| 2569 | + "pin-project-lite", | ||
| 2570 | + "tokio", | ||
| 2571 | +] | ||
| 2572 | + | ||
| 2573 | +[[package]] | ||
| 2574 | +name = "tower" | ||
| 2575 | +version = "0.5.3" | ||
| 2576 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2577 | +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" | ||
| 2578 | +dependencies = [ | ||
| 2579 | + "futures-core", | ||
| 2580 | + "futures-util", | ||
| 2581 | + "pin-project-lite", | ||
| 2582 | + "sync_wrapper", | ||
| 2583 | + "tokio", | ||
| 2584 | + "tower-layer", | ||
| 2585 | + "tower-service", | ||
| 2586 | + "tracing", | ||
| 2587 | +] | ||
| 2588 | + | ||
| 2589 | +[[package]] | ||
| 2590 | +name = "tower-layer" | ||
| 2591 | +version = "0.3.3" | ||
| 2592 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2593 | +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" | ||
| 2594 | + | ||
| 2595 | +[[package]] | ||
| 2596 | +name = "tower-service" | ||
| 2597 | +version = "0.3.3" | ||
| 2598 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2599 | +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" | ||
| 2600 | + | ||
| 2601 | +[[package]] | ||
| 2602 | +name = "tracing" | ||
| 2603 | +version = "0.1.44" | ||
| 2604 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2605 | +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" | ||
| 2606 | +dependencies = [ | ||
| 2607 | + "log", | ||
| 2608 | + "pin-project-lite", | ||
| 2609 | + "tracing-attributes", | ||
| 2610 | + "tracing-core", | ||
| 2611 | +] | ||
| 2612 | + | ||
| 2613 | +[[package]] | ||
| 2614 | +name = "tracing-attributes" | ||
| 2615 | +version = "0.1.31" | ||
| 2616 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2617 | +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" | ||
| 2618 | +dependencies = [ | ||
| 2619 | + "proc-macro2", | ||
| 2620 | + "quote", | ||
| 2621 | + "syn 2.0.119", | ||
| 2622 | +] | ||
| 2623 | + | ||
| 2624 | +[[package]] | ||
| 2625 | +name = "tracing-core" | ||
| 2626 | +version = "0.1.36" | ||
| 2627 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2628 | +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" | ||
| 2629 | +dependencies = [ | ||
| 2630 | + "once_cell", | ||
| 2631 | + "valuable", | ||
| 2632 | +] | ||
| 2633 | + | ||
| 2634 | +[[package]] | ||
| 2635 | +name = "tracing-log" | ||
| 2636 | +version = "0.2.0" | ||
| 2637 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2638 | +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" | ||
| 2639 | +dependencies = [ | ||
| 2640 | + "log", | ||
| 2641 | + "once_cell", | ||
| 2642 | + "tracing-core", | ||
| 2643 | +] | ||
| 2644 | + | ||
| 2645 | +[[package]] | ||
| 2646 | +name = "tracing-subscriber" | ||
| 2647 | +version = "0.3.23" | ||
| 2648 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2649 | +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" | ||
| 2650 | +dependencies = [ | ||
| 2651 | + "matchers", | ||
| 2652 | + "nu-ansi-term", | ||
| 2653 | + "once_cell", | ||
| 2654 | + "regex-automata", | ||
| 2655 | + "sharded-slab", | ||
| 2656 | + "smallvec", | ||
| 2657 | + "thread_local", | ||
| 2658 | + "tracing", | ||
| 2659 | + "tracing-core", | ||
| 2660 | + "tracing-log", | ||
| 2661 | +] | ||
| 2662 | + | ||
| 2663 | +[[package]] | ||
| 2664 | +name = "typenum" | ||
| 2665 | +version = "1.20.1" | ||
| 2666 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2667 | +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" | ||
| 2668 | + | ||
| 2669 | +[[package]] | ||
| 2670 | +name = "unicode-bidi" | ||
| 2671 | +version = "0.3.18" | ||
| 2672 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2673 | +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" | ||
| 2674 | + | ||
| 2675 | +[[package]] | ||
| 2676 | +name = "unicode-ident" | ||
| 2677 | +version = "1.0.24" | ||
| 2678 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2679 | +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" | ||
| 2680 | + | ||
| 2681 | +[[package]] | ||
| 2682 | +name = "unicode-normalization" | ||
| 2683 | +version = "0.1.25" | ||
| 2684 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2685 | +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" | ||
| 2686 | +dependencies = [ | ||
| 2687 | + "tinyvec", | ||
| 2688 | +] | ||
| 2689 | + | ||
| 2690 | +[[package]] | ||
| 2691 | +name = "unicode-properties" | ||
| 2692 | +version = "0.1.4" | ||
| 2693 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2694 | +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" | ||
| 2695 | + | ||
| 2696 | +[[package]] | ||
| 2697 | +name = "untrusted" | ||
| 2698 | +version = "0.9.0" | ||
| 2699 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2700 | +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" | ||
| 2701 | + | ||
| 2702 | +[[package]] | ||
| 2703 | +name = "url" | ||
| 2704 | +version = "2.5.8" | ||
| 2705 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2706 | +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" | ||
| 2707 | +dependencies = [ | ||
| 2708 | + "form_urlencoded", | ||
| 2709 | + "idna", | ||
| 2710 | + "percent-encoding", | ||
| 2711 | + "serde", | ||
| 2712 | +] | ||
| 2713 | + | ||
| 2714 | +[[package]] | ||
| 2715 | +name = "utf8_iter" | ||
| 2716 | +version = "1.0.4" | ||
| 2717 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2718 | +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" | ||
| 2719 | + | ||
| 2720 | +[[package]] | ||
| 2721 | +name = "utf8parse" | ||
| 2722 | +version = "0.2.2" | ||
| 2723 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2724 | +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" | ||
| 2725 | + | ||
| 2726 | +[[package]] | ||
| 2727 | +name = "uuid" | ||
| 2728 | +version = "1.24.0" | ||
| 2729 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2730 | +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" | ||
| 2731 | +dependencies = [ | ||
| 2732 | + "getrandom 0.4.3", | ||
| 2733 | + "js-sys", | ||
| 2734 | + "serde_core", | ||
| 2735 | + "wasm-bindgen", | ||
| 2736 | +] | ||
| 2737 | + | ||
| 2738 | +[[package]] | ||
| 2739 | +name = "valuable" | ||
| 2740 | +version = "0.1.1" | ||
| 2741 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2742 | +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" | ||
| 2743 | + | ||
| 2744 | +[[package]] | ||
| 2745 | +name = "vcpkg" | ||
| 2746 | +version = "0.2.15" | ||
| 2747 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2748 | +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" | ||
| 2749 | + | ||
| 2750 | +[[package]] | ||
| 2751 | +name = "version_check" | ||
| 2752 | +version = "0.9.5" | ||
| 2753 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2754 | +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" | ||
| 2755 | + | ||
| 2756 | +[[package]] | ||
| 2757 | +name = "wasi" | ||
| 2758 | +version = "0.11.1+wasi-snapshot-preview1" | ||
| 2759 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2760 | +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" | ||
| 2761 | + | ||
| 2762 | +[[package]] | ||
| 2763 | +name = "wasite" | ||
| 2764 | +version = "0.1.0" | ||
| 2765 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2766 | +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" | ||
| 2767 | + | ||
| 2768 | +[[package]] | ||
| 2769 | +name = "wasm-bindgen" | ||
| 2770 | +version = "0.2.126" | ||
| 2771 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2772 | +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" | ||
| 2773 | +dependencies = [ | ||
| 2774 | + "cfg-if", | ||
| 2775 | + "once_cell", | ||
| 2776 | + "rustversion", | ||
| 2777 | + "wasm-bindgen-macro", | ||
| 2778 | + "wasm-bindgen-shared", | ||
| 2779 | +] | ||
| 2780 | + | ||
| 2781 | +[[package]] | ||
| 2782 | +name = "wasm-bindgen-macro" | ||
| 2783 | +version = "0.2.126" | ||
| 2784 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2785 | +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" | ||
| 2786 | +dependencies = [ | ||
| 2787 | + "quote", | ||
| 2788 | + "wasm-bindgen-macro-support", | ||
| 2789 | +] | ||
| 2790 | + | ||
| 2791 | +[[package]] | ||
| 2792 | +name = "wasm-bindgen-macro-support" | ||
| 2793 | +version = "0.2.126" | ||
| 2794 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2795 | +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" | ||
| 2796 | +dependencies = [ | ||
| 2797 | + "bumpalo", | ||
| 2798 | + "proc-macro2", | ||
| 2799 | + "quote", | ||
| 2800 | + "syn 2.0.119", | ||
| 2801 | + "wasm-bindgen-shared", | ||
| 2802 | +] | ||
| 2803 | + | ||
| 2804 | +[[package]] | ||
| 2805 | +name = "wasm-bindgen-shared" | ||
| 2806 | +version = "0.2.126" | ||
| 2807 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2808 | +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" | ||
| 2809 | +dependencies = [ | ||
| 2810 | + "unicode-ident", | ||
| 2811 | +] | ||
| 2812 | + | ||
| 2813 | +[[package]] | ||
| 2814 | +name = "webpki-roots" | ||
| 2815 | +version = "0.26.11" | ||
| 2816 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2817 | +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" | ||
| 2818 | +dependencies = [ | ||
| 2819 | + "webpki-roots 1.0.9", | ||
| 2820 | +] | ||
| 2821 | + | ||
| 2822 | +[[package]] | ||
| 2823 | +name = "webpki-roots" | ||
| 2824 | +version = "1.0.9" | ||
| 2825 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2826 | +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" | ||
| 2827 | +dependencies = [ | ||
| 2828 | + "rustls-pki-types", | ||
| 2829 | +] | ||
| 2830 | + | ||
| 2831 | +[[package]] | ||
| 2832 | +name = "whoami" | ||
| 2833 | +version = "1.6.1" | ||
| 2834 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2835 | +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" | ||
| 2836 | +dependencies = [ | ||
| 2837 | + "libredox", | ||
| 2838 | + "wasite", | ||
| 2839 | +] | ||
| 2840 | + | ||
| 2841 | +[[package]] | ||
| 2842 | +name = "windows-core" | ||
| 2843 | +version = "0.62.2" | ||
| 2844 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2845 | +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" | ||
| 2846 | +dependencies = [ | ||
| 2847 | + "windows-implement", | ||
| 2848 | + "windows-interface", | ||
| 2849 | + "windows-link", | ||
| 2850 | + "windows-result", | ||
| 2851 | + "windows-strings", | ||
| 2852 | +] | ||
| 2853 | + | ||
| 2854 | +[[package]] | ||
| 2855 | +name = "windows-implement" | ||
| 2856 | +version = "0.60.2" | ||
| 2857 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2858 | +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" | ||
| 2859 | +dependencies = [ | ||
| 2860 | + "proc-macro2", | ||
| 2861 | + "quote", | ||
| 2862 | + "syn 2.0.119", | ||
| 2863 | +] | ||
| 2864 | + | ||
| 2865 | +[[package]] | ||
| 2866 | +name = "windows-interface" | ||
| 2867 | +version = "0.59.3" | ||
| 2868 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2869 | +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" | ||
| 2870 | +dependencies = [ | ||
| 2871 | + "proc-macro2", | ||
| 2872 | + "quote", | ||
| 2873 | + "syn 2.0.119", | ||
| 2874 | +] | ||
| 2875 | + | ||
| 2876 | +[[package]] | ||
| 2877 | +name = "windows-link" | ||
| 2878 | +version = "0.2.1" | ||
| 2879 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2880 | +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" | ||
| 2881 | + | ||
| 2882 | +[[package]] | ||
| 2883 | +name = "windows-result" | ||
| 2884 | +version = "0.4.1" | ||
| 2885 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2886 | +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" | ||
| 2887 | +dependencies = [ | ||
| 2888 | + "windows-link", | ||
| 2889 | +] | ||
| 2890 | + | ||
| 2891 | +[[package]] | ||
| 2892 | +name = "windows-strings" | ||
| 2893 | +version = "0.5.1" | ||
| 2894 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2895 | +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" | ||
| 2896 | +dependencies = [ | ||
| 2897 | + "windows-link", | ||
| 2898 | +] | ||
| 2899 | + | ||
| 2900 | +[[package]] | ||
| 2901 | +name = "windows-sys" | ||
| 2902 | +version = "0.48.0" | ||
| 2903 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2904 | +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" | ||
| 2905 | +dependencies = [ | ||
| 2906 | + "windows-targets 0.48.5", | ||
| 2907 | +] | ||
| 2908 | + | ||
| 2909 | +[[package]] | ||
| 2910 | +name = "windows-sys" | ||
| 2911 | +version = "0.52.0" | ||
| 2912 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2913 | +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" | ||
| 2914 | +dependencies = [ | ||
| 2915 | + "windows-targets 0.52.6", | ||
| 2916 | +] | ||
| 2917 | + | ||
| 2918 | +[[package]] | ||
| 2919 | +name = "windows-sys" | ||
| 2920 | +version = "0.59.0" | ||
| 2921 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2922 | +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" | ||
| 2923 | +dependencies = [ | ||
| 2924 | + "windows-targets 0.52.6", | ||
| 2925 | +] | ||
| 2926 | + | ||
| 2927 | +[[package]] | ||
| 2928 | +name = "windows-sys" | ||
| 2929 | +version = "0.61.2" | ||
| 2930 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2931 | +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" | ||
| 2932 | +dependencies = [ | ||
| 2933 | + "windows-link", | ||
| 2934 | +] | ||
| 2935 | + | ||
| 2936 | +[[package]] | ||
| 2937 | +name = "windows-targets" | ||
| 2938 | +version = "0.48.5" | ||
| 2939 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2940 | +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" | ||
| 2941 | +dependencies = [ | ||
| 2942 | + "windows_aarch64_gnullvm 0.48.5", | ||
| 2943 | + "windows_aarch64_msvc 0.48.5", | ||
| 2944 | + "windows_i686_gnu 0.48.5", | ||
| 2945 | + "windows_i686_msvc 0.48.5", | ||
| 2946 | + "windows_x86_64_gnu 0.48.5", | ||
| 2947 | + "windows_x86_64_gnullvm 0.48.5", | ||
| 2948 | + "windows_x86_64_msvc 0.48.5", | ||
| 2949 | +] | ||
| 2950 | + | ||
| 2951 | +[[package]] | ||
| 2952 | +name = "windows-targets" | ||
| 2953 | +version = "0.52.6" | ||
| 2954 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2955 | +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" | ||
| 2956 | +dependencies = [ | ||
| 2957 | + "windows_aarch64_gnullvm 0.52.6", | ||
| 2958 | + "windows_aarch64_msvc 0.52.6", | ||
| 2959 | + "windows_i686_gnu 0.52.6", | ||
| 2960 | + "windows_i686_gnullvm", | ||
| 2961 | + "windows_i686_msvc 0.52.6", | ||
| 2962 | + "windows_x86_64_gnu 0.52.6", | ||
| 2963 | + "windows_x86_64_gnullvm 0.52.6", | ||
| 2964 | + "windows_x86_64_msvc 0.52.6", | ||
| 2965 | +] | ||
| 2966 | + | ||
| 2967 | +[[package]] | ||
| 2968 | +name = "windows_aarch64_gnullvm" | ||
| 2969 | +version = "0.48.5" | ||
| 2970 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2971 | +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" | ||
| 2972 | + | ||
| 2973 | +[[package]] | ||
| 2974 | +name = "windows_aarch64_gnullvm" | ||
| 2975 | +version = "0.52.6" | ||
| 2976 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2977 | +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" | ||
| 2978 | + | ||
| 2979 | +[[package]] | ||
| 2980 | +name = "windows_aarch64_msvc" | ||
| 2981 | +version = "0.48.5" | ||
| 2982 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2983 | +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" | ||
| 2984 | + | ||
| 2985 | +[[package]] | ||
| 2986 | +name = "windows_aarch64_msvc" | ||
| 2987 | +version = "0.52.6" | ||
| 2988 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2989 | +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" | ||
| 2990 | + | ||
| 2991 | +[[package]] | ||
| 2992 | +name = "windows_i686_gnu" | ||
| 2993 | +version = "0.48.5" | ||
| 2994 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2995 | +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" | ||
| 2996 | + | ||
| 2997 | +[[package]] | ||
| 2998 | +name = "windows_i686_gnu" | ||
| 2999 | +version = "0.52.6" | ||
| 3000 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3001 | +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" | ||
| 3002 | + | ||
| 3003 | +[[package]] | ||
| 3004 | +name = "windows_i686_gnullvm" | ||
| 3005 | +version = "0.52.6" | ||
| 3006 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3007 | +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" | ||
| 3008 | + | ||
| 3009 | +[[package]] | ||
| 3010 | +name = "windows_i686_msvc" | ||
| 3011 | +version = "0.48.5" | ||
| 3012 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3013 | +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" | ||
| 3014 | + | ||
| 3015 | +[[package]] | ||
| 3016 | +name = "windows_i686_msvc" | ||
| 3017 | +version = "0.52.6" | ||
| 3018 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3019 | +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" | ||
| 3020 | + | ||
| 3021 | +[[package]] | ||
| 3022 | +name = "windows_x86_64_gnu" | ||
| 3023 | +version = "0.48.5" | ||
| 3024 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3025 | +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" | ||
| 3026 | + | ||
| 3027 | +[[package]] | ||
| 3028 | +name = "windows_x86_64_gnu" | ||
| 3029 | +version = "0.52.6" | ||
| 3030 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3031 | +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" | ||
| 3032 | + | ||
| 3033 | +[[package]] | ||
| 3034 | +name = "windows_x86_64_gnullvm" | ||
| 3035 | +version = "0.48.5" | ||
| 3036 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3037 | +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" | ||
| 3038 | + | ||
| 3039 | +[[package]] | ||
| 3040 | +name = "windows_x86_64_gnullvm" | ||
| 3041 | +version = "0.52.6" | ||
| 3042 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3043 | +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" | ||
| 3044 | + | ||
| 3045 | +[[package]] | ||
| 3046 | +name = "windows_x86_64_msvc" | ||
| 3047 | +version = "0.48.5" | ||
| 3048 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3049 | +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" | ||
| 3050 | + | ||
| 3051 | +[[package]] | ||
| 3052 | +name = "windows_x86_64_msvc" | ||
| 3053 | +version = "0.52.6" | ||
| 3054 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3055 | +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" | ||
| 3056 | + | ||
| 3057 | +[[package]] | ||
| 3058 | +name = "winnow" | ||
| 3059 | +version = "1.0.4" | ||
| 3060 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3061 | +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" | ||
| 3062 | +dependencies = [ | ||
| 3063 | + "memchr", | ||
| 3064 | +] | ||
| 3065 | + | ||
| 3066 | +[[package]] | ||
| 3067 | +name = "writeable" | ||
| 3068 | +version = "0.6.3" | ||
| 3069 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3070 | +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" | ||
| 3071 | + | ||
| 3072 | +[[package]] | ||
| 3073 | +name = "yoke" | ||
| 3074 | +version = "0.8.3" | ||
| 3075 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3076 | +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" | ||
| 3077 | +dependencies = [ | ||
| 3078 | + "stable_deref_trait", | ||
| 3079 | + "yoke-derive", | ||
| 3080 | + "zerofrom", | ||
| 3081 | +] | ||
| 3082 | + | ||
| 3083 | +[[package]] | ||
| 3084 | +name = "yoke-derive" | ||
| 3085 | +version = "0.8.2" | ||
| 3086 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3087 | +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" | ||
| 3088 | +dependencies = [ | ||
| 3089 | + "proc-macro2", | ||
| 3090 | + "quote", | ||
| 3091 | + "syn 2.0.119", | ||
| 3092 | + "synstructure", | ||
| 3093 | +] | ||
| 3094 | + | ||
| 3095 | +[[package]] | ||
| 3096 | +name = "zerocopy" | ||
| 3097 | +version = "0.8.56" | ||
| 3098 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3099 | +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" | ||
| 3100 | +dependencies = [ | ||
| 3101 | + "zerocopy-derive", | ||
| 3102 | +] | ||
| 3103 | + | ||
| 3104 | +[[package]] | ||
| 3105 | +name = "zerocopy-derive" | ||
| 3106 | +version = "0.8.56" | ||
| 3107 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3108 | +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" | ||
| 3109 | +dependencies = [ | ||
| 3110 | + "proc-macro2", | ||
| 3111 | + "quote", | ||
| 3112 | + "syn 2.0.119", | ||
| 3113 | +] | ||
| 3114 | + | ||
| 3115 | +[[package]] | ||
| 3116 | +name = "zerofrom" | ||
| 3117 | +version = "0.1.8" | ||
| 3118 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3119 | +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" | ||
| 3120 | +dependencies = [ | ||
| 3121 | + "zerofrom-derive", | ||
| 3122 | +] | ||
| 3123 | + | ||
| 3124 | +[[package]] | ||
| 3125 | +name = "zerofrom-derive" | ||
| 3126 | +version = "0.1.7" | ||
| 3127 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3128 | +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" | ||
| 3129 | +dependencies = [ | ||
| 3130 | + "proc-macro2", | ||
| 3131 | + "quote", | ||
| 3132 | + "syn 2.0.119", | ||
| 3133 | + "synstructure", | ||
| 3134 | +] | ||
| 3135 | + | ||
| 3136 | +[[package]] | ||
| 3137 | +name = "zeroize" | ||
| 3138 | +version = "1.9.0" | ||
| 3139 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3140 | +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" | ||
| 3141 | + | ||
| 3142 | +[[package]] | ||
| 3143 | +name = "zerotrie" | ||
| 3144 | +version = "0.2.4" | ||
| 3145 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3146 | +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" | ||
| 3147 | +dependencies = [ | ||
| 3148 | + "displaydoc", | ||
| 3149 | + "yoke", | ||
| 3150 | + "zerofrom", | ||
| 3151 | +] | ||
| 3152 | + | ||
| 3153 | +[[package]] | ||
| 3154 | +name = "zerovec" | ||
| 3155 | +version = "0.11.6" | ||
| 3156 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3157 | +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" | ||
| 3158 | +dependencies = [ | ||
| 3159 | + "yoke", | ||
| 3160 | + "zerofrom", | ||
| 3161 | + "zerovec-derive", | ||
| 3162 | +] | ||
| 3163 | + | ||
| 3164 | +[[package]] | ||
| 3165 | +name = "zerovec-derive" | ||
| 3166 | +version = "0.11.3" | ||
| 3167 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3168 | +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" | ||
| 3169 | +dependencies = [ | ||
| 3170 | + "proc-macro2", | ||
| 3171 | + "quote", | ||
| 3172 | + "syn 2.0.119", | ||
| 3173 | +] | ||
| 3174 | + | ||
| 3175 | +[[package]] | ||
| 3176 | +name = "zmij" | ||
| 3177 | +version = "1.0.23" | ||
| 3178 | +source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3179 | +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" | ||
added
Cargo.toml +31 -0 | new file mode 100644 | ||
| @@ -0,0 +1,31 @@ | ||
| 1 | +[package] | |
| 2 | +name = "time-time" | |
| 3 | +version = "0.1.0" | |
| 4 | +edition = "2024" | |
| 5 | + | |
| 6 | +[dependencies] | |
| 7 | +axum = "0.8" | |
| 8 | +axum-extra = { version = "0.12", features = ["cookie"] } | |
| 9 | +tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] } | |
| 10 | +askama = "0.16" | |
| 11 | +sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls-ring", "postgres", "chrono", "migrate", "uuid"] } | |
| 12 | +uuid = { version = "1", features = ["v7", "serde"] } | |
| 13 | +biscuit-auth = "6" | |
| 14 | +argon2 = "0.5" | |
| 15 | +chrono = { version = "0.4", features = ["serde"] } | |
| 16 | +chrono-tz = "0.10" | |
| 17 | +clap = { version = "4", features = ["derive"] } | |
| 18 | +thiserror = "2" | |
| 19 | +anyhow = "1" | |
| 20 | +hmac = "0.12" | |
| 21 | +sha2 = "0.10" | |
| 22 | +hex = "0.4" | |
| 23 | +subtle = "2" | |
| 24 | +time = "0.3" | |
| 25 | +rpassword = "7" | |
| 26 | +serde = { version = "1", features = ["derive"] } | |
| 27 | +tracing = "0.1" | |
| 28 | +tracing-subscriber = { version = "0.3", features = ["env-filter"] } | |
| 29 | + | |
| 30 | +[profile.release] | |
| 31 | +lto = "thin" | |
| new file mode 100644 | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | +[package] | ||
| 2 | +name = "time-time" | ||
| 3 | +version = "0.1.0" | ||
| 4 | +edition = "2024" | ||
| 5 | + | ||
| 6 | +[dependencies] | ||
| 7 | +axum = "0.8" | ||
| 8 | +axum-extra = { version = "0.12", features = ["cookie"] } | ||
| 9 | +tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] } | ||
| 10 | +askama = "0.16" | ||
| 11 | +sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls-ring", "postgres", "chrono", "migrate", "uuid"] } | ||
| 12 | +uuid = { version = "1", features = ["v7", "serde"] } | ||
| 13 | +biscuit-auth = "6" | ||
| 14 | +argon2 = "0.5" | ||
| 15 | +chrono = { version = "0.4", features = ["serde"] } | ||
| 16 | +chrono-tz = "0.10" | ||
| 17 | +clap = { version = "4", features = ["derive"] } | ||
| 18 | +thiserror = "2" | ||
| 19 | +anyhow = "1" | ||
| 20 | +hmac = "0.12" | ||
| 21 | +sha2 = "0.10" | ||
| 22 | +hex = "0.4" | ||
| 23 | +subtle = "2" | ||
| 24 | +time = "0.3" | ||
| 25 | +rpassword = "7" | ||
| 26 | +serde = { version = "1", features = ["derive"] } | ||
| 27 | +tracing = "0.1" | ||
| 28 | +tracing-subscriber = { version = "0.3", features = ["env-filter"] } | ||
| 29 | + | ||
| 30 | +[profile.release] | ||
| 31 | +lto = "thin" | ||
added
assets/icon-512.png +0 -0 | new file mode 100644 | ||
| Binary files /dev/null and b/assets/icon-512.png differ | ||
| new file mode 100644 | |||
| Binary files /dev/null and b/assets/icon-512.png differ | Binary files /dev/null and b/assets/icon-512.png differ | ||
added
assets/manifest.json +18 -0 | new file mode 100644 | ||
| @@ -0,0 +1,18 @@ | ||
| 1 | +{ | |
| 2 | + "name": "time-time", | |
| 3 | + "short_name": "time-time", | |
| 4 | + "description": "Comptage de temps passé sur des tâches", | |
| 5 | + "start_url": "/", | |
| 6 | + "display": "standalone", | |
| 7 | + "background_color": "#f7f8fa", | |
| 8 | + "theme_color": "#1d3557", | |
| 9 | + "lang": "fr", | |
| 10 | + "icons": [ | |
| 11 | + { | |
| 12 | + "src": "/icon-512.png", | |
| 13 | + "sizes": "512x512", | |
| 14 | + "type": "image/png", | |
| 15 | + "purpose": "any maskable" | |
| 16 | + } | |
| 17 | + ] | |
| 18 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | +{ | ||
| 2 | + "name": "time-time", | ||
| 3 | + "short_name": "time-time", | ||
| 4 | + "description": "Comptage de temps passé sur des tâches", | ||
| 5 | + "start_url": "/", | ||
| 6 | + "display": "standalone", | ||
| 7 | + "background_color": "#f7f8fa", | ||
| 8 | + "theme_color": "#1d3557", | ||
| 9 | + "lang": "fr", | ||
| 10 | + "icons": [ | ||
| 11 | + { | ||
| 12 | + "src": "/icon-512.png", | ||
| 13 | + "sizes": "512x512", | ||
| 14 | + "type": "image/png", | ||
| 15 | + "purpose": "any maskable" | ||
| 16 | + } | ||
| 17 | + ] | ||
| 18 | +} | ||
added
assets/style.css +177 -0 | new file mode 100644 | ||
| @@ -0,0 +1,177 @@ | ||
| 1 | +/* Mobile-first, zéro JS. */ | |
| 2 | +:root { | |
| 3 | + --bg: #f7f8fa; | |
| 4 | + --card: #ffffff; | |
| 5 | + --ink: #1a2233; | |
| 6 | + --muted: #5b6474; | |
| 7 | + --primary: #1d3557; | |
| 8 | + --primary-ink: #ffffff; | |
| 9 | + --accent: #4e79a7; | |
| 10 | + --danger: #c0392b; | |
| 11 | + --border: #d8dde6; | |
| 12 | + --ok-bg: #e7f6ec; | |
| 13 | + --ok-ink: #1e7a3c; | |
| 14 | + --err-bg: #fdecea; | |
| 15 | + --err-ink: #b23327; | |
| 16 | + --radius: 12px; | |
| 17 | +} | |
| 18 | + | |
| 19 | +* { box-sizing: border-box; } | |
| 20 | + | |
| 21 | +body { | |
| 22 | + margin: 0; | |
| 23 | + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; | |
| 24 | + background: var(--bg); | |
| 25 | + color: var(--ink); | |
| 26 | + font-size: 17px; | |
| 27 | + line-height: 1.45; | |
| 28 | +} | |
| 29 | + | |
| 30 | +.topbar { | |
| 31 | + background: var(--primary); | |
| 32 | + padding: .7rem 1rem; | |
| 33 | + position: sticky; | |
| 34 | + top: 0; | |
| 35 | +} | |
| 36 | +.brand { | |
| 37 | + color: var(--primary-ink); | |
| 38 | + text-decoration: none; | |
| 39 | + font-weight: 700; | |
| 40 | + font-size: 1.05rem; | |
| 41 | +} | |
| 42 | + | |
| 43 | +.page { | |
| 44 | + max-width: 560px; | |
| 45 | + margin: 0 auto; | |
| 46 | + padding: 1rem 1rem 3rem; | |
| 47 | +} | |
| 48 | + | |
| 49 | +h1 { font-size: 1.4rem; margin: .5rem 0 1rem; } | |
| 50 | +h2 { font-size: 1.1rem; margin: 1.6rem 0 .6rem; } | |
| 51 | + | |
| 52 | +/* Boutons — cibles tactiles généreuses. */ | |
| 53 | +.btn { | |
| 54 | + display: inline-block; | |
| 55 | + padding: .65rem 1rem; | |
| 56 | + min-height: 44px; | |
| 57 | + border: 1px solid var(--border); | |
| 58 | + border-radius: var(--radius); | |
| 59 | + background: var(--card); | |
| 60 | + color: var(--ink); | |
| 61 | + text-decoration: none; | |
| 62 | + text-align: center; | |
| 63 | + font-size: 1rem; | |
| 64 | + cursor: pointer; | |
| 65 | +} | |
| 66 | +.btn:active { transform: translateY(1px); } | |
| 67 | +.btn-primary { background: var(--primary); border-color: var(--primary); color: var(--primary-ink); } | |
| 68 | +.btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; } | |
| 69 | +.btn-big { display: block; width: 100%; padding: 1rem; font-size: 1.15rem; } | |
| 70 | +.btn-small { padding: .4rem .7rem; min-height: 36px; font-size: .9rem; } | |
| 71 | + | |
| 72 | +.menu { margin-top: 2rem; } | |
| 73 | + | |
| 74 | +.stack { display: flex; flex-direction: column; gap: .5rem; } | |
| 75 | +.stack label { font-weight: 600; margin-top: .6rem; } | |
| 76 | +.stack .btn { margin-top: 1rem; } | |
| 77 | + | |
| 78 | +input, select { | |
| 79 | + width: 100%; | |
| 80 | + padding: .65rem .8rem; | |
| 81 | + font-size: 1.05rem; | |
| 82 | + border: 1px solid var(--border); | |
| 83 | + border-radius: var(--radius); | |
| 84 | + background: var(--card); | |
| 85 | + color: var(--ink); | |
| 86 | +} | |
| 87 | +input:focus, select:focus { outline: 2px solid var(--accent); outline-offset: 1px; } | |
| 88 | + | |
| 89 | +.presets { display: flex; gap: .5rem; flex-wrap: wrap; } | |
| 90 | +.btn-preset { flex: 1 1 auto; } | |
| 91 | + | |
| 92 | +.flash { | |
| 93 | + padding: .6rem .9rem; | |
| 94 | + border-radius: var(--radius); | |
| 95 | + margin: .5rem 0; | |
| 96 | +} | |
| 97 | +.flash-ok { background: var(--ok-bg); color: var(--ok-ink); } | |
| 98 | +.flash-error { background: var(--err-bg); color: var(--err-ink); } | |
| 99 | + | |
| 100 | +/* Liste de mes temps */ | |
| 101 | +.entries { list-style: none; margin: 0; padding: 0; } | |
| 102 | +.entries li + li { margin-top: .5rem; } | |
| 103 | +.entry { | |
| 104 | + display: grid; | |
| 105 | + grid-template-columns: auto 1fr auto; | |
| 106 | + gap: .8rem; | |
| 107 | + align-items: center; | |
| 108 | + padding: .8rem 1rem; | |
| 109 | + background: var(--card); | |
| 110 | + border: 1px solid var(--border); | |
| 111 | + border-radius: var(--radius); | |
| 112 | + text-decoration: none; | |
| 113 | + color: var(--ink); | |
| 114 | +} | |
| 115 | +.entry-date { color: var(--muted); font-variant-numeric: tabular-nums; } | |
| 116 | +.entry-task { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |
| 117 | +.entry-duration { font-variant-numeric: tabular-nums; } | |
| 118 | + | |
| 119 | +.pager { display: flex; justify-content: space-between; gap: .5rem; margin: 1rem 0; } | |
| 120 | + | |
| 121 | +/* Stats */ | |
| 122 | +.chart-block { | |
| 123 | + background: var(--card); | |
| 124 | + border: 1px solid var(--border); | |
| 125 | + border-radius: var(--radius); | |
| 126 | + padding: .8rem; | |
| 127 | + margin-bottom: 1.2rem; | |
| 128 | +} | |
| 129 | +.chart-head { | |
| 130 | + display: flex; | |
| 131 | + align-items: center; | |
| 132 | + justify-content: space-between; | |
| 133 | + gap: .5rem; | |
| 134 | + margin-bottom: .4rem; | |
| 135 | +} | |
| 136 | +.chart-head h2 { margin: 0; font-size: 1rem; text-align: center; flex: 1; } | |
| 137 | +.btn-nav { min-width: 44px; } | |
| 138 | +.btn-nav-placeholder { min-width: 44px; display: inline-block; } | |
| 139 | + | |
| 140 | +.chart { display: block; } | |
| 141 | +.gridline { stroke: var(--border); stroke-width: 1; } | |
| 142 | +.axis { stroke: var(--muted); stroke-width: 1; } | |
| 143 | +.tick { font-size: 10px; fill: var(--muted); } | |
| 144 | +.bar-label { font-size: 11px; fill: var(--ink); font-weight: 600; } | |
| 145 | +.total-label { font-size: 10px; fill: var(--muted); } | |
| 146 | +.empty-label { font-size: 12px; fill: var(--muted); } | |
| 147 | + | |
| 148 | +.legend { | |
| 149 | + list-style: none; | |
| 150 | + margin: 0 0 1rem; | |
| 151 | + padding: 0; | |
| 152 | + display: flex; | |
| 153 | + flex-wrap: wrap; | |
| 154 | + gap: .4rem .9rem; | |
| 155 | + font-size: .9rem; | |
| 156 | +} | |
| 157 | +.legend li { display: flex; align-items: center; gap: .35rem; } | |
| 158 | +.swatch { | |
| 159 | + width: 12px; height: 12px; | |
| 160 | + border-radius: 3px; | |
| 161 | + display: inline-block; | |
| 162 | +} | |
| 163 | + | |
| 164 | +/* Paramètres */ | |
| 165 | +.task-list { list-style: none; margin: 0; padding: 0; } | |
| 166 | +.task { | |
| 167 | + display: flex; | |
| 168 | + gap: .5rem; | |
| 169 | + align-items: center; | |
| 170 | + padding: .5rem 0; | |
| 171 | + border-bottom: 1px solid var(--border); | |
| 172 | +} | |
| 173 | +.task-rename { display: flex; gap: .5rem; flex: 1; } | |
| 174 | +.task-archived input { color: var(--muted); } | |
| 175 | +.task-add { display: flex; gap: .5rem; margin-top: .8rem; } | |
| 176 | +.hint { color: var(--muted); font-size: .85rem; } | |
| 177 | +.danger-zone { margin-top: 2rem; } | |
| new file mode 100644 | |||
| @@ -0,0 +1,177 @@ | |||
| 1 | +/* Mobile-first, zéro JS. */ | ||
| 2 | +:root { | ||
| 3 | + --bg: #f7f8fa; | ||
| 4 | + --card: #ffffff; | ||
| 5 | + --ink: #1a2233; | ||
| 6 | + --muted: #5b6474; | ||
| 7 | + --primary: #1d3557; | ||
| 8 | + --primary-ink: #ffffff; | ||
| 9 | + --accent: #4e79a7; | ||
| 10 | + --danger: #c0392b; | ||
| 11 | + --border: #d8dde6; | ||
| 12 | + --ok-bg: #e7f6ec; | ||
| 13 | + --ok-ink: #1e7a3c; | ||
| 14 | + --err-bg: #fdecea; | ||
| 15 | + --err-ink: #b23327; | ||
| 16 | + --radius: 12px; | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +* { box-sizing: border-box; } | ||
| 20 | + | ||
| 21 | +body { | ||
| 22 | + margin: 0; | ||
| 23 | + font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; | ||
| 24 | + background: var(--bg); | ||
| 25 | + color: var(--ink); | ||
| 26 | + font-size: 17px; | ||
| 27 | + line-height: 1.45; | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +.topbar { | ||
| 31 | + background: var(--primary); | ||
| 32 | + padding: .7rem 1rem; | ||
| 33 | + position: sticky; | ||
| 34 | + top: 0; | ||
| 35 | +} | ||
| 36 | +.brand { | ||
| 37 | + color: var(--primary-ink); | ||
| 38 | + text-decoration: none; | ||
| 39 | + font-weight: 700; | ||
| 40 | + font-size: 1.05rem; | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +.page { | ||
| 44 | + max-width: 560px; | ||
| 45 | + margin: 0 auto; | ||
| 46 | + padding: 1rem 1rem 3rem; | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +h1 { font-size: 1.4rem; margin: .5rem 0 1rem; } | ||
| 50 | +h2 { font-size: 1.1rem; margin: 1.6rem 0 .6rem; } | ||
| 51 | + | ||
| 52 | +/* Boutons — cibles tactiles généreuses. */ | ||
| 53 | +.btn { | ||
| 54 | + display: inline-block; | ||
| 55 | + padding: .65rem 1rem; | ||
| 56 | + min-height: 44px; | ||
| 57 | + border: 1px solid var(--border); | ||
| 58 | + border-radius: var(--radius); | ||
| 59 | + background: var(--card); | ||
| 60 | + color: var(--ink); | ||
| 61 | + text-decoration: none; | ||
| 62 | + text-align: center; | ||
| 63 | + font-size: 1rem; | ||
| 64 | + cursor: pointer; | ||
| 65 | +} | ||
| 66 | +.btn:active { transform: translateY(1px); } | ||
| 67 | +.btn-primary { background: var(--primary); border-color: var(--primary); color: var(--primary-ink); } | ||
| 68 | +.btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; } | ||
| 69 | +.btn-big { display: block; width: 100%; padding: 1rem; font-size: 1.15rem; } | ||
| 70 | +.btn-small { padding: .4rem .7rem; min-height: 36px; font-size: .9rem; } | ||
| 71 | + | ||
| 72 | +.menu { margin-top: 2rem; } | ||
| 73 | + | ||
| 74 | +.stack { display: flex; flex-direction: column; gap: .5rem; } | ||
| 75 | +.stack label { font-weight: 600; margin-top: .6rem; } | ||
| 76 | +.stack .btn { margin-top: 1rem; } | ||
| 77 | + | ||
| 78 | +input, select { | ||
| 79 | + width: 100%; | ||
| 80 | + padding: .65rem .8rem; | ||
| 81 | + font-size: 1.05rem; | ||
| 82 | + border: 1px solid var(--border); | ||
| 83 | + border-radius: var(--radius); | ||
| 84 | + background: var(--card); | ||
| 85 | + color: var(--ink); | ||
| 86 | +} | ||
| 87 | +input:focus, select:focus { outline: 2px solid var(--accent); outline-offset: 1px; } | ||
| 88 | + | ||
| 89 | +.presets { display: flex; gap: .5rem; flex-wrap: wrap; } | ||
| 90 | +.btn-preset { flex: 1 1 auto; } | ||
| 91 | + | ||
| 92 | +.flash { | ||
| 93 | + padding: .6rem .9rem; | ||
| 94 | + border-radius: var(--radius); | ||
| 95 | + margin: .5rem 0; | ||
| 96 | +} | ||
| 97 | +.flash-ok { background: var(--ok-bg); color: var(--ok-ink); } | ||
| 98 | +.flash-error { background: var(--err-bg); color: var(--err-ink); } | ||
| 99 | + | ||
| 100 | +/* Liste de mes temps */ | ||
| 101 | +.entries { list-style: none; margin: 0; padding: 0; } | ||
| 102 | +.entries li + li { margin-top: .5rem; } | ||
| 103 | +.entry { | ||
| 104 | + display: grid; | ||
| 105 | + grid-template-columns: auto 1fr auto; | ||
| 106 | + gap: .8rem; | ||
| 107 | + align-items: center; | ||
| 108 | + padding: .8rem 1rem; | ||
| 109 | + background: var(--card); | ||
| 110 | + border: 1px solid var(--border); | ||
| 111 | + border-radius: var(--radius); | ||
| 112 | + text-decoration: none; | ||
| 113 | + color: var(--ink); | ||
| 114 | +} | ||
| 115 | +.entry-date { color: var(--muted); font-variant-numeric: tabular-nums; } | ||
| 116 | +.entry-task { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | ||
| 117 | +.entry-duration { font-variant-numeric: tabular-nums; } | ||
| 118 | + | ||
| 119 | +.pager { display: flex; justify-content: space-between; gap: .5rem; margin: 1rem 0; } | ||
| 120 | + | ||
| 121 | +/* Stats */ | ||
| 122 | +.chart-block { | ||
| 123 | + background: var(--card); | ||
| 124 | + border: 1px solid var(--border); | ||
| 125 | + border-radius: var(--radius); | ||
| 126 | + padding: .8rem; | ||
| 127 | + margin-bottom: 1.2rem; | ||
| 128 | +} | ||
| 129 | +.chart-head { | ||
| 130 | + display: flex; | ||
| 131 | + align-items: center; | ||
| 132 | + justify-content: space-between; | ||
| 133 | + gap: .5rem; | ||
| 134 | + margin-bottom: .4rem; | ||
| 135 | +} | ||
| 136 | +.chart-head h2 { margin: 0; font-size: 1rem; text-align: center; flex: 1; } | ||
| 137 | +.btn-nav { min-width: 44px; } | ||
| 138 | +.btn-nav-placeholder { min-width: 44px; display: inline-block; } | ||
| 139 | + | ||
| 140 | +.chart { display: block; } | ||
| 141 | +.gridline { stroke: var(--border); stroke-width: 1; } | ||
| 142 | +.axis { stroke: var(--muted); stroke-width: 1; } | ||
| 143 | +.tick { font-size: 10px; fill: var(--muted); } | ||
| 144 | +.bar-label { font-size: 11px; fill: var(--ink); font-weight: 600; } | ||
| 145 | +.total-label { font-size: 10px; fill: var(--muted); } | ||
| 146 | +.empty-label { font-size: 12px; fill: var(--muted); } | ||
| 147 | + | ||
| 148 | +.legend { | ||
| 149 | + list-style: none; | ||
| 150 | + margin: 0 0 1rem; | ||
| 151 | + padding: 0; | ||
| 152 | + display: flex; | ||
| 153 | + flex-wrap: wrap; | ||
| 154 | + gap: .4rem .9rem; | ||
| 155 | + font-size: .9rem; | ||
| 156 | +} | ||
| 157 | +.legend li { display: flex; align-items: center; gap: .35rem; } | ||
| 158 | +.swatch { | ||
| 159 | + width: 12px; height: 12px; | ||
| 160 | + border-radius: 3px; | ||
| 161 | + display: inline-block; | ||
| 162 | +} | ||
| 163 | + | ||
| 164 | +/* Paramètres */ | ||
| 165 | +.task-list { list-style: none; margin: 0; padding: 0; } | ||
| 166 | +.task { | ||
| 167 | + display: flex; | ||
| 168 | + gap: .5rem; | ||
| 169 | + align-items: center; | ||
| 170 | + padding: .5rem 0; | ||
| 171 | + border-bottom: 1px solid var(--border); | ||
| 172 | +} | ||
| 173 | +.task-rename { display: flex; gap: .5rem; flex: 1; } | ||
| 174 | +.task-archived input { color: var(--muted); } | ||
| 175 | +.task-add { display: flex; gap: .5rem; margin-top: .8rem; } | ||
| 176 | +.hint { color: var(--muted); font-size: .85rem; } | ||
| 177 | +.danger-zone { margin-top: 2rem; } | ||
added
migrations/0001_init.sql +30 -0 | new file mode 100644 | ||
| @@ -0,0 +1,30 @@ | ||
| 1 | +-- IDs : UUIDv7 générés côté application (Rust), pas de DEFAULT en base. | |
| 2 | +CREATE TABLE users ( | |
| 3 | + id UUID PRIMARY KEY, | |
| 4 | + username TEXT NOT NULL UNIQUE, | |
| 5 | + password_hash TEXT NOT NULL, -- PHC string ($argon2id$...) | |
| 6 | + created_at TIMESTAMPTZ NOT NULL DEFAULT now() | |
| 7 | +); | |
| 8 | + | |
| 9 | +CREATE TABLE tasks ( | |
| 10 | + id UUID PRIMARY KEY, | |
| 11 | + name TEXT NOT NULL UNIQUE, | |
| 12 | + archived BOOLEAN NOT NULL DEFAULT FALSE, | |
| 13 | + created_at TIMESTAMPTZ NOT NULL DEFAULT now() | |
| 14 | +); | |
| 15 | + | |
| 16 | +CREATE TABLE time_entries ( | |
| 17 | + id UUID PRIMARY KEY, | |
| 18 | + user_id UUID NOT NULL REFERENCES users(id), | |
| 19 | + task_id UUID NOT NULL REFERENCES tasks(id), | |
| 20 | + minutes INTEGER NOT NULL CHECK (minutes > 0 AND minutes <= 1440), | |
| 21 | + entry_date DATE NOT NULL, | |
| 22 | + created_at TIMESTAMPTZ NOT NULL DEFAULT now() | |
| 23 | +); | |
| 24 | +CREATE INDEX idx_entries_date ON time_entries (entry_date); | |
| 25 | +CREATE INDEX idx_entries_user_recent ON time_entries (user_id, entry_date DESC, id DESC); | |
| 26 | + | |
| 27 | +CREATE TABLE revoked_tokens ( | |
| 28 | + revocation_id BYTEA PRIMARY KEY, | |
| 29 | + revoked_at TIMESTAMPTZ NOT NULL DEFAULT now() | |
| 30 | +); | |
| new file mode 100644 | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | +-- IDs : UUIDv7 générés côté application (Rust), pas de DEFAULT en base. | ||
| 2 | +CREATE TABLE users ( | ||
| 3 | + id UUID PRIMARY KEY, | ||
| 4 | + username TEXT NOT NULL UNIQUE, | ||
| 5 | + password_hash TEXT NOT NULL, -- PHC string ($argon2id$...) | ||
| 6 | + created_at TIMESTAMPTZ NOT NULL DEFAULT now() | ||
| 7 | +); | ||
| 8 | + | ||
| 9 | +CREATE TABLE tasks ( | ||
| 10 | + id UUID PRIMARY KEY, | ||
| 11 | + name TEXT NOT NULL UNIQUE, | ||
| 12 | + archived BOOLEAN NOT NULL DEFAULT FALSE, | ||
| 13 | + created_at TIMESTAMPTZ NOT NULL DEFAULT now() | ||
| 14 | +); | ||
| 15 | + | ||
| 16 | +CREATE TABLE time_entries ( | ||
| 17 | + id UUID PRIMARY KEY, | ||
| 18 | + user_id UUID NOT NULL REFERENCES users(id), | ||
| 19 | + task_id UUID NOT NULL REFERENCES tasks(id), | ||
| 20 | + minutes INTEGER NOT NULL CHECK (minutes > 0 AND minutes <= 1440), | ||
| 21 | + entry_date DATE NOT NULL, | ||
| 22 | + created_at TIMESTAMPTZ NOT NULL DEFAULT now() | ||
| 23 | +); | ||
| 24 | +CREATE INDEX idx_entries_date ON time_entries (entry_date); | ||
| 25 | +CREATE INDEX idx_entries_user_recent ON time_entries (user_id, entry_date DESC, id DESC); | ||
| 26 | + | ||
| 27 | +CREATE TABLE revoked_tokens ( | ||
| 28 | + revocation_id BYTEA PRIMARY KEY, | ||
| 29 | + revoked_at TIMESTAMPTZ NOT NULL DEFAULT now() | ||
| 30 | +); | ||
added
src/auth/csrf.rs +31 -0 | new file mode 100644 | ||
| @@ -0,0 +1,31 @@ | ||
| 1 | +use hmac::{Hmac, Mac}; | |
| 2 | +use sha2::Sha256; | |
| 3 | +use subtle::ConstantTimeEq; | |
| 4 | + | |
| 5 | +/// Token CSRF stateless : hex(HMAC-SHA256(csrf_key, revocation_id de la session)). | |
| 6 | +/// Lié à la session (un attaquant cross-origin ne peut pas le calculer), | |
| 7 | +/// aucun stockage, survit aux redémarrages. | |
| 8 | +pub fn token_for(csrf_key: &[u8; 32], session_rev_id: &[u8]) -> String { | |
| 9 | + let mut mac = Hmac::<Sha256>::new_from_slice(csrf_key).expect("HMAC accepte toute taille"); | |
| 10 | + mac.update(session_rev_id); | |
| 11 | + hex::encode(mac.finalize().into_bytes()) | |
| 12 | +} | |
| 13 | + | |
| 14 | +pub fn verify(csrf_key: &[u8; 32], session_rev_id: &[u8], submitted: &str) -> bool { | |
| 15 | + let expected = token_for(csrf_key, session_rev_id); | |
| 16 | + expected.as_bytes().ct_eq(submitted.as_bytes()).into() | |
| 17 | +} | |
| 18 | + | |
| 19 | +#[cfg(test)] | |
| 20 | +mod tests { | |
| 21 | + use super::*; | |
| 22 | + | |
| 23 | + #[test] | |
| 24 | + fn roundtrip_and_mismatch() { | |
| 25 | + let key = [7u8; 32]; | |
| 26 | + let t = token_for(&key, b"rev-id"); | |
| 27 | + assert!(verify(&key, b"rev-id", &t)); | |
| 28 | + assert!(!verify(&key, b"other", &t)); | |
| 29 | + assert!(!verify(&key, b"rev-id", "deadbeef")); | |
| 30 | + } | |
| 31 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | +use hmac::{Hmac, Mac}; | ||
| 2 | +use sha2::Sha256; | ||
| 3 | +use subtle::ConstantTimeEq; | ||
| 4 | + | ||
| 5 | +/// Token CSRF stateless : hex(HMAC-SHA256(csrf_key, revocation_id de la session)). | ||
| 6 | +/// Lié à la session (un attaquant cross-origin ne peut pas le calculer), | ||
| 7 | +/// aucun stockage, survit aux redémarrages. | ||
| 8 | +pub fn token_for(csrf_key: &[u8; 32], session_rev_id: &[u8]) -> String { | ||
| 9 | + let mut mac = Hmac::<Sha256>::new_from_slice(csrf_key).expect("HMAC accepte toute taille"); | ||
| 10 | + mac.update(session_rev_id); | ||
| 11 | + hex::encode(mac.finalize().into_bytes()) | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +pub fn verify(csrf_key: &[u8; 32], session_rev_id: &[u8], submitted: &str) -> bool { | ||
| 15 | + let expected = token_for(csrf_key, session_rev_id); | ||
| 16 | + expected.as_bytes().ct_eq(submitted.as_bytes()).into() | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +#[cfg(test)] | ||
| 20 | +mod tests { | ||
| 21 | + use super::*; | ||
| 22 | + | ||
| 23 | + #[test] | ||
| 24 | + fn roundtrip_and_mismatch() { | ||
| 25 | + let key = [7u8; 32]; | ||
| 26 | + let t = token_for(&key, b"rev-id"); | ||
| 27 | + assert!(verify(&key, b"rev-id", &t)); | ||
| 28 | + assert!(!verify(&key, b"other", &t)); | ||
| 29 | + assert!(!verify(&key, b"rev-id", "deadbeef")); | ||
| 30 | + } | ||
| 31 | +} | ||
added
src/auth/extractor.rs +74 -0 | new file mode 100644 | ||
| @@ -0,0 +1,74 @@ | ||
| 1 | +use axum::extract::FromRequestParts; | |
| 2 | +use axum::http::header::SET_COOKIE; | |
| 3 | +use axum::http::request::Parts; | |
| 4 | +use axum::response::{IntoResponse, Redirect, Response}; | |
| 5 | +use axum_extra::extract::CookieJar; | |
| 6 | +use uuid::Uuid; | |
| 7 | + | |
| 8 | +use super::{SESSION_COOKIE, token}; | |
| 9 | +use crate::state::AppState; | |
| 10 | + | |
| 11 | +/// Utilisateur authentifié, extrait du biscuit en cookie. | |
| 12 | +pub struct AuthUser { | |
| 13 | + pub id: Uuid, | |
| 14 | + /// Tous les identifiants de révocation du token (pour logout). | |
| 15 | + pub rev_ids: Vec<Vec<u8>>, | |
| 16 | +} | |
| 17 | + | |
| 18 | +impl AuthUser { | |
| 19 | + /// Identifiant de révocation du bloc d'autorité — sert de base au token CSRF. | |
| 20 | + pub fn session_rev_id(&self) -> &[u8] { | |
| 21 | + self.rev_ids.first().map(Vec::as_slice).unwrap_or(&[]) | |
| 22 | + } | |
| 23 | + | |
| 24 | + pub fn csrf_token(&self, state: &AppState) -> String { | |
| 25 | + super::csrf::token_for(&state.csrf_key, self.session_rev_id()) | |
| 26 | + } | |
| 27 | + | |
| 28 | + pub fn check_csrf(&self, state: &AppState, submitted: &str) -> bool { | |
| 29 | + super::csrf::verify(&state.csrf_key, self.session_rev_id(), submitted) | |
| 30 | + } | |
| 31 | +} | |
| 32 | + | |
| 33 | +/// Rejet : suppression du cookie + redirection vers /login. | |
| 34 | +pub struct AuthRedirect; | |
| 35 | + | |
| 36 | +impl IntoResponse for AuthRedirect { | |
| 37 | + fn into_response(self) -> Response { | |
| 38 | + let clear = format!("{SESSION_COOKIE}=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax"); | |
| 39 | + ([(SET_COOKIE, clear)], Redirect::to("/login")).into_response() | |
| 40 | + } | |
| 41 | +} | |
| 42 | + | |
| 43 | +fn authenticate(parts: &mut Parts, state: &AppState) -> Option<AuthUser> { | |
| 44 | + let jar = CookieJar::from_headers(&parts.headers); | |
| 45 | + let cookie = jar.get(SESSION_COOKIE)?; | |
| 46 | + let verified = token::verify(cookie.value(), state.public).ok()?; | |
| 47 | + if state.revocations.contains_any(&verified.revocation_ids) { | |
| 48 | + return None; | |
| 49 | + } | |
| 50 | + Some(AuthUser { | |
| 51 | + id: verified.user_id, | |
| 52 | + rev_ids: verified.revocation_ids, | |
| 53 | + }) | |
| 54 | +} | |
| 55 | + | |
| 56 | +impl FromRequestParts<AppState> for AuthUser { | |
| 57 | + type Rejection = AuthRedirect; | |
| 58 | + | |
| 59 | + async fn from_request_parts(parts: &mut Parts, state: &AppState) -> Result<Self, AuthRedirect> { | |
| 60 | + authenticate(parts, state).ok_or(AuthRedirect) | |
| 61 | + } | |
| 62 | +} | |
| 63 | + | |
| 64 | +/// Permet `Option<AuthUser>` dans les handlers (page de login). | |
| 65 | +impl axum::extract::OptionalFromRequestParts<AppState> for AuthUser { | |
| 66 | + type Rejection = std::convert::Infallible; | |
| 67 | + | |
| 68 | + async fn from_request_parts( | |
| 69 | + parts: &mut Parts, | |
| 70 | + state: &AppState, | |
| 71 | + ) -> Result<Option<Self>, Self::Rejection> { | |
| 72 | + Ok(authenticate(parts, state)) | |
| 73 | + } | |
| 74 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | +use axum::extract::FromRequestParts; | ||
| 2 | +use axum::http::header::SET_COOKIE; | ||
| 3 | +use axum::http::request::Parts; | ||
| 4 | +use axum::response::{IntoResponse, Redirect, Response}; | ||
| 5 | +use axum_extra::extract::CookieJar; | ||
| 6 | +use uuid::Uuid; | ||
| 7 | + | ||
| 8 | +use super::{SESSION_COOKIE, token}; | ||
| 9 | +use crate::state::AppState; | ||
| 10 | + | ||
| 11 | +/// Utilisateur authentifié, extrait du biscuit en cookie. | ||
| 12 | +pub struct AuthUser { | ||
| 13 | + pub id: Uuid, | ||
| 14 | + /// Tous les identifiants de révocation du token (pour logout). | ||
| 15 | + pub rev_ids: Vec<Vec<u8>>, | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +impl AuthUser { | ||
| 19 | + /// Identifiant de révocation du bloc d'autorité — sert de base au token CSRF. | ||
| 20 | + pub fn session_rev_id(&self) -> &[u8] { | ||
| 21 | + self.rev_ids.first().map(Vec::as_slice).unwrap_or(&[]) | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + pub fn csrf_token(&self, state: &AppState) -> String { | ||
| 25 | + super::csrf::token_for(&state.csrf_key, self.session_rev_id()) | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + pub fn check_csrf(&self, state: &AppState, submitted: &str) -> bool { | ||
| 29 | + super::csrf::verify(&state.csrf_key, self.session_rev_id(), submitted) | ||
| 30 | + } | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +/// Rejet : suppression du cookie + redirection vers /login. | ||
| 34 | +pub struct AuthRedirect; | ||
| 35 | + | ||
| 36 | +impl IntoResponse for AuthRedirect { | ||
| 37 | + fn into_response(self) -> Response { | ||
| 38 | + let clear = format!("{SESSION_COOKIE}=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax"); | ||
| 39 | + ([(SET_COOKIE, clear)], Redirect::to("/login")).into_response() | ||
| 40 | + } | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +fn authenticate(parts: &mut Parts, state: &AppState) -> Option<AuthUser> { | ||
| 44 | + let jar = CookieJar::from_headers(&parts.headers); | ||
| 45 | + let cookie = jar.get(SESSION_COOKIE)?; | ||
| 46 | + let verified = token::verify(cookie.value(), state.public).ok()?; | ||
| 47 | + if state.revocations.contains_any(&verified.revocation_ids) { | ||
| 48 | + return None; | ||
| 49 | + } | ||
| 50 | + Some(AuthUser { | ||
| 51 | + id: verified.user_id, | ||
| 52 | + rev_ids: verified.revocation_ids, | ||
| 53 | + }) | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +impl FromRequestParts<AppState> for AuthUser { | ||
| 57 | + type Rejection = AuthRedirect; | ||
| 58 | + | ||
| 59 | + async fn from_request_parts(parts: &mut Parts, state: &AppState) -> Result<Self, AuthRedirect> { | ||
| 60 | + authenticate(parts, state).ok_or(AuthRedirect) | ||
| 61 | + } | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +/// Permet `Option<AuthUser>` dans les handlers (page de login). | ||
| 65 | +impl axum::extract::OptionalFromRequestParts<AppState> for AuthUser { | ||
| 66 | + type Rejection = std::convert::Infallible; | ||
| 67 | + | ||
| 68 | + async fn from_request_parts( | ||
| 69 | + parts: &mut Parts, | ||
| 70 | + state: &AppState, | ||
| 71 | + ) -> Result<Option<Self>, Self::Rejection> { | ||
| 72 | + Ok(authenticate(parts, state)) | ||
| 73 | + } | ||
| 74 | +} | ||
added
src/auth/mod.rs +9 -0 | new file mode 100644 | ||
| @@ -0,0 +1,9 @@ | ||
| 1 | +pub mod csrf; | |
| 2 | +pub mod extractor; | |
| 3 | +pub mod password; | |
| 4 | +pub mod revocation; | |
| 5 | +pub mod token; | |
| 6 | + | |
| 7 | +pub const SESSION_COOKIE: &str = "tt_session"; | |
| 8 | +/// ~6 mois. | |
| 9 | +pub const SESSION_SECONDS: i64 = 6 * 30 * 24 * 3600; | |
| new file mode 100644 | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | +pub mod csrf; | ||
| 2 | +pub mod extractor; | ||
| 3 | +pub mod password; | ||
| 4 | +pub mod revocation; | ||
| 5 | +pub mod token; | ||
| 6 | + | ||
| 7 | +pub const SESSION_COOKIE: &str = "tt_session"; | ||
| 8 | +/// ~6 mois. | ||
| 9 | +pub const SESSION_SECONDS: i64 = 6 * 30 * 24 * 3600; | ||
added
src/auth/password.rs +35 -0 | new file mode 100644 | ||
| @@ -0,0 +1,35 @@ | ||
| 1 | +use anyhow::{Result, anyhow}; | |
| 2 | +use argon2::Argon2; | |
| 3 | +use argon2::password_hash::rand_core::OsRng; | |
| 4 | +use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}; | |
| 5 | + | |
| 6 | +/// Hash argon2id (paramètres par défaut de la crate), sortie PHC string. | |
| 7 | +pub fn hash(password: &str) -> Result<String> { | |
| 8 | + let salt = SaltString::generate(&mut OsRng); | |
| 9 | + Argon2::default() | |
| 10 | + .hash_password(password.as_bytes(), &salt) | |
| 11 | + .map(|h| h.to_string()) | |
| 12 | + .map_err(|e| anyhow!("hash mot de passe : {e}")) | |
| 13 | +} | |
| 14 | + | |
| 15 | +pub fn verify(password: &str, phc: &str) -> bool { | |
| 16 | + let Ok(parsed) = PasswordHash::new(phc) else { | |
| 17 | + return false; | |
| 18 | + }; | |
| 19 | + Argon2::default() | |
| 20 | + .verify_password(password.as_bytes(), &parsed) | |
| 21 | + .is_ok() | |
| 22 | +} | |
| 23 | + | |
| 24 | +#[cfg(test)] | |
| 25 | +mod tests { | |
| 26 | + use super::*; | |
| 27 | + | |
| 28 | + #[test] | |
| 29 | + fn roundtrip() { | |
| 30 | + let h = hash("s3cret").unwrap(); | |
| 31 | + assert!(h.starts_with("$argon2id$")); | |
| 32 | + assert!(verify("s3cret", &h)); | |
| 33 | + assert!(!verify("wrong", &h)); | |
| 34 | + } | |
| 35 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | +use anyhow::{Result, anyhow}; | ||
| 2 | +use argon2::Argon2; | ||
| 3 | +use argon2::password_hash::rand_core::OsRng; | ||
| 4 | +use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}; | ||
| 5 | + | ||
| 6 | +/// Hash argon2id (paramètres par défaut de la crate), sortie PHC string. | ||
| 7 | +pub fn hash(password: &str) -> Result<String> { | ||
| 8 | + let salt = SaltString::generate(&mut OsRng); | ||
| 9 | + Argon2::default() | ||
| 10 | + .hash_password(password.as_bytes(), &salt) | ||
| 11 | + .map(|h| h.to_string()) | ||
| 12 | + .map_err(|e| anyhow!("hash mot de passe : {e}")) | ||
| 13 | +} | ||
| 14 | + | ||
| 15 | +pub fn verify(password: &str, phc: &str) -> bool { | ||
| 16 | + let Ok(parsed) = PasswordHash::new(phc) else { | ||
| 17 | + return false; | ||
| 18 | + }; | ||
| 19 | + Argon2::default() | ||
| 20 | + .verify_password(password.as_bytes(), &parsed) | ||
| 21 | + .is_ok() | ||
| 22 | +} | ||
| 23 | + | ||
| 24 | +#[cfg(test)] | ||
| 25 | +mod tests { | ||
| 26 | + use super::*; | ||
| 27 | + | ||
| 28 | + #[test] | ||
| 29 | + fn roundtrip() { | ||
| 30 | + let h = hash("s3cret").unwrap(); | ||
| 31 | + assert!(h.starts_with("$argon2id$")); | ||
| 32 | + assert!(verify("s3cret", &h)); | ||
| 33 | + assert!(!verify("wrong", &h)); | ||
| 34 | + } | ||
| 35 | +} | ||
added
src/auth/revocation.rs +59 -0 | new file mode 100644 | ||
| @@ -0,0 +1,59 @@ | ||
| 1 | +use std::collections::HashSet; | |
| 2 | +use std::sync::{Arc, RwLock}; | |
| 3 | +use std::time::Duration; | |
| 4 | + | |
| 5 | +use anyhow::Result; | |
| 6 | +use sqlx::PgPool; | |
| 7 | + | |
| 8 | +/// Cache mémoire de la liste de révocation (bytes bruts, jamais base64). | |
| 9 | +#[derive(Clone, Default)] | |
| 10 | +pub struct RevocationCache(Arc<RwLock<HashSet<Vec<u8>>>>); | |
| 11 | + | |
| 12 | +impl RevocationCache { | |
| 13 | + pub fn contains_any(&self, ids: &[Vec<u8>]) -> bool { | |
| 14 | + let guard = self.0.read().expect("revocation cache lock poisoned"); | |
| 15 | + ids.iter().any(|id| guard.contains(id)) | |
| 16 | + } | |
| 17 | + | |
| 18 | + pub fn insert_all(&self, ids: &[Vec<u8>]) { | |
| 19 | + let mut guard = self.0.write().expect("revocation cache lock poisoned"); | |
| 20 | + for id in ids { | |
| 21 | + guard.insert(id.clone()); | |
| 22 | + } | |
| 23 | + } | |
| 24 | + | |
| 25 | + pub fn replace(&self, set: HashSet<Vec<u8>>) { | |
| 26 | + *self.0.write().expect("revocation cache lock poisoned") = set; | |
| 27 | + } | |
| 28 | +} | |
| 29 | + | |
| 30 | +/// Révoque en base + effet immédiat sur le cache local. | |
| 31 | +pub async fn revoke(pool: &PgPool, cache: &RevocationCache, ids: &[Vec<u8>]) -> Result<()> { | |
| 32 | + for id in ids { | |
| 33 | + sqlx::query("INSERT INTO revoked_tokens (revocation_id) VALUES ($1) ON CONFLICT DO NOTHING") | |
| 34 | + .bind(id) | |
| 35 | + .execute(pool) | |
| 36 | + .await?; | |
| 37 | + } | |
| 38 | + cache.insert_all(ids); | |
| 39 | + Ok(()) | |
| 40 | +} | |
| 41 | + | |
| 42 | +pub async fn reload(pool: &PgPool, cache: &RevocationCache) -> Result<()> { | |
| 43 | + let rows: Vec<(Vec<u8>,)> = sqlx::query_as("SELECT revocation_id FROM revoked_tokens") | |
| 44 | + .fetch_all(pool) | |
| 45 | + .await?; | |
| 46 | + cache.replace(rows.into_iter().map(|(id,)| id).collect()); | |
| 47 | + Ok(()) | |
| 48 | +} | |
| 49 | + | |
| 50 | +/// Tâche de fond : recharge complète toutes les 30 s (échelle : petit groupe). | |
| 51 | +pub async fn refresher(pool: PgPool, cache: RevocationCache) { | |
| 52 | + let mut interval = tokio::time::interval(Duration::from_secs(30)); | |
| 53 | + loop { | |
| 54 | + interval.tick().await; | |
| 55 | + if let Err(e) = reload(&pool, &cache).await { | |
| 56 | + tracing::warn!(error = %e, "rechargement de la liste de révocation échoué"); | |
| 57 | + } | |
| 58 | + } | |
| 59 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,59 @@ | |||
| 1 | +use std::collections::HashSet; | ||
| 2 | +use std::sync::{Arc, RwLock}; | ||
| 3 | +use std::time::Duration; | ||
| 4 | + | ||
| 5 | +use anyhow::Result; | ||
| 6 | +use sqlx::PgPool; | ||
| 7 | + | ||
| 8 | +/// Cache mémoire de la liste de révocation (bytes bruts, jamais base64). | ||
| 9 | +#[derive(Clone, Default)] | ||
| 10 | +pub struct RevocationCache(Arc<RwLock<HashSet<Vec<u8>>>>); | ||
| 11 | + | ||
| 12 | +impl RevocationCache { | ||
| 13 | + pub fn contains_any(&self, ids: &[Vec<u8>]) -> bool { | ||
| 14 | + let guard = self.0.read().expect("revocation cache lock poisoned"); | ||
| 15 | + ids.iter().any(|id| guard.contains(id)) | ||
| 16 | + } | ||
| 17 | + | ||
| 18 | + pub fn insert_all(&self, ids: &[Vec<u8>]) { | ||
| 19 | + let mut guard = self.0.write().expect("revocation cache lock poisoned"); | ||
| 20 | + for id in ids { | ||
| 21 | + guard.insert(id.clone()); | ||
| 22 | + } | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + pub fn replace(&self, set: HashSet<Vec<u8>>) { | ||
| 26 | + *self.0.write().expect("revocation cache lock poisoned") = set; | ||
| 27 | + } | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +/// Révoque en base + effet immédiat sur le cache local. | ||
| 31 | +pub async fn revoke(pool: &PgPool, cache: &RevocationCache, ids: &[Vec<u8>]) -> Result<()> { | ||
| 32 | + for id in ids { | ||
| 33 | + sqlx::query("INSERT INTO revoked_tokens (revocation_id) VALUES ($1) ON CONFLICT DO NOTHING") | ||
| 34 | + .bind(id) | ||
| 35 | + .execute(pool) | ||
| 36 | + .await?; | ||
| 37 | + } | ||
| 38 | + cache.insert_all(ids); | ||
| 39 | + Ok(()) | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +pub async fn reload(pool: &PgPool, cache: &RevocationCache) -> Result<()> { | ||
| 43 | + let rows: Vec<(Vec<u8>,)> = sqlx::query_as("SELECT revocation_id FROM revoked_tokens") | ||
| 44 | + .fetch_all(pool) | ||
| 45 | + .await?; | ||
| 46 | + cache.replace(rows.into_iter().map(|(id,)| id).collect()); | ||
| 47 | + Ok(()) | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +/// Tâche de fond : recharge complète toutes les 30 s (échelle : petit groupe). | ||
| 51 | +pub async fn refresher(pool: PgPool, cache: RevocationCache) { | ||
| 52 | + let mut interval = tokio::time::interval(Duration::from_secs(30)); | ||
| 53 | + loop { | ||
| 54 | + interval.tick().await; | ||
| 55 | + if let Err(e) = reload(&pool, &cache).await { | ||
| 56 | + tracing::warn!(error = %e, "rechargement de la liste de révocation échoué"); | ||
| 57 | + } | ||
| 58 | + } | ||
| 59 | +} | ||
added
src/auth/token.rs +95 -0 | new file mode 100644 | ||
| @@ -0,0 +1,95 @@ | ||
| 1 | +use std::collections::HashMap; | |
| 2 | +use std::time::{Duration, SystemTime, UNIX_EPOCH}; | |
| 3 | + | |
| 4 | +use anyhow::{Context, Result, anyhow}; | |
| 5 | +use biscuit_auth::builder::{AuthorizerBuilder, Term}; | |
| 6 | +use biscuit_auth::datalog::RunLimits; | |
| 7 | +use biscuit_auth::{Biscuit, KeyPair, PublicKey}; | |
| 8 | +use uuid::Uuid; | |
| 9 | + | |
| 10 | +use super::SESSION_SECONDS; | |
| 11 | + | |
| 12 | +pub struct VerifiedToken { | |
| 13 | + pub user_id: Uuid, | |
| 14 | + /// Identifiants de révocation de tous les blocs (bytes bruts). | |
| 15 | + pub revocation_ids: Vec<Vec<u8>>, | |
| 16 | +} | |
| 17 | + | |
| 18 | +/// Forge un biscuit `user(<uuid>)` + check d'expiration à ~6 mois, encodé base64. | |
| 19 | +pub fn mint(root: &KeyPair, user_id: Uuid) -> Result<String> { | |
| 20 | + let expiry = SystemTime::now() + Duration::from_secs(SESSION_SECONDS as u64); | |
| 21 | + let expiry_secs = expiry | |
| 22 | + .duration_since(UNIX_EPOCH) | |
| 23 | + .context("horloge système antérieure à 1970")? | |
| 24 | + .as_secs(); | |
| 25 | + let builder = Biscuit::builder().code_with_params( | |
| 26 | + r#"user({id}); check if time($t), $t < {exp};"#, | |
| 27 | + HashMap::from([ | |
| 28 | + ("id".to_string(), Term::Str(user_id.to_string())), | |
| 29 | + ("exp".to_string(), Term::Date(expiry_secs)), | |
| 30 | + ]), | |
| 31 | + HashMap::new(), | |
| 32 | + )?; | |
| 33 | + let biscuit = builder.build(root)?; | |
| 34 | + Ok(biscuit.to_base64()?) | |
| 35 | +} | |
| 36 | + | |
| 37 | +/// Vérifie signature + expiration, extrait l'id utilisateur et les | |
| 38 | +/// identifiants de révocation. Ne consulte PAS la liste de révocation | |
| 39 | +/// (fait par l'extracteur, contre le cache). | |
| 40 | +pub fn verify(token_b64: &str, public: PublicKey) -> Result<VerifiedToken> { | |
| 41 | + let biscuit = Biscuit::from_base64(token_b64, public)?; | |
| 42 | + | |
| 43 | + let mut authorizer = AuthorizerBuilder::new() | |
| 44 | + .time() | |
| 45 | + .set_limits(RunLimits { | |
| 46 | + max_time: Duration::from_millis(100), | |
| 47 | + ..Default::default() | |
| 48 | + }) | |
| 49 | + .code("allow if user($u);")? | |
| 50 | + .build(&biscuit)?; | |
| 51 | + authorizer.authorize()?; | |
| 52 | + | |
| 53 | + let ids: Vec<(String,)> = authorizer.query("data($id) <- user($id)")?; | |
| 54 | + let (id_str,) = ids.first().ok_or_else(|| anyhow!("fact user absent"))?; | |
| 55 | + let user_id = Uuid::parse_str(id_str).context("fact user n'est pas un UUID")?; | |
| 56 | + | |
| 57 | + Ok(VerifiedToken { | |
| 58 | + user_id, | |
| 59 | + revocation_ids: biscuit.revocation_identifiers(), | |
| 60 | + }) | |
| 61 | +} | |
| 62 | + | |
| 63 | +#[cfg(test)] | |
| 64 | +mod tests { | |
| 65 | + use super::*; | |
| 66 | + | |
| 67 | + #[test] | |
| 68 | + fn mint_and_verify() { | |
| 69 | + let kp = KeyPair::new(); | |
| 70 | + let uid = Uuid::now_v7(); | |
| 71 | + let token = mint(&kp, uid).unwrap(); | |
| 72 | + let v = verify(&token, kp.public()).unwrap(); | |
| 73 | + assert_eq!(v.user_id, uid); | |
| 74 | + assert!(!v.revocation_ids.is_empty()); | |
| 75 | + } | |
| 76 | + | |
| 77 | + #[test] | |
| 78 | + fn rejects_wrong_key() { | |
| 79 | + let kp = KeyPair::new(); | |
| 80 | + let other = KeyPair::new(); | |
| 81 | + let token = mint(&kp, Uuid::now_v7()).unwrap(); | |
| 82 | + assert!(verify(&token, other.public()).is_err()); | |
| 83 | + } | |
| 84 | + | |
| 85 | + #[test] | |
| 86 | + fn rejects_tampered() { | |
| 87 | + let kp = KeyPair::new(); | |
| 88 | + let token = mint(&kp, Uuid::now_v7()).unwrap(); | |
| 89 | + let mut bytes = token.into_bytes(); | |
| 90 | + let last = bytes.len() - 1; | |
| 91 | + bytes[last] = if bytes[last] == b'A' { b'B' } else { b'A' }; | |
| 92 | + let tampered = String::from_utf8(bytes).unwrap(); | |
| 93 | + assert!(verify(&tampered, kp.public()).is_err()); | |
| 94 | + } | |
| 95 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,95 @@ | |||
| 1 | +use std::collections::HashMap; | ||
| 2 | +use std::time::{Duration, SystemTime, UNIX_EPOCH}; | ||
| 3 | + | ||
| 4 | +use anyhow::{Context, Result, anyhow}; | ||
| 5 | +use biscuit_auth::builder::{AuthorizerBuilder, Term}; | ||
| 6 | +use biscuit_auth::datalog::RunLimits; | ||
| 7 | +use biscuit_auth::{Biscuit, KeyPair, PublicKey}; | ||
| 8 | +use uuid::Uuid; | ||
| 9 | + | ||
| 10 | +use super::SESSION_SECONDS; | ||
| 11 | + | ||
| 12 | +pub struct VerifiedToken { | ||
| 13 | + pub user_id: Uuid, | ||
| 14 | + /// Identifiants de révocation de tous les blocs (bytes bruts). | ||
| 15 | + pub revocation_ids: Vec<Vec<u8>>, | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +/// Forge un biscuit `user(<uuid>)` + check d'expiration à ~6 mois, encodé base64. | ||
| 19 | +pub fn mint(root: &KeyPair, user_id: Uuid) -> Result<String> { | ||
| 20 | + let expiry = SystemTime::now() + Duration::from_secs(SESSION_SECONDS as u64); | ||
| 21 | + let expiry_secs = expiry | ||
| 22 | + .duration_since(UNIX_EPOCH) | ||
| 23 | + .context("horloge système antérieure à 1970")? | ||
| 24 | + .as_secs(); | ||
| 25 | + let builder = Biscuit::builder().code_with_params( | ||
| 26 | + r#"user({id}); check if time($t), $t < {exp};"#, | ||
| 27 | + HashMap::from([ | ||
| 28 | + ("id".to_string(), Term::Str(user_id.to_string())), | ||
| 29 | + ("exp".to_string(), Term::Date(expiry_secs)), | ||
| 30 | + ]), | ||
| 31 | + HashMap::new(), | ||
| 32 | + )?; | ||
| 33 | + let biscuit = builder.build(root)?; | ||
| 34 | + Ok(biscuit.to_base64()?) | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +/// Vérifie signature + expiration, extrait l'id utilisateur et les | ||
| 38 | +/// identifiants de révocation. Ne consulte PAS la liste de révocation | ||
| 39 | +/// (fait par l'extracteur, contre le cache). | ||
| 40 | +pub fn verify(token_b64: &str, public: PublicKey) -> Result<VerifiedToken> { | ||
| 41 | + let biscuit = Biscuit::from_base64(token_b64, public)?; | ||
| 42 | + | ||
| 43 | + let mut authorizer = AuthorizerBuilder::new() | ||
| 44 | + .time() | ||
| 45 | + .set_limits(RunLimits { | ||
| 46 | + max_time: Duration::from_millis(100), | ||
| 47 | + ..Default::default() | ||
| 48 | + }) | ||
| 49 | + .code("allow if user($u);")? | ||
| 50 | + .build(&biscuit)?; | ||
| 51 | + authorizer.authorize()?; | ||
| 52 | + | ||
| 53 | + let ids: Vec<(String,)> = authorizer.query("data($id) <- user($id)")?; | ||
| 54 | + let (id_str,) = ids.first().ok_or_else(|| anyhow!("fact user absent"))?; | ||
| 55 | + let user_id = Uuid::parse_str(id_str).context("fact user n'est pas un UUID")?; | ||
| 56 | + | ||
| 57 | + Ok(VerifiedToken { | ||
| 58 | + user_id, | ||
| 59 | + revocation_ids: biscuit.revocation_identifiers(), | ||
| 60 | + }) | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +#[cfg(test)] | ||
| 64 | +mod tests { | ||
| 65 | + use super::*; | ||
| 66 | + | ||
| 67 | + #[test] | ||
| 68 | + fn mint_and_verify() { | ||
| 69 | + let kp = KeyPair::new(); | ||
| 70 | + let uid = Uuid::now_v7(); | ||
| 71 | + let token = mint(&kp, uid).unwrap(); | ||
| 72 | + let v = verify(&token, kp.public()).unwrap(); | ||
| 73 | + assert_eq!(v.user_id, uid); | ||
| 74 | + assert!(!v.revocation_ids.is_empty()); | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + #[test] | ||
| 78 | + fn rejects_wrong_key() { | ||
| 79 | + let kp = KeyPair::new(); | ||
| 80 | + let other = KeyPair::new(); | ||
| 81 | + let token = mint(&kp, Uuid::now_v7()).unwrap(); | ||
| 82 | + assert!(verify(&token, other.public()).is_err()); | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + #[test] | ||
| 86 | + fn rejects_tampered() { | ||
| 87 | + let kp = KeyPair::new(); | ||
| 88 | + let token = mint(&kp, Uuid::now_v7()).unwrap(); | ||
| 89 | + let mut bytes = token.into_bytes(); | ||
| 90 | + let last = bytes.len() - 1; | ||
| 91 | + bytes[last] = if bytes[last] == b'A' { b'B' } else { b'A' }; | ||
| 92 | + let tampered = String::from_utf8(bytes).unwrap(); | ||
| 93 | + assert!(verify(&tampered, kp.public()).is_err()); | ||
| 94 | + } | ||
| 95 | +} | ||
added
src/charts.rs +267 -0 | new file mode 100644 | ||
| @@ -0,0 +1,267 @@ | ||
| 1 | +use std::collections::BTreeMap; | |
| 2 | + | |
| 3 | +use uuid::Uuid; | |
| 4 | + | |
| 5 | +use crate::db::stats::StatRow; | |
| 6 | +use crate::db::users::UserRow; | |
| 7 | + | |
| 8 | +/// Palette fixe, contrastée, lisible sur fond clair. | |
| 9 | +pub const PALETTE: [&str; 12] = [ | |
| 10 | + "#4e79a7", "#f28e2b", "#e15759", "#76b7b2", "#59a14f", "#edc948", "#b07aa1", "#ff9da7", | |
| 11 | + "#9c755f", "#bab0ac", "#3b6ea5", "#8cd17d", | |
| 12 | +]; | |
| 13 | + | |
| 14 | +/// Couleur stable dans le temps : hash de l'UUID de la tâche → index de palette. | |
| 15 | +pub fn task_color(task_id: Uuid) -> &'static str { | |
| 16 | + let h = task_id | |
| 17 | + .as_bytes() | |
| 18 | + .iter() | |
| 19 | + .fold(0usize, |acc, &b| acc.wrapping_mul(31).wrapping_add(b as usize)); | |
| 20 | + PALETTE[h % PALETTE.len()] | |
| 21 | +} | |
| 22 | + | |
| 23 | +/// « 45 min », « 2 h », « 12 h 30 » | |
| 24 | +pub fn fmt_minutes(total: i64) -> String { | |
| 25 | + let (h, m) = (total / 60, total % 60); | |
| 26 | + match (h, m) { | |
| 27 | + (0, m) => format!("{m} min"), | |
| 28 | + (h, 0) => format!("{h} h"), | |
| 29 | + (h, m) => format!("{h} h {m:02}"), | |
| 30 | + } | |
| 31 | +} | |
| 32 | + | |
| 33 | +// Géométrie du SVG (viewBox 0 0 400 260, en dur dans le template) — le template est muet. | |
| 34 | +const LEFT: f32 = 44.0; | |
| 35 | +const RIGHT: f32 = 392.0; | |
| 36 | +const TOP: f32 = 24.0; | |
| 37 | +const BOTTOM: f32 = 232.0; | |
| 38 | +const N_TICKS: i64 = 4; | |
| 39 | + | |
| 40 | +pub struct ChartView { | |
| 41 | + pub title: String, | |
| 42 | + pub empty: bool, | |
| 43 | + pub bars: Vec<BarView>, | |
| 44 | + pub y_ticks: Vec<Tick>, | |
| 45 | + pub baseline: f32, | |
| 46 | +} | |
| 47 | + | |
| 48 | +pub struct BarView { | |
| 49 | + pub user_label: String, | |
| 50 | + pub total_label: String, | |
| 51 | + pub label_x: f32, | |
| 52 | + pub total_y: f32, | |
| 53 | + pub segments: Vec<SegView>, | |
| 54 | +} | |
| 55 | + | |
| 56 | +pub struct SegView { | |
| 57 | + pub x: f32, | |
| 58 | + pub y: f32, | |
| 59 | + pub w: f32, | |
| 60 | + pub h: f32, | |
| 61 | + pub color: &'static str, | |
| 62 | + pub title: String, | |
| 63 | +} | |
| 64 | + | |
| 65 | +pub struct Tick { | |
| 66 | + pub y: f32, | |
| 67 | + pub label: String, | |
| 68 | +} | |
| 69 | + | |
| 70 | +pub struct LegendItem { | |
| 71 | + pub name: String, | |
| 72 | + pub color: &'static str, | |
| 73 | +} | |
| 74 | + | |
| 75 | +/// Échelle Y : plus petit pas « rond » (en minutes) tel que max ≤ 4 pas. | |
| 76 | +fn nice_step(max: i64) -> i64 { | |
| 77 | + const STEPS: [i64; 10] = [15, 30, 60, 120, 240, 480, 960, 1920, 3840, 7680]; | |
| 78 | + for s in STEPS { | |
| 79 | + if max <= N_TICKS * s { | |
| 80 | + return s; | |
| 81 | + } | |
| 82 | + } | |
| 83 | + (max / N_TICKS).max(1) | |
| 84 | +} | |
| 85 | + | |
| 86 | +impl ChartView { | |
| 87 | + /// `users` : tous les utilisateurs (une barre chacun, même à zéro), | |
| 88 | + /// `rows` : totaux user × tâche de la période. | |
| 89 | + pub fn build(title: String, users: &[UserRow], rows: &[StatRow]) -> Self { | |
| 90 | + // user_id -> [(task_id, task_name, total)] trié par task_id (UUIDv7 ≈ ordre | |
| 91 | + // de création) : ordre d'empilement stable entre les trois graphiques. | |
| 92 | + let mut by_user: BTreeMap<Uuid, Vec<&StatRow>> = BTreeMap::new(); | |
| 93 | + for r in rows { | |
| 94 | + by_user.entry(r.user_id).or_default().push(r); | |
| 95 | + } | |
| 96 | + for segs in by_user.values_mut() { | |
| 97 | + segs.sort_by_key(|r| r.task_id); | |
| 98 | + } | |
| 99 | + | |
| 100 | + let max_total = users | |
| 101 | + .iter() | |
| 102 | + .map(|u| { | |
| 103 | + by_user | |
| 104 | + .get(&u.id) | |
| 105 | + .map(|segs| segs.iter().map(|r| r.total).sum::<i64>()) | |
| 106 | + .unwrap_or(0) | |
| 107 | + }) | |
| 108 | + .max() | |
| 109 | + .unwrap_or(0); | |
| 110 | + let empty = max_total == 0; | |
| 111 | + let step = nice_step(max_total.max(1)); | |
| 112 | + let y_max = (N_TICKS * step) as f32; | |
| 113 | + let scale = (BOTTOM - TOP) / y_max; | |
| 114 | + | |
| 115 | + let y_ticks = (0..=N_TICKS) | |
| 116 | + .map(|i| Tick { | |
| 117 | + y: BOTTOM - (i * step) as f32 * scale, | |
| 118 | + label: fmt_minutes(i * step), | |
| 119 | + }) | |
| 120 | + .collect(); | |
| 121 | + | |
| 122 | + let n = users.len().max(1) as f32; | |
| 123 | + let slot = (RIGHT - LEFT) / n; | |
| 124 | + let bar_w = (slot * 0.6).min(80.0); | |
| 125 | + | |
| 126 | + let bars = users | |
| 127 | + .iter() | |
| 128 | + .enumerate() | |
| 129 | + .map(|(i, u)| { | |
| 130 | + let center = LEFT + slot * (i as f32 + 0.5); | |
| 131 | + let x = center - bar_w / 2.0; | |
| 132 | + let mut cumul = 0i64; | |
| 133 | + let mut segments = Vec::new(); | |
| 134 | + if let Some(segs) = by_user.get(&u.id) { | |
| 135 | + for r in segs { | |
| 136 | + let y_bottom = BOTTOM - cumul as f32 * scale; | |
| 137 | + cumul += r.total; | |
| 138 | + let y_top = BOTTOM - cumul as f32 * scale; | |
| 139 | + segments.push(SegView { | |
| 140 | + x, | |
| 141 | + y: y_top, | |
| 142 | + w: bar_w, | |
| 143 | + h: y_bottom - y_top, | |
| 144 | + color: task_color(r.task_id), | |
| 145 | + title: format!("{} — {}", r.task_name, fmt_minutes(r.total)), | |
| 146 | + }); | |
| 147 | + } | |
| 148 | + } | |
| 149 | + BarView { | |
| 150 | + user_label: u.username.clone(), | |
| 151 | + total_label: if cumul > 0 { | |
| 152 | + fmt_minutes(cumul) | |
| 153 | + } else { | |
| 154 | + String::new() | |
| 155 | + }, | |
| 156 | + label_x: center, | |
| 157 | + total_y: BOTTOM - cumul as f32 * scale - 5.0, | |
| 158 | + segments, | |
| 159 | + } | |
| 160 | + }) | |
| 161 | + .collect(); | |
| 162 | + | |
| 163 | + Self { | |
| 164 | + title, | |
| 165 | + empty, | |
| 166 | + bars, | |
| 167 | + y_ticks, | |
| 168 | + baseline: BOTTOM, | |
| 169 | + } | |
| 170 | + } | |
| 171 | + | |
| 172 | + pub fn left(&self) -> f32 { | |
| 173 | + LEFT | |
| 174 | + } | |
| 175 | + pub fn right(&self) -> f32 { | |
| 176 | + RIGHT | |
| 177 | + } | |
| 178 | +} | |
| 179 | + | |
| 180 | +/// Légende commune : union des tâches présentes, triée par nom. | |
| 181 | +pub fn legend(row_sets: &[&[StatRow]]) -> Vec<LegendItem> { | |
| 182 | + let mut seen: BTreeMap<String, &'static str> = BTreeMap::new(); | |
| 183 | + for rows in row_sets { | |
| 184 | + for r in *rows { | |
| 185 | + seen.entry(r.task_name.clone()) | |
| 186 | + .or_insert_with(|| task_color(r.task_id)); | |
| 187 | + } | |
| 188 | + } | |
| 189 | + seen.into_iter() | |
| 190 | + .map(|(name, color)| LegendItem { name, color }) | |
| 191 | + .collect() | |
| 192 | +} | |
| 193 | + | |
| 194 | +#[cfg(test)] | |
| 195 | +mod tests { | |
| 196 | + use super::*; | |
| 197 | + | |
| 198 | + fn user(id: u128, name: &str) -> UserRow { | |
| 199 | + UserRow { | |
| 200 | + id: Uuid::from_u128(id), | |
| 201 | + username: name.into(), | |
| 202 | + } | |
| 203 | + } | |
| 204 | + | |
| 205 | + fn row(uid: u128, tid: u128, task: &str, total: i64) -> StatRow { | |
| 206 | + StatRow { | |
| 207 | + user_id: Uuid::from_u128(uid), | |
| 208 | + task_id: Uuid::from_u128(tid), | |
| 209 | + task_name: task.into(), | |
| 210 | + total, | |
| 211 | + } | |
| 212 | + } | |
| 213 | + | |
| 214 | + #[test] | |
| 215 | + fn fmt() { | |
| 216 | + assert_eq!(fmt_minutes(45), "45 min"); | |
| 217 | + assert_eq!(fmt_minutes(120), "2 h"); | |
| 218 | + assert_eq!(fmt_minutes(750), "12 h 30"); | |
| 219 | + } | |
| 220 | + | |
| 221 | + #[test] | |
| 222 | + fn geometry_sums_to_totals() { | |
| 223 | + let users = [user(1, "alice"), user(2, "bob")]; | |
| 224 | + let rows = [ | |
| 225 | + row(1, 10, "dev", 90), | |
| 226 | + row(1, 11, "réunions", 30), | |
| 227 | + row(2, 10, "dev", 240), | |
| 228 | + ]; | |
| 229 | + let chart = ChartView::build("t".into(), &users, &rows); | |
| 230 | + assert_eq!(chart.bars.len(), 2); | |
| 231 | + | |
| 232 | + // La hauteur cumulée des segments correspond au total, à l'échelle près. | |
| 233 | + let alice = &chart.bars[0]; | |
| 234 | + let bob = &chart.bars[1]; | |
| 235 | + let h_alice: f32 = alice.segments.iter().map(|s| s.h).sum(); | |
| 236 | + let h_bob: f32 = bob.segments.iter().map(|s| s.h).sum(); | |
| 237 | + assert!((h_alice / h_bob - 120.0 / 240.0).abs() < 1e-3); | |
| 238 | + assert_eq!(alice.total_label, "2 h"); | |
| 239 | + assert_eq!(bob.total_label, "4 h"); | |
| 240 | + | |
| 241 | + // Empilement : segments contigus, du bas vers le haut. | |
| 242 | + assert!((alice.segments[0].y - (alice.segments[1].y + alice.segments[1].h)).abs() < 1e-3); | |
| 243 | + } | |
| 244 | + | |
| 245 | + #[test] | |
| 246 | + fn zero_user_has_no_segments() { | |
| 247 | + let users = [user(1, "alice"), user(2, "bob")]; | |
| 248 | + let rows = [row(1, 10, "dev", 60)]; | |
| 249 | + let chart = ChartView::build("t".into(), &users, &rows); | |
| 250 | + assert!(chart.bars[1].segments.is_empty()); | |
| 251 | + assert_eq!(chart.bars[1].total_label, ""); | |
| 252 | + assert!(!chart.empty); | |
| 253 | + } | |
| 254 | + | |
| 255 | + #[test] | |
| 256 | + fn stable_color() { | |
| 257 | + let id = Uuid::from_u128(42); | |
| 258 | + assert_eq!(task_color(id), task_color(id)); | |
| 259 | + } | |
| 260 | + | |
| 261 | + #[test] | |
| 262 | + fn nice_steps() { | |
| 263 | + assert_eq!(nice_step(1), 15); // ≤ 1 h → pas de 15 min | |
| 264 | + assert_eq!(nice_step(240), 60); | |
| 265 | + assert_eq!(nice_step(241), 120); | |
| 266 | + } | |
| 267 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,267 @@ | |||
| 1 | +use std::collections::BTreeMap; | ||
| 2 | + | ||
| 3 | +use uuid::Uuid; | ||
| 4 | + | ||
| 5 | +use crate::db::stats::StatRow; | ||
| 6 | +use crate::db::users::UserRow; | ||
| 7 | + | ||
| 8 | +/// Palette fixe, contrastée, lisible sur fond clair. | ||
| 9 | +pub const PALETTE: [&str; 12] = [ | ||
| 10 | + "#4e79a7", "#f28e2b", "#e15759", "#76b7b2", "#59a14f", "#edc948", "#b07aa1", "#ff9da7", | ||
| 11 | + "#9c755f", "#bab0ac", "#3b6ea5", "#8cd17d", | ||
| 12 | +]; | ||
| 13 | + | ||
| 14 | +/// Couleur stable dans le temps : hash de l'UUID de la tâche → index de palette. | ||
| 15 | +pub fn task_color(task_id: Uuid) -> &'static str { | ||
| 16 | + let h = task_id | ||
| 17 | + .as_bytes() | ||
| 18 | + .iter() | ||
| 19 | + .fold(0usize, |acc, &b| acc.wrapping_mul(31).wrapping_add(b as usize)); | ||
| 20 | + PALETTE[h % PALETTE.len()] | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +/// « 45 min », « 2 h », « 12 h 30 » | ||
| 24 | +pub fn fmt_minutes(total: i64) -> String { | ||
| 25 | + let (h, m) = (total / 60, total % 60); | ||
| 26 | + match (h, m) { | ||
| 27 | + (0, m) => format!("{m} min"), | ||
| 28 | + (h, 0) => format!("{h} h"), | ||
| 29 | + (h, m) => format!("{h} h {m:02}"), | ||
| 30 | + } | ||
| 31 | +} | ||
| 32 | + | ||
| 33 | +// Géométrie du SVG (viewBox 0 0 400 260, en dur dans le template) — le template est muet. | ||
| 34 | +const LEFT: f32 = 44.0; | ||
| 35 | +const RIGHT: f32 = 392.0; | ||
| 36 | +const TOP: f32 = 24.0; | ||
| 37 | +const BOTTOM: f32 = 232.0; | ||
| 38 | +const N_TICKS: i64 = 4; | ||
| 39 | + | ||
| 40 | +pub struct ChartView { | ||
| 41 | + pub title: String, | ||
| 42 | + pub empty: bool, | ||
| 43 | + pub bars: Vec<BarView>, | ||
| 44 | + pub y_ticks: Vec<Tick>, | ||
| 45 | + pub baseline: f32, | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +pub struct BarView { | ||
| 49 | + pub user_label: String, | ||
| 50 | + pub total_label: String, | ||
| 51 | + pub label_x: f32, | ||
| 52 | + pub total_y: f32, | ||
| 53 | + pub segments: Vec<SegView>, | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +pub struct SegView { | ||
| 57 | + pub x: f32, | ||
| 58 | + pub y: f32, | ||
| 59 | + pub w: f32, | ||
| 60 | + pub h: f32, | ||
| 61 | + pub color: &'static str, | ||
| 62 | + pub title: String, | ||
| 63 | +} | ||
| 64 | + | ||
| 65 | +pub struct Tick { | ||
| 66 | + pub y: f32, | ||
| 67 | + pub label: String, | ||
| 68 | +} | ||
| 69 | + | ||
| 70 | +pub struct LegendItem { | ||
| 71 | + pub name: String, | ||
| 72 | + pub color: &'static str, | ||
| 73 | +} | ||
| 74 | + | ||
| 75 | +/// Échelle Y : plus petit pas « rond » (en minutes) tel que max ≤ 4 pas. | ||
| 76 | +fn nice_step(max: i64) -> i64 { | ||
| 77 | + const STEPS: [i64; 10] = [15, 30, 60, 120, 240, 480, 960, 1920, 3840, 7680]; | ||
| 78 | + for s in STEPS { | ||
| 79 | + if max <= N_TICKS * s { | ||
| 80 | + return s; | ||
| 81 | + } | ||
| 82 | + } | ||
| 83 | + (max / N_TICKS).max(1) | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +impl ChartView { | ||
| 87 | + /// `users` : tous les utilisateurs (une barre chacun, même à zéro), | ||
| 88 | + /// `rows` : totaux user × tâche de la période. | ||
| 89 | + pub fn build(title: String, users: &[UserRow], rows: &[StatRow]) -> Self { | ||
| 90 | + // user_id -> [(task_id, task_name, total)] trié par task_id (UUIDv7 ≈ ordre | ||
| 91 | + // de création) : ordre d'empilement stable entre les trois graphiques. | ||
| 92 | + let mut by_user: BTreeMap<Uuid, Vec<&StatRow>> = BTreeMap::new(); | ||
| 93 | + for r in rows { | ||
| 94 | + by_user.entry(r.user_id).or_default().push(r); | ||
| 95 | + } | ||
| 96 | + for segs in by_user.values_mut() { | ||
| 97 | + segs.sort_by_key(|r| r.task_id); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + let max_total = users | ||
| 101 | + .iter() | ||
| 102 | + .map(|u| { | ||
| 103 | + by_user | ||
| 104 | + .get(&u.id) | ||
| 105 | + .map(|segs| segs.iter().map(|r| r.total).sum::<i64>()) | ||
| 106 | + .unwrap_or(0) | ||
| 107 | + }) | ||
| 108 | + .max() | ||
| 109 | + .unwrap_or(0); | ||
| 110 | + let empty = max_total == 0; | ||
| 111 | + let step = nice_step(max_total.max(1)); | ||
| 112 | + let y_max = (N_TICKS * step) as f32; | ||
| 113 | + let scale = (BOTTOM - TOP) / y_max; | ||
| 114 | + | ||
| 115 | + let y_ticks = (0..=N_TICKS) | ||
| 116 | + .map(|i| Tick { | ||
| 117 | + y: BOTTOM - (i * step) as f32 * scale, | ||
| 118 | + label: fmt_minutes(i * step), | ||
| 119 | + }) | ||
| 120 | + .collect(); | ||
| 121 | + | ||
| 122 | + let n = users.len().max(1) as f32; | ||
| 123 | + let slot = (RIGHT - LEFT) / n; | ||
| 124 | + let bar_w = (slot * 0.6).min(80.0); | ||
| 125 | + | ||
| 126 | + let bars = users | ||
| 127 | + .iter() | ||
| 128 | + .enumerate() | ||
| 129 | + .map(|(i, u)| { | ||
| 130 | + let center = LEFT + slot * (i as f32 + 0.5); | ||
| 131 | + let x = center - bar_w / 2.0; | ||
| 132 | + let mut cumul = 0i64; | ||
| 133 | + let mut segments = Vec::new(); | ||
| 134 | + if let Some(segs) = by_user.get(&u.id) { | ||
| 135 | + for r in segs { | ||
| 136 | + let y_bottom = BOTTOM - cumul as f32 * scale; | ||
| 137 | + cumul += r.total; | ||
| 138 | + let y_top = BOTTOM - cumul as f32 * scale; | ||
| 139 | + segments.push(SegView { | ||
| 140 | + x, | ||
| 141 | + y: y_top, | ||
| 142 | + w: bar_w, | ||
| 143 | + h: y_bottom - y_top, | ||
| 144 | + color: task_color(r.task_id), | ||
| 145 | + title: format!("{} — {}", r.task_name, fmt_minutes(r.total)), | ||
| 146 | + }); | ||
| 147 | + } | ||
| 148 | + } | ||
| 149 | + BarView { | ||
| 150 | + user_label: u.username.clone(), | ||
| 151 | + total_label: if cumul > 0 { | ||
| 152 | + fmt_minutes(cumul) | ||
| 153 | + } else { | ||
| 154 | + String::new() | ||
| 155 | + }, | ||
| 156 | + label_x: center, | ||
| 157 | + total_y: BOTTOM - cumul as f32 * scale - 5.0, | ||
| 158 | + segments, | ||
| 159 | + } | ||
| 160 | + }) | ||
| 161 | + .collect(); | ||
| 162 | + | ||
| 163 | + Self { | ||
| 164 | + title, | ||
| 165 | + empty, | ||
| 166 | + bars, | ||
| 167 | + y_ticks, | ||
| 168 | + baseline: BOTTOM, | ||
| 169 | + } | ||
| 170 | + } | ||
| 171 | + | ||
| 172 | + pub fn left(&self) -> f32 { | ||
| 173 | + LEFT | ||
| 174 | + } | ||
| 175 | + pub fn right(&self) -> f32 { | ||
| 176 | + RIGHT | ||
| 177 | + } | ||
| 178 | +} | ||
| 179 | + | ||
| 180 | +/// Légende commune : union des tâches présentes, triée par nom. | ||
| 181 | +pub fn legend(row_sets: &[&[StatRow]]) -> Vec<LegendItem> { | ||
| 182 | + let mut seen: BTreeMap<String, &'static str> = BTreeMap::new(); | ||
| 183 | + for rows in row_sets { | ||
| 184 | + for r in *rows { | ||
| 185 | + seen.entry(r.task_name.clone()) | ||
| 186 | + .or_insert_with(|| task_color(r.task_id)); | ||
| 187 | + } | ||
| 188 | + } | ||
| 189 | + seen.into_iter() | ||
| 190 | + .map(|(name, color)| LegendItem { name, color }) | ||
| 191 | + .collect() | ||
| 192 | +} | ||
| 193 | + | ||
| 194 | +#[cfg(test)] | ||
| 195 | +mod tests { | ||
| 196 | + use super::*; | ||
| 197 | + | ||
| 198 | + fn user(id: u128, name: &str) -> UserRow { | ||
| 199 | + UserRow { | ||
| 200 | + id: Uuid::from_u128(id), | ||
| 201 | + username: name.into(), | ||
| 202 | + } | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + fn row(uid: u128, tid: u128, task: &str, total: i64) -> StatRow { | ||
| 206 | + StatRow { | ||
| 207 | + user_id: Uuid::from_u128(uid), | ||
| 208 | + task_id: Uuid::from_u128(tid), | ||
| 209 | + task_name: task.into(), | ||
| 210 | + total, | ||
| 211 | + } | ||
| 212 | + } | ||
| 213 | + | ||
| 214 | + #[test] | ||
| 215 | + fn fmt() { | ||
| 216 | + assert_eq!(fmt_minutes(45), "45 min"); | ||
| 217 | + assert_eq!(fmt_minutes(120), "2 h"); | ||
| 218 | + assert_eq!(fmt_minutes(750), "12 h 30"); | ||
| 219 | + } | ||
| 220 | + | ||
| 221 | + #[test] | ||
| 222 | + fn geometry_sums_to_totals() { | ||
| 223 | + let users = [user(1, "alice"), user(2, "bob")]; | ||
| 224 | + let rows = [ | ||
| 225 | + row(1, 10, "dev", 90), | ||
| 226 | + row(1, 11, "réunions", 30), | ||
| 227 | + row(2, 10, "dev", 240), | ||
| 228 | + ]; | ||
| 229 | + let chart = ChartView::build("t".into(), &users, &rows); | ||
| 230 | + assert_eq!(chart.bars.len(), 2); | ||
| 231 | + | ||
| 232 | + // La hauteur cumulée des segments correspond au total, à l'échelle près. | ||
| 233 | + let alice = &chart.bars[0]; | ||
| 234 | + let bob = &chart.bars[1]; | ||
| 235 | + let h_alice: f32 = alice.segments.iter().map(|s| s.h).sum(); | ||
| 236 | + let h_bob: f32 = bob.segments.iter().map(|s| s.h).sum(); | ||
| 237 | + assert!((h_alice / h_bob - 120.0 / 240.0).abs() < 1e-3); | ||
| 238 | + assert_eq!(alice.total_label, "2 h"); | ||
| 239 | + assert_eq!(bob.total_label, "4 h"); | ||
| 240 | + | ||
| 241 | + // Empilement : segments contigus, du bas vers le haut. | ||
| 242 | + assert!((alice.segments[0].y - (alice.segments[1].y + alice.segments[1].h)).abs() < 1e-3); | ||
| 243 | + } | ||
| 244 | + | ||
| 245 | + #[test] | ||
| 246 | + fn zero_user_has_no_segments() { | ||
| 247 | + let users = [user(1, "alice"), user(2, "bob")]; | ||
| 248 | + let rows = [row(1, 10, "dev", 60)]; | ||
| 249 | + let chart = ChartView::build("t".into(), &users, &rows); | ||
| 250 | + assert!(chart.bars[1].segments.is_empty()); | ||
| 251 | + assert_eq!(chart.bars[1].total_label, ""); | ||
| 252 | + assert!(!chart.empty); | ||
| 253 | + } | ||
| 254 | + | ||
| 255 | + #[test] | ||
| 256 | + fn stable_color() { | ||
| 257 | + let id = Uuid::from_u128(42); | ||
| 258 | + assert_eq!(task_color(id), task_color(id)); | ||
| 259 | + } | ||
| 260 | + | ||
| 261 | + #[test] | ||
| 262 | + fn nice_steps() { | ||
| 263 | + assert_eq!(nice_step(1), 15); // ≤ 1 h → pas de 15 min | ||
| 264 | + assert_eq!(nice_step(240), 60); | ||
| 265 | + assert_eq!(nice_step(241), 120); | ||
| 266 | + } | ||
| 267 | +} | ||
added
src/cli.rs +56 -0 | new file mode 100644 | ||
| @@ -0,0 +1,56 @@ | ||
| 1 | +use anyhow::{Context, Result, bail}; | |
| 2 | +use biscuit_auth::KeyPair; | |
| 3 | +use sqlx::postgres::PgPoolOptions; | |
| 4 | + | |
| 5 | +use crate::auth::password; | |
| 6 | +use crate::config; | |
| 7 | +use crate::db::users; | |
| 8 | + | |
| 9 | +/// Génère un keypair racine et l'affiche (à faire une fois, en local). | |
| 10 | +pub fn keygen() { | |
| 11 | + let kp = KeyPair::new(); | |
| 12 | + println!("BISCUIT_PRIVATE_KEY={}", hex::encode(kp.private().to_bytes())); | |
| 13 | + println!("# clé publique (information) : {}", kp.public().to_bytes_hex()); | |
| 14 | +} | |
| 15 | + | |
| 16 | +/// `time-time user add <username>` — création de compte côté serveur. | |
| 17 | +pub async fn user_add(username: &str, password_arg: Option<String>) -> Result<()> { | |
| 18 | + let username = username.trim(); | |
| 19 | + if username.is_empty() { | |
| 20 | + bail!("le nom d'utilisateur ne peut pas être vide"); | |
| 21 | + } | |
| 22 | + | |
| 23 | + let password = match password_arg { | |
| 24 | + Some(p) => p, | |
| 25 | + None => { | |
| 26 | + let p1 = rpassword::prompt_password("Mot de passe : ")?; | |
| 27 | + let p2 = rpassword::prompt_password("Confirmation : ")?; | |
| 28 | + if p1 != p2 { | |
| 29 | + bail!("les mots de passe ne correspondent pas"); | |
| 30 | + } | |
| 31 | + p1 | |
| 32 | + } | |
| 33 | + }; | |
| 34 | + if password.len() < 8 { | |
| 35 | + bail!("le mot de passe doit faire au moins 8 caractères"); | |
| 36 | + } | |
| 37 | + | |
| 38 | + let pool = PgPoolOptions::new() | |
| 39 | + .max_connections(1) | |
| 40 | + .connect(&config::database_url()?) | |
| 41 | + .await | |
| 42 | + .context("connexion à la base")?; | |
| 43 | + sqlx::migrate!().run(&pool).await?; | |
| 44 | + | |
| 45 | + let hash = password::hash(&password)?; | |
| 46 | + match users::create(&pool, username, &hash).await { | |
| 47 | + Ok(id) => { | |
| 48 | + println!("Utilisateur « {username} » créé ({id})."); | |
| 49 | + Ok(()) | |
| 50 | + } | |
| 51 | + Err(sqlx::Error::Database(db)) if db.is_unique_violation() => { | |
| 52 | + bail!("l'utilisateur « {username} » existe déjà") | |
| 53 | + } | |
| 54 | + Err(e) => Err(e.into()), | |
| 55 | + } | |
| 56 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,56 @@ | |||
| 1 | +use anyhow::{Context, Result, bail}; | ||
| 2 | +use biscuit_auth::KeyPair; | ||
| 3 | +use sqlx::postgres::PgPoolOptions; | ||
| 4 | + | ||
| 5 | +use crate::auth::password; | ||
| 6 | +use crate::config; | ||
| 7 | +use crate::db::users; | ||
| 8 | + | ||
| 9 | +/// Génère un keypair racine et l'affiche (à faire une fois, en local). | ||
| 10 | +pub fn keygen() { | ||
| 11 | + let kp = KeyPair::new(); | ||
| 12 | + println!("BISCUIT_PRIVATE_KEY={}", hex::encode(kp.private().to_bytes())); | ||
| 13 | + println!("# clé publique (information) : {}", kp.public().to_bytes_hex()); | ||
| 14 | +} | ||
| 15 | + | ||
| 16 | +/// `time-time user add <username>` — création de compte côté serveur. | ||
| 17 | +pub async fn user_add(username: &str, password_arg: Option<String>) -> Result<()> { | ||
| 18 | + let username = username.trim(); | ||
| 19 | + if username.is_empty() { | ||
| 20 | + bail!("le nom d'utilisateur ne peut pas être vide"); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + let password = match password_arg { | ||
| 24 | + Some(p) => p, | ||
| 25 | + None => { | ||
| 26 | + let p1 = rpassword::prompt_password("Mot de passe : ")?; | ||
| 27 | + let p2 = rpassword::prompt_password("Confirmation : ")?; | ||
| 28 | + if p1 != p2 { | ||
| 29 | + bail!("les mots de passe ne correspondent pas"); | ||
| 30 | + } | ||
| 31 | + p1 | ||
| 32 | + } | ||
| 33 | + }; | ||
| 34 | + if password.len() < 8 { | ||
| 35 | + bail!("le mot de passe doit faire au moins 8 caractères"); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + let pool = PgPoolOptions::new() | ||
| 39 | + .max_connections(1) | ||
| 40 | + .connect(&config::database_url()?) | ||
| 41 | + .await | ||
| 42 | + .context("connexion à la base")?; | ||
| 43 | + sqlx::migrate!().run(&pool).await?; | ||
| 44 | + | ||
| 45 | + let hash = password::hash(&password)?; | ||
| 46 | + match users::create(&pool, username, &hash).await { | ||
| 47 | + Ok(id) => { | ||
| 48 | + println!("Utilisateur « {username} » créé ({id})."); | ||
| 49 | + Ok(()) | ||
| 50 | + } | ||
| 51 | + Err(sqlx::Error::Database(db)) if db.is_unique_violation() => { | ||
| 52 | + bail!("l'utilisateur « {username} » existe déjà") | ||
| 53 | + } | ||
| 54 | + Err(e) => Err(e.into()), | ||
| 55 | + } | ||
| 56 | +} | ||
added
src/config.rs +53 -0 | new file mode 100644 | ||
| @@ -0,0 +1,53 @@ | ||
| 1 | +use anyhow::{Context, Result}; | |
| 2 | + | |
| 3 | +/// URL de la base : POSTGRESQL_ADDON_URI (Clever Cloud) sinon DATABASE_URL, | |
| 4 | +/// avec sslmode=require imposé si absent. | |
| 5 | +pub fn database_url() -> Result<String> { | |
| 6 | + let url = std::env::var("POSTGRESQL_ADDON_URI") | |
| 7 | + .or_else(|_| std::env::var("DATABASE_URL")) | |
| 8 | + .context("POSTGRESQL_ADDON_URI ou DATABASE_URL doit être définie")?; | |
| 9 | + Ok(ensure_sslmode_require(&url)) | |
| 10 | +} | |
| 11 | + | |
| 12 | +fn ensure_sslmode_require(url: &str) -> String { | |
| 13 | + if url.contains("sslmode=") { | |
| 14 | + url.to_string() | |
| 15 | + } else if url.contains('?') { | |
| 16 | + format!("{url}&sslmode=require") | |
| 17 | + } else { | |
| 18 | + format!("{url}?sslmode=require") | |
| 19 | + } | |
| 20 | +} | |
| 21 | + | |
| 22 | +pub fn port() -> u16 { | |
| 23 | + std::env::var("PORT") | |
| 24 | + .ok() | |
| 25 | + .and_then(|p| p.parse().ok()) | |
| 26 | + .unwrap_or(8080) | |
| 27 | +} | |
| 28 | + | |
| 29 | +pub fn biscuit_private_key_hex() -> Result<String> { | |
| 30 | + std::env::var("BISCUIT_PRIVATE_KEY") | |
| 31 | + .context("BISCUIT_PRIVATE_KEY doit être définie (générer avec `time-time keygen`)") | |
| 32 | +} | |
| 33 | + | |
| 34 | +#[cfg(test)] | |
| 35 | +mod tests { | |
| 36 | + use super::*; | |
| 37 | + | |
| 38 | + #[test] | |
| 39 | + fn sslmode_appended() { | |
| 40 | + assert_eq!( | |
| 41 | + ensure_sslmode_require("postgres://u:p@h:5432/db"), | |
| 42 | + "postgres://u:p@h:5432/db?sslmode=require" | |
| 43 | + ); | |
| 44 | + assert_eq!( | |
| 45 | + ensure_sslmode_require("postgres://u:p@h:5432/db?foo=1"), | |
| 46 | + "postgres://u:p@h:5432/db?foo=1&sslmode=require" | |
| 47 | + ); | |
| 48 | + assert_eq!( | |
| 49 | + ensure_sslmode_require("postgres://u:p@h:5432/db?sslmode=verify-full"), | |
| 50 | + "postgres://u:p@h:5432/db?sslmode=verify-full" | |
| 51 | + ); | |
| 52 | + } | |
| 53 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | +use anyhow::{Context, Result}; | ||
| 2 | + | ||
| 3 | +/// URL de la base : POSTGRESQL_ADDON_URI (Clever Cloud) sinon DATABASE_URL, | ||
| 4 | +/// avec sslmode=require imposé si absent. | ||
| 5 | +pub fn database_url() -> Result<String> { | ||
| 6 | + let url = std::env::var("POSTGRESQL_ADDON_URI") | ||
| 7 | + .or_else(|_| std::env::var("DATABASE_URL")) | ||
| 8 | + .context("POSTGRESQL_ADDON_URI ou DATABASE_URL doit être définie")?; | ||
| 9 | + Ok(ensure_sslmode_require(&url)) | ||
| 10 | +} | ||
| 11 | + | ||
| 12 | +fn ensure_sslmode_require(url: &str) -> String { | ||
| 13 | + if url.contains("sslmode=") { | ||
| 14 | + url.to_string() | ||
| 15 | + } else if url.contains('?') { | ||
| 16 | + format!("{url}&sslmode=require") | ||
| 17 | + } else { | ||
| 18 | + format!("{url}?sslmode=require") | ||
| 19 | + } | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +pub fn port() -> u16 { | ||
| 23 | + std::env::var("PORT") | ||
| 24 | + .ok() | ||
| 25 | + .and_then(|p| p.parse().ok()) | ||
| 26 | + .unwrap_or(8080) | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +pub fn biscuit_private_key_hex() -> Result<String> { | ||
| 30 | + std::env::var("BISCUIT_PRIVATE_KEY") | ||
| 31 | + .context("BISCUIT_PRIVATE_KEY doit être définie (générer avec `time-time keygen`)") | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +#[cfg(test)] | ||
| 35 | +mod tests { | ||
| 36 | + use super::*; | ||
| 37 | + | ||
| 38 | + #[test] | ||
| 39 | + fn sslmode_appended() { | ||
| 40 | + assert_eq!( | ||
| 41 | + ensure_sslmode_require("postgres://u:p@h:5432/db"), | ||
| 42 | + "postgres://u:p@h:5432/db?sslmode=require" | ||
| 43 | + ); | ||
| 44 | + assert_eq!( | ||
| 45 | + ensure_sslmode_require("postgres://u:p@h:5432/db?foo=1"), | ||
| 46 | + "postgres://u:p@h:5432/db?foo=1&sslmode=require" | ||
| 47 | + ); | ||
| 48 | + assert_eq!( | ||
| 49 | + ensure_sslmode_require("postgres://u:p@h:5432/db?sslmode=verify-full"), | ||
| 50 | + "postgres://u:p@h:5432/db?sslmode=verify-full" | ||
| 51 | + ); | ||
| 52 | + } | ||
| 53 | +} | ||
added
src/db/entries.rs +106 -0 | new file mode 100644 | ||
| @@ -0,0 +1,106 @@ | ||
| 1 | +use chrono::NaiveDate; | |
| 2 | +use sqlx::PgPool; | |
| 3 | +use uuid::Uuid; | |
| 4 | + | |
| 5 | +pub const PAGE_SIZE: i64 = 20; | |
| 6 | + | |
| 7 | +#[derive(sqlx::FromRow)] | |
| 8 | +pub struct EntryRow { | |
| 9 | + pub id: Uuid, | |
| 10 | + pub task_id: Uuid, | |
| 11 | + pub task_name: String, | |
| 12 | + pub minutes: i32, | |
| 13 | + pub entry_date: NaiveDate, | |
| 14 | +} | |
| 15 | + | |
| 16 | +pub async fn insert( | |
| 17 | + pool: &PgPool, | |
| 18 | + user_id: Uuid, | |
| 19 | + task_id: Uuid, | |
| 20 | + minutes: i32, | |
| 21 | + entry_date: NaiveDate, | |
| 22 | +) -> sqlx::Result<Uuid> { | |
| 23 | + let id = Uuid::now_v7(); | |
| 24 | + sqlx::query( | |
| 25 | + "INSERT INTO time_entries (id, user_id, task_id, minutes, entry_date) | |
| 26 | + VALUES ($1, $2, $3, $4, $5)", | |
| 27 | + ) | |
| 28 | + .bind(id) | |
| 29 | + .bind(user_id) | |
| 30 | + .bind(task_id) | |
| 31 | + .bind(minutes) | |
| 32 | + .bind(entry_date) | |
| 33 | + .execute(pool) | |
| 34 | + .await?; | |
| 35 | + Ok(id) | |
| 36 | +} | |
| 37 | + | |
| 38 | +/// Page de MES temps, du plus récent au plus ancien. | |
| 39 | +/// Retourne (entrées, y a-t-il une page suivante) — on lit PAGE_SIZE + 1 lignes. | |
| 40 | +pub async fn list_page( | |
| 41 | + pool: &PgPool, | |
| 42 | + user_id: Uuid, | |
| 43 | + page: i64, | |
| 44 | +) -> sqlx::Result<(Vec<EntryRow>, bool)> { | |
| 45 | + let offset = (page - 1) * PAGE_SIZE; | |
| 46 | + let mut rows: Vec<EntryRow> = sqlx::query_as( | |
| 47 | + "SELECT e.id, e.task_id, t.name AS task_name, e.minutes, e.entry_date | |
| 48 | + FROM time_entries e JOIN tasks t ON t.id = e.task_id | |
| 49 | + WHERE e.user_id = $1 | |
| 50 | + ORDER BY e.entry_date DESC, e.id DESC | |
| 51 | + LIMIT $2 OFFSET $3", | |
| 52 | + ) | |
| 53 | + .bind(user_id) | |
| 54 | + .bind(PAGE_SIZE + 1) | |
| 55 | + .bind(offset) | |
| 56 | + .fetch_all(pool) | |
| 57 | + .await?; | |
| 58 | + let has_next = rows.len() as i64 > PAGE_SIZE; | |
| 59 | + rows.truncate(PAGE_SIZE as usize); | |
| 60 | + Ok((rows, has_next)) | |
| 61 | +} | |
| 62 | + | |
| 63 | +/// Une entrée m'appartenant (édition) — None si absente ou pas à moi. | |
| 64 | +pub async fn get_owned(pool: &PgPool, id: Uuid, user_id: Uuid) -> sqlx::Result<Option<EntryRow>> { | |
| 65 | + sqlx::query_as( | |
| 66 | + "SELECT e.id, e.task_id, t.name AS task_name, e.minutes, e.entry_date | |
| 67 | + FROM time_entries e JOIN tasks t ON t.id = e.task_id | |
| 68 | + WHERE e.id = $1 AND e.user_id = $2", | |
| 69 | + ) | |
| 70 | + .bind(id) | |
| 71 | + .bind(user_id) | |
| 72 | + .fetch_optional(pool) | |
| 73 | + .await | |
| 74 | +} | |
| 75 | + | |
| 76 | +/// Ownership vérifié en SQL : 0 ligne touchée = pas à moi / inexistant. | |
| 77 | +pub async fn update_owned( | |
| 78 | + pool: &PgPool, | |
| 79 | + id: Uuid, | |
| 80 | + user_id: Uuid, | |
| 81 | + task_id: Uuid, | |
| 82 | + minutes: i32, | |
| 83 | + entry_date: NaiveDate, | |
| 84 | +) -> sqlx::Result<u64> { | |
| 85 | + let res = sqlx::query( | |
| 86 | + "UPDATE time_entries SET task_id = $1, minutes = $2, entry_date = $3 | |
| 87 | + WHERE id = $4 AND user_id = $5", | |
| 88 | + ) | |
| 89 | + .bind(task_id) | |
| 90 | + .bind(minutes) | |
| 91 | + .bind(entry_date) | |
| 92 | + .bind(id) | |
| 93 | + .bind(user_id) | |
| 94 | + .execute(pool) | |
| 95 | + .await?; | |
| 96 | + Ok(res.rows_affected()) | |
| 97 | +} | |
| 98 | + | |
| 99 | +pub async fn delete_owned(pool: &PgPool, id: Uuid, user_id: Uuid) -> sqlx::Result<u64> { | |
| 100 | + let res = sqlx::query("DELETE FROM time_entries WHERE id = $1 AND user_id = $2") | |
| 101 | + .bind(id) | |
| 102 | + .bind(user_id) | |
| 103 | + .execute(pool) | |
| 104 | + .await?; | |
| 105 | + Ok(res.rows_affected()) | |
| 106 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,106 @@ | |||
| 1 | +use chrono::NaiveDate; | ||
| 2 | +use sqlx::PgPool; | ||
| 3 | +use uuid::Uuid; | ||
| 4 | + | ||
| 5 | +pub const PAGE_SIZE: i64 = 20; | ||
| 6 | + | ||
| 7 | +#[derive(sqlx::FromRow)] | ||
| 8 | +pub struct EntryRow { | ||
| 9 | + pub id: Uuid, | ||
| 10 | + pub task_id: Uuid, | ||
| 11 | + pub task_name: String, | ||
| 12 | + pub minutes: i32, | ||
| 13 | + pub entry_date: NaiveDate, | ||
| 14 | +} | ||
| 15 | + | ||
| 16 | +pub async fn insert( | ||
| 17 | + pool: &PgPool, | ||
| 18 | + user_id: Uuid, | ||
| 19 | + task_id: Uuid, | ||
| 20 | + minutes: i32, | ||
| 21 | + entry_date: NaiveDate, | ||
| 22 | +) -> sqlx::Result<Uuid> { | ||
| 23 | + let id = Uuid::now_v7(); | ||
| 24 | + sqlx::query( | ||
| 25 | + "INSERT INTO time_entries (id, user_id, task_id, minutes, entry_date) | ||
| 26 | + VALUES ($1, $2, $3, $4, $5)", | ||
| 27 | + ) | ||
| 28 | + .bind(id) | ||
| 29 | + .bind(user_id) | ||
| 30 | + .bind(task_id) | ||
| 31 | + .bind(minutes) | ||
| 32 | + .bind(entry_date) | ||
| 33 | + .execute(pool) | ||
| 34 | + .await?; | ||
| 35 | + Ok(id) | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +/// Page de MES temps, du plus récent au plus ancien. | ||
| 39 | +/// Retourne (entrées, y a-t-il une page suivante) — on lit PAGE_SIZE + 1 lignes. | ||
| 40 | +pub async fn list_page( | ||
| 41 | + pool: &PgPool, | ||
| 42 | + user_id: Uuid, | ||
| 43 | + page: i64, | ||
| 44 | +) -> sqlx::Result<(Vec<EntryRow>, bool)> { | ||
| 45 | + let offset = (page - 1) * PAGE_SIZE; | ||
| 46 | + let mut rows: Vec<EntryRow> = sqlx::query_as( | ||
| 47 | + "SELECT e.id, e.task_id, t.name AS task_name, e.minutes, e.entry_date | ||
| 48 | + FROM time_entries e JOIN tasks t ON t.id = e.task_id | ||
| 49 | + WHERE e.user_id = $1 | ||
| 50 | + ORDER BY e.entry_date DESC, e.id DESC | ||
| 51 | + LIMIT $2 OFFSET $3", | ||
| 52 | + ) | ||
| 53 | + .bind(user_id) | ||
| 54 | + .bind(PAGE_SIZE + 1) | ||
| 55 | + .bind(offset) | ||
| 56 | + .fetch_all(pool) | ||
| 57 | + .await?; | ||
| 58 | + let has_next = rows.len() as i64 > PAGE_SIZE; | ||
| 59 | + rows.truncate(PAGE_SIZE as usize); | ||
| 60 | + Ok((rows, has_next)) | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +/// Une entrée m'appartenant (édition) — None si absente ou pas à moi. | ||
| 64 | +pub async fn get_owned(pool: &PgPool, id: Uuid, user_id: Uuid) -> sqlx::Result<Option<EntryRow>> { | ||
| 65 | + sqlx::query_as( | ||
| 66 | + "SELECT e.id, e.task_id, t.name AS task_name, e.minutes, e.entry_date | ||
| 67 | + FROM time_entries e JOIN tasks t ON t.id = e.task_id | ||
| 68 | + WHERE e.id = $1 AND e.user_id = $2", | ||
| 69 | + ) | ||
| 70 | + .bind(id) | ||
| 71 | + .bind(user_id) | ||
| 72 | + .fetch_optional(pool) | ||
| 73 | + .await | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +/// Ownership vérifié en SQL : 0 ligne touchée = pas à moi / inexistant. | ||
| 77 | +pub async fn update_owned( | ||
| 78 | + pool: &PgPool, | ||
| 79 | + id: Uuid, | ||
| 80 | + user_id: Uuid, | ||
| 81 | + task_id: Uuid, | ||
| 82 | + minutes: i32, | ||
| 83 | + entry_date: NaiveDate, | ||
| 84 | +) -> sqlx::Result<u64> { | ||
| 85 | + let res = sqlx::query( | ||
| 86 | + "UPDATE time_entries SET task_id = $1, minutes = $2, entry_date = $3 | ||
| 87 | + WHERE id = $4 AND user_id = $5", | ||
| 88 | + ) | ||
| 89 | + .bind(task_id) | ||
| 90 | + .bind(minutes) | ||
| 91 | + .bind(entry_date) | ||
| 92 | + .bind(id) | ||
| 93 | + .bind(user_id) | ||
| 94 | + .execute(pool) | ||
| 95 | + .await?; | ||
| 96 | + Ok(res.rows_affected()) | ||
| 97 | +} | ||
| 98 | + | ||
| 99 | +pub async fn delete_owned(pool: &PgPool, id: Uuid, user_id: Uuid) -> sqlx::Result<u64> { | ||
| 100 | + let res = sqlx::query("DELETE FROM time_entries WHERE id = $1 AND user_id = $2") | ||
| 101 | + .bind(id) | ||
| 102 | + .bind(user_id) | ||
| 103 | + .execute(pool) | ||
| 104 | + .await?; | ||
| 105 | + Ok(res.rows_affected()) | ||
| 106 | +} | ||
added
src/db/mod.rs +4 -0 | new file mode 100644 | ||
| @@ -0,0 +1,4 @@ | ||
| 1 | +pub mod entries; | |
| 2 | +pub mod stats; | |
| 3 | +pub mod tasks; | |
| 4 | +pub mod users; | |
| new file mode 100644 | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | +pub mod entries; | ||
| 2 | +pub mod stats; | ||
| 3 | +pub mod tasks; | ||
| 4 | +pub mod users; | ||
added
src/db/stats.rs +33 -0 | new file mode 100644 | ||
| @@ -0,0 +1,33 @@ | ||
| 1 | +use chrono::NaiveDate; | |
| 2 | +use sqlx::PgPool; | |
| 3 | +use uuid::Uuid; | |
| 4 | + | |
| 5 | +#[derive(sqlx::FromRow)] | |
| 6 | +pub struct StatRow { | |
| 7 | + pub user_id: Uuid, | |
| 8 | + pub task_id: Uuid, | |
| 9 | + pub task_name: String, | |
| 10 | + pub total: i64, | |
| 11 | +} | |
| 12 | + | |
| 13 | +/// Totaux par utilisateur × tâche sur [from, to) ; bornes NULL = depuis le début. | |
| 14 | +/// Les tâches archivées restent incluses. | |
| 15 | +pub async fn totals( | |
| 16 | + pool: &PgPool, | |
| 17 | + from: Option<NaiveDate>, | |
| 18 | + to: Option<NaiveDate>, | |
| 19 | +) -> sqlx::Result<Vec<StatRow>> { | |
| 20 | + sqlx::query_as( | |
| 21 | + "SELECT e.user_id, e.task_id, t.name AS task_name, | |
| 22 | + SUM(e.minutes)::BIGINT AS total | |
| 23 | + FROM time_entries e | |
| 24 | + JOIN tasks t ON t.id = e.task_id | |
| 25 | + WHERE ($1::date IS NULL OR e.entry_date >= $1) | |
| 26 | + AND ($2::date IS NULL OR e.entry_date < $2) | |
| 27 | + GROUP BY e.user_id, e.task_id, t.name", | |
| 28 | + ) | |
| 29 | + .bind(from) | |
| 30 | + .bind(to) | |
| 31 | + .fetch_all(pool) | |
| 32 | + .await | |
| 33 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | +use chrono::NaiveDate; | ||
| 2 | +use sqlx::PgPool; | ||
| 3 | +use uuid::Uuid; | ||
| 4 | + | ||
| 5 | +#[derive(sqlx::FromRow)] | ||
| 6 | +pub struct StatRow { | ||
| 7 | + pub user_id: Uuid, | ||
| 8 | + pub task_id: Uuid, | ||
| 9 | + pub task_name: String, | ||
| 10 | + pub total: i64, | ||
| 11 | +} | ||
| 12 | + | ||
| 13 | +/// Totaux par utilisateur × tâche sur [from, to) ; bornes NULL = depuis le début. | ||
| 14 | +/// Les tâches archivées restent incluses. | ||
| 15 | +pub async fn totals( | ||
| 16 | + pool: &PgPool, | ||
| 17 | + from: Option<NaiveDate>, | ||
| 18 | + to: Option<NaiveDate>, | ||
| 19 | +) -> sqlx::Result<Vec<StatRow>> { | ||
| 20 | + sqlx::query_as( | ||
| 21 | + "SELECT e.user_id, e.task_id, t.name AS task_name, | ||
| 22 | + SUM(e.minutes)::BIGINT AS total | ||
| 23 | + FROM time_entries e | ||
| 24 | + JOIN tasks t ON t.id = e.task_id | ||
| 25 | + WHERE ($1::date IS NULL OR e.entry_date >= $1) | ||
| 26 | + AND ($2::date IS NULL OR e.entry_date < $2) | ||
| 27 | + GROUP BY e.user_id, e.task_id, t.name", | ||
| 28 | + ) | ||
| 29 | + .bind(from) | ||
| 30 | + .bind(to) | ||
| 31 | + .fetch_all(pool) | ||
| 32 | + .await | ||
| 33 | +} | ||
added
src/db/tasks.rs +50 -0 | new file mode 100644 | ||
| @@ -0,0 +1,50 @@ | ||
| 1 | +use sqlx::PgPool; | |
| 2 | +use uuid::Uuid; | |
| 3 | + | |
| 4 | +#[derive(sqlx::FromRow)] | |
| 5 | +pub struct TaskRow { | |
| 6 | + pub id: Uuid, | |
| 7 | + pub name: String, | |
| 8 | + pub archived: bool, | |
| 9 | +} | |
| 10 | + | |
| 11 | +/// Tâches proposées dans le formulaire d'ajout (non archivées). | |
| 12 | +pub async fn list_active(pool: &PgPool) -> sqlx::Result<Vec<TaskRow>> { | |
| 13 | + sqlx::query_as("SELECT id, name, archived FROM tasks WHERE NOT archived ORDER BY name") | |
| 14 | + .fetch_all(pool) | |
| 15 | + .await | |
| 16 | +} | |
| 17 | + | |
| 18 | +/// Toutes les tâches (écran Paramètres). | |
| 19 | +pub async fn list_all(pool: &PgPool) -> sqlx::Result<Vec<TaskRow>> { | |
| 20 | + sqlx::query_as("SELECT id, name, archived FROM tasks ORDER BY archived, name") | |
| 21 | + .fetch_all(pool) | |
| 22 | + .await | |
| 23 | +} | |
| 24 | + | |
| 25 | +pub async fn create(pool: &PgPool, name: &str) -> sqlx::Result<Uuid> { | |
| 26 | + let id = Uuid::now_v7(); | |
| 27 | + sqlx::query("INSERT INTO tasks (id, name) VALUES ($1, $2)") | |
| 28 | + .bind(id) | |
| 29 | + .bind(name) | |
| 30 | + .execute(pool) | |
| 31 | + .await?; | |
| 32 | + Ok(id) | |
| 33 | +} | |
| 34 | + | |
| 35 | +pub async fn rename(pool: &PgPool, id: Uuid, name: &str) -> sqlx::Result<u64> { | |
| 36 | + let res = sqlx::query("UPDATE tasks SET name = $1 WHERE id = $2") | |
| 37 | + .bind(name) | |
| 38 | + .bind(id) | |
| 39 | + .execute(pool) | |
| 40 | + .await?; | |
| 41 | + Ok(res.rows_affected()) | |
| 42 | +} | |
| 43 | + | |
| 44 | +pub async fn toggle_archived(pool: &PgPool, id: Uuid) -> sqlx::Result<u64> { | |
| 45 | + let res = sqlx::query("UPDATE tasks SET archived = NOT archived WHERE id = $1") | |
| 46 | + .bind(id) | |
| 47 | + .execute(pool) | |
| 48 | + .await?; | |
| 49 | + Ok(res.rows_affected()) | |
| 50 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | +use sqlx::PgPool; | ||
| 2 | +use uuid::Uuid; | ||
| 3 | + | ||
| 4 | +#[derive(sqlx::FromRow)] | ||
| 5 | +pub struct TaskRow { | ||
| 6 | + pub id: Uuid, | ||
| 7 | + pub name: String, | ||
| 8 | + pub archived: bool, | ||
| 9 | +} | ||
| 10 | + | ||
| 11 | +/// Tâches proposées dans le formulaire d'ajout (non archivées). | ||
| 12 | +pub async fn list_active(pool: &PgPool) -> sqlx::Result<Vec<TaskRow>> { | ||
| 13 | + sqlx::query_as("SELECT id, name, archived FROM tasks WHERE NOT archived ORDER BY name") | ||
| 14 | + .fetch_all(pool) | ||
| 15 | + .await | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +/// Toutes les tâches (écran Paramètres). | ||
| 19 | +pub async fn list_all(pool: &PgPool) -> sqlx::Result<Vec<TaskRow>> { | ||
| 20 | + sqlx::query_as("SELECT id, name, archived FROM tasks ORDER BY archived, name") | ||
| 21 | + .fetch_all(pool) | ||
| 22 | + .await | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +pub async fn create(pool: &PgPool, name: &str) -> sqlx::Result<Uuid> { | ||
| 26 | + let id = Uuid::now_v7(); | ||
| 27 | + sqlx::query("INSERT INTO tasks (id, name) VALUES ($1, $2)") | ||
| 28 | + .bind(id) | ||
| 29 | + .bind(name) | ||
| 30 | + .execute(pool) | ||
| 31 | + .await?; | ||
| 32 | + Ok(id) | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | +pub async fn rename(pool: &PgPool, id: Uuid, name: &str) -> sqlx::Result<u64> { | ||
| 36 | + let res = sqlx::query("UPDATE tasks SET name = $1 WHERE id = $2") | ||
| 37 | + .bind(name) | ||
| 38 | + .bind(id) | ||
| 39 | + .execute(pool) | ||
| 40 | + .await?; | ||
| 41 | + Ok(res.rows_affected()) | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +pub async fn toggle_archived(pool: &PgPool, id: Uuid) -> sqlx::Result<u64> { | ||
| 45 | + let res = sqlx::query("UPDATE tasks SET archived = NOT archived WHERE id = $1") | ||
| 46 | + .bind(id) | ||
| 47 | + .execute(pool) | ||
| 48 | + .await?; | ||
| 49 | + Ok(res.rows_affected()) | ||
| 50 | +} | ||
added
src/db/users.rs +55 -0 | new file mode 100644 | ||
| @@ -0,0 +1,55 @@ | ||
| 1 | +use sqlx::PgPool; | |
| 2 | +use uuid::Uuid; | |
| 3 | + | |
| 4 | +#[derive(sqlx::FromRow)] | |
| 5 | +pub struct UserRow { | |
| 6 | + pub id: Uuid, | |
| 7 | + pub username: String, | |
| 8 | +} | |
| 9 | + | |
| 10 | +#[derive(sqlx::FromRow)] | |
| 11 | +pub struct Credentials { | |
| 12 | + pub id: Uuid, | |
| 13 | + pub password_hash: String, | |
| 14 | +} | |
| 15 | + | |
| 16 | +pub async fn create(pool: &PgPool, username: &str, password_hash: &str) -> sqlx::Result<Uuid> { | |
| 17 | + let id = Uuid::now_v7(); | |
| 18 | + sqlx::query("INSERT INTO users (id, username, password_hash) VALUES ($1, $2, $3)") | |
| 19 | + .bind(id) | |
| 20 | + .bind(username) | |
| 21 | + .bind(password_hash) | |
| 22 | + .execute(pool) | |
| 23 | + .await?; | |
| 24 | + Ok(id) | |
| 25 | +} | |
| 26 | + | |
| 27 | +pub async fn find_credentials(pool: &PgPool, username: &str) -> sqlx::Result<Option<Credentials>> { | |
| 28 | + sqlx::query_as("SELECT id, password_hash FROM users WHERE username = $1") | |
| 29 | + .bind(username) | |
| 30 | + .fetch_optional(pool) | |
| 31 | + .await | |
| 32 | +} | |
| 33 | + | |
| 34 | +pub async fn password_hash(pool: &PgPool, id: Uuid) -> sqlx::Result<Option<String>> { | |
| 35 | + let row: Option<(String,)> = sqlx::query_as("SELECT password_hash FROM users WHERE id = $1") | |
| 36 | + .bind(id) | |
| 37 | + .fetch_optional(pool) | |
| 38 | + .await?; | |
| 39 | + Ok(row.map(|(h,)| h)) | |
| 40 | +} | |
| 41 | + | |
| 42 | +pub async fn update_password(pool: &PgPool, id: Uuid, password_hash: &str) -> sqlx::Result<()> { | |
| 43 | + sqlx::query("UPDATE users SET password_hash = $1 WHERE id = $2") | |
| 44 | + .bind(password_hash) | |
| 45 | + .bind(id) | |
| 46 | + .execute(pool) | |
| 47 | + .await?; | |
| 48 | + Ok(()) | |
| 49 | +} | |
| 50 | + | |
| 51 | +pub async fn list_all(pool: &PgPool) -> sqlx::Result<Vec<UserRow>> { | |
| 52 | + sqlx::query_as("SELECT id, username FROM users ORDER BY username") | |
| 53 | + .fetch_all(pool) | |
| 54 | + .await | |
| 55 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | +use sqlx::PgPool; | ||
| 2 | +use uuid::Uuid; | ||
| 3 | + | ||
| 4 | +#[derive(sqlx::FromRow)] | ||
| 5 | +pub struct UserRow { | ||
| 6 | + pub id: Uuid, | ||
| 7 | + pub username: String, | ||
| 8 | +} | ||
| 9 | + | ||
| 10 | +#[derive(sqlx::FromRow)] | ||
| 11 | +pub struct Credentials { | ||
| 12 | + pub id: Uuid, | ||
| 13 | + pub password_hash: String, | ||
| 14 | +} | ||
| 15 | + | ||
| 16 | +pub async fn create(pool: &PgPool, username: &str, password_hash: &str) -> sqlx::Result<Uuid> { | ||
| 17 | + let id = Uuid::now_v7(); | ||
| 18 | + sqlx::query("INSERT INTO users (id, username, password_hash) VALUES ($1, $2, $3)") | ||
| 19 | + .bind(id) | ||
| 20 | + .bind(username) | ||
| 21 | + .bind(password_hash) | ||
| 22 | + .execute(pool) | ||
| 23 | + .await?; | ||
| 24 | + Ok(id) | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +pub async fn find_credentials(pool: &PgPool, username: &str) -> sqlx::Result<Option<Credentials>> { | ||
| 28 | + sqlx::query_as("SELECT id, password_hash FROM users WHERE username = $1") | ||
| 29 | + .bind(username) | ||
| 30 | + .fetch_optional(pool) | ||
| 31 | + .await | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +pub async fn password_hash(pool: &PgPool, id: Uuid) -> sqlx::Result<Option<String>> { | ||
| 35 | + let row: Option<(String,)> = sqlx::query_as("SELECT password_hash FROM users WHERE id = $1") | ||
| 36 | + .bind(id) | ||
| 37 | + .fetch_optional(pool) | ||
| 38 | + .await?; | ||
| 39 | + Ok(row.map(|(h,)| h)) | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +pub async fn update_password(pool: &PgPool, id: Uuid, password_hash: &str) -> sqlx::Result<()> { | ||
| 43 | + sqlx::query("UPDATE users SET password_hash = $1 WHERE id = $2") | ||
| 44 | + .bind(password_hash) | ||
| 45 | + .bind(id) | ||
| 46 | + .execute(pool) | ||
| 47 | + .await?; | ||
| 48 | + Ok(()) | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +pub async fn list_all(pool: &PgPool) -> sqlx::Result<Vec<UserRow>> { | ||
| 52 | + sqlx::query_as("SELECT id, username FROM users ORDER BY username") | ||
| 53 | + .fetch_all(pool) | ||
| 54 | + .await | ||
| 55 | +} | ||
added
src/error.rs +48 -0 | new file mode 100644 | ||
| @@ -0,0 +1,48 @@ | ||
| 1 | +use axum::http::StatusCode; | |
| 2 | +use axum::response::{Html, IntoResponse, Response}; | |
| 3 | + | |
| 4 | +#[derive(Debug, thiserror::Error)] | |
| 5 | +pub enum AppError { | |
| 6 | + #[error("erreur base de données : {0}")] | |
| 7 | + Db(#[from] sqlx::Error), | |
| 8 | + #[error("erreur template : {0}")] | |
| 9 | + Template(#[from] askama::Error), | |
| 10 | + #[error("introuvable")] | |
| 11 | + NotFound, | |
| 12 | + #[error("formulaire invalide : {0}")] | |
| 13 | + BadRequest(String), | |
| 14 | + #[error("jeton CSRF invalide")] | |
| 15 | + Csrf, | |
| 16 | + #[error("{0}")] | |
| 17 | + Internal(#[from] anyhow::Error), | |
| 18 | +} | |
| 19 | + | |
| 20 | +impl IntoResponse for AppError { | |
| 21 | + fn into_response(self) -> Response { | |
| 22 | + let (status, message) = match &self { | |
| 23 | + AppError::NotFound => (StatusCode::NOT_FOUND, "Page introuvable.".to_string()), | |
| 24 | + AppError::BadRequest(m) => (StatusCode::BAD_REQUEST, m.clone()), | |
| 25 | + AppError::Csrf => ( | |
| 26 | + StatusCode::FORBIDDEN, | |
| 27 | + "Formulaire expiré, veuillez réessayer.".to_string(), | |
| 28 | + ), | |
| 29 | + _ => { | |
| 30 | + tracing::error!(error = %self, "erreur interne"); | |
| 31 | + ( | |
| 32 | + StatusCode::INTERNAL_SERVER_ERROR, | |
| 33 | + "Erreur interne.".to_string(), | |
| 34 | + ) | |
| 35 | + } | |
| 36 | + }; | |
| 37 | + let body = format!( | |
| 38 | + r#"<!doctype html><html lang="fr"><head><meta charset="utf-8"> | |
| 39 | +<meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 40 | +<link rel="stylesheet" href="/static/style.css"><title>{status}</title></head> | |
| 41 | +<body><main class="page"><h1>{code}</h1><p>{message}</p> | |
| 42 | +<p><a class="btn" href="/">← Accueil</a></p></main></body></html>"#, | |
| 43 | + status = status.as_u16(), | |
| 44 | + code = status.as_u16(), | |
| 45 | + ); | |
| 46 | + (status, Html(body)).into_response() | |
| 47 | + } | |
| 48 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | +use axum::http::StatusCode; | ||
| 2 | +use axum::response::{Html, IntoResponse, Response}; | ||
| 3 | + | ||
| 4 | +#[derive(Debug, thiserror::Error)] | ||
| 5 | +pub enum AppError { | ||
| 6 | + #[error("erreur base de données : {0}")] | ||
| 7 | + Db(#[from] sqlx::Error), | ||
| 8 | + #[error("erreur template : {0}")] | ||
| 9 | + Template(#[from] askama::Error), | ||
| 10 | + #[error("introuvable")] | ||
| 11 | + NotFound, | ||
| 12 | + #[error("formulaire invalide : {0}")] | ||
| 13 | + BadRequest(String), | ||
| 14 | + #[error("jeton CSRF invalide")] | ||
| 15 | + Csrf, | ||
| 16 | + #[error("{0}")] | ||
| 17 | + Internal(#[from] anyhow::Error), | ||
| 18 | +} | ||
| 19 | + | ||
| 20 | +impl IntoResponse for AppError { | ||
| 21 | + fn into_response(self) -> Response { | ||
| 22 | + let (status, message) = match &self { | ||
| 23 | + AppError::NotFound => (StatusCode::NOT_FOUND, "Page introuvable.".to_string()), | ||
| 24 | + AppError::BadRequest(m) => (StatusCode::BAD_REQUEST, m.clone()), | ||
| 25 | + AppError::Csrf => ( | ||
| 26 | + StatusCode::FORBIDDEN, | ||
| 27 | + "Formulaire expiré, veuillez réessayer.".to_string(), | ||
| 28 | + ), | ||
| 29 | + _ => { | ||
| 30 | + tracing::error!(error = %self, "erreur interne"); | ||
| 31 | + ( | ||
| 32 | + StatusCode::INTERNAL_SERVER_ERROR, | ||
| 33 | + "Erreur interne.".to_string(), | ||
| 34 | + ) | ||
| 35 | + } | ||
| 36 | + }; | ||
| 37 | + let body = format!( | ||
| 38 | + r#"<!doctype html><html lang="fr"><head><meta charset="utf-8"> | ||
| 39 | +<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 40 | +<link rel="stylesheet" href="/static/style.css"><title>{status}</title></head> | ||
| 41 | +<body><main class="page"><h1>{code}</h1><p>{message}</p> | ||
| 42 | +<p><a class="btn" href="/">← Accueil</a></p></main></body></html>"#, | ||
| 43 | + status = status.as_u16(), | ||
| 44 | + code = status.as_u16(), | ||
| 45 | + ); | ||
| 46 | + (status, Html(body)).into_response() | ||
| 47 | + } | ||
| 48 | +} | ||
added
src/main.rs +102 -0 | new file mode 100644 | ||
| @@ -0,0 +1,102 @@ | ||
| 1 | +mod auth; | |
| 2 | +mod charts; | |
| 3 | +mod cli; | |
| 4 | +mod config; | |
| 5 | +mod db; | |
| 6 | +mod error; | |
| 7 | +mod period; | |
| 8 | +mod render; | |
| 9 | +mod routes; | |
| 10 | +mod state; | |
| 11 | + | |
| 12 | +use anyhow::{Context, Result}; | |
| 13 | +use clap::{Parser, Subcommand}; | |
| 14 | +use sqlx::postgres::PgPoolOptions; | |
| 15 | + | |
| 16 | +use crate::state::AppState; | |
| 17 | + | |
| 18 | +#[derive(Parser)] | |
| 19 | +#[command(name = "time-time", about = "Comptage de temps passé sur des tâches")] | |
| 20 | +struct Cli { | |
| 21 | + #[command(subcommand)] | |
| 22 | + command: Option<Command>, | |
| 23 | +} | |
| 24 | + | |
| 25 | +#[derive(Subcommand)] | |
| 26 | +enum Command { | |
| 27 | + /// Lance le serveur web (défaut) | |
| 28 | + Serve, | |
| 29 | + /// Gestion des utilisateurs | |
| 30 | + User { | |
| 31 | + #[command(subcommand)] | |
| 32 | + command: UserCommand, | |
| 33 | + }, | |
| 34 | + /// Génère la paire de clés Biscuit racine | |
| 35 | + Keygen, | |
| 36 | +} | |
| 37 | + | |
| 38 | +#[derive(Subcommand)] | |
| 39 | +enum UserCommand { | |
| 40 | + /// Crée un utilisateur | |
| 41 | + Add { | |
| 42 | + username: String, | |
| 43 | + #[arg(long)] | |
| 44 | + password: Option<String>, | |
| 45 | + }, | |
| 46 | +} | |
| 47 | + | |
| 48 | +#[tokio::main] | |
| 49 | +async fn main() -> Result<()> { | |
| 50 | + let cli = Cli::parse(); | |
| 51 | + match cli.command { | |
| 52 | + None | Some(Command::Serve) => serve().await, | |
| 53 | + Some(Command::Keygen) => { | |
| 54 | + cli::keygen(); | |
| 55 | + Ok(()) | |
| 56 | + } | |
| 57 | + Some(Command::User { | |
| 58 | + command: UserCommand::Add { username, password }, | |
| 59 | + }) => cli::user_add(&username, password).await, | |
| 60 | + } | |
| 61 | +} | |
| 62 | + | |
| 63 | +async fn serve() -> Result<()> { | |
| 64 | + tracing_subscriber::fmt() | |
| 65 | + .with_env_filter( | |
| 66 | + tracing_subscriber::EnvFilter::try_from_default_env() | |
| 67 | + .unwrap_or_else(|_| "info".into()), | |
| 68 | + ) | |
| 69 | + .init(); | |
| 70 | + | |
| 71 | + let pool = PgPoolOptions::new() | |
| 72 | + .max_connections(5) | |
| 73 | + .connect(&config::database_url()?) | |
| 74 | + .await | |
| 75 | + .context("connexion à la base")?; | |
| 76 | + sqlx::migrate!().run(&pool).await.context("migrations")?; | |
| 77 | + | |
| 78 | + let state = AppState::new(pool.clone(), &config::biscuit_private_key_hex()?)?; | |
| 79 | + | |
| 80 | + // Cache de révocation : chargement initial puis rafraîchissement périodique. | |
| 81 | + auth::revocation::reload(&pool, &state.revocations) | |
| 82 | + .await | |
| 83 | + .context("chargement de la liste de révocation")?; | |
| 84 | + tokio::spawn(auth::revocation::refresher( | |
| 85 | + pool.clone(), | |
| 86 | + state.revocations.clone(), | |
| 87 | + )); | |
| 88 | + | |
| 89 | + let app = routes::router(state); | |
| 90 | + let addr = format!("0.0.0.0:{}", config::port()); | |
| 91 | + let listener = tokio::net::TcpListener::bind(&addr) | |
| 92 | + .await | |
| 93 | + .with_context(|| format!("bind {addr}"))?; | |
| 94 | + tracing::info!("écoute sur http://{addr}"); | |
| 95 | + | |
| 96 | + axum::serve(listener, app) | |
| 97 | + .with_graceful_shutdown(async { | |
| 98 | + let _ = tokio::signal::ctrl_c().await; | |
| 99 | + }) | |
| 100 | + .await?; | |
| 101 | + Ok(()) | |
| 102 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,102 @@ | |||
| 1 | +mod auth; | ||
| 2 | +mod charts; | ||
| 3 | +mod cli; | ||
| 4 | +mod config; | ||
| 5 | +mod db; | ||
| 6 | +mod error; | ||
| 7 | +mod period; | ||
| 8 | +mod render; | ||
| 9 | +mod routes; | ||
| 10 | +mod state; | ||
| 11 | + | ||
| 12 | +use anyhow::{Context, Result}; | ||
| 13 | +use clap::{Parser, Subcommand}; | ||
| 14 | +use sqlx::postgres::PgPoolOptions; | ||
| 15 | + | ||
| 16 | +use crate::state::AppState; | ||
| 17 | + | ||
| 18 | +#[derive(Parser)] | ||
| 19 | +#[command(name = "time-time", about = "Comptage de temps passé sur des tâches")] | ||
| 20 | +struct Cli { | ||
| 21 | + #[command(subcommand)] | ||
| 22 | + command: Option<Command>, | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +#[derive(Subcommand)] | ||
| 26 | +enum Command { | ||
| 27 | + /// Lance le serveur web (défaut) | ||
| 28 | + Serve, | ||
| 29 | + /// Gestion des utilisateurs | ||
| 30 | + User { | ||
| 31 | + #[command(subcommand)] | ||
| 32 | + command: UserCommand, | ||
| 33 | + }, | ||
| 34 | + /// Génère la paire de clés Biscuit racine | ||
| 35 | + Keygen, | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +#[derive(Subcommand)] | ||
| 39 | +enum UserCommand { | ||
| 40 | + /// Crée un utilisateur | ||
| 41 | + Add { | ||
| 42 | + username: String, | ||
| 43 | + #[arg(long)] | ||
| 44 | + password: Option<String>, | ||
| 45 | + }, | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +#[tokio::main] | ||
| 49 | +async fn main() -> Result<()> { | ||
| 50 | + let cli = Cli::parse(); | ||
| 51 | + match cli.command { | ||
| 52 | + None | Some(Command::Serve) => serve().await, | ||
| 53 | + Some(Command::Keygen) => { | ||
| 54 | + cli::keygen(); | ||
| 55 | + Ok(()) | ||
| 56 | + } | ||
| 57 | + Some(Command::User { | ||
| 58 | + command: UserCommand::Add { username, password }, | ||
| 59 | + }) => cli::user_add(&username, password).await, | ||
| 60 | + } | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +async fn serve() -> Result<()> { | ||
| 64 | + tracing_subscriber::fmt() | ||
| 65 | + .with_env_filter( | ||
| 66 | + tracing_subscriber::EnvFilter::try_from_default_env() | ||
| 67 | + .unwrap_or_else(|_| "info".into()), | ||
| 68 | + ) | ||
| 69 | + .init(); | ||
| 70 | + | ||
| 71 | + let pool = PgPoolOptions::new() | ||
| 72 | + .max_connections(5) | ||
| 73 | + .connect(&config::database_url()?) | ||
| 74 | + .await | ||
| 75 | + .context("connexion à la base")?; | ||
| 76 | + sqlx::migrate!().run(&pool).await.context("migrations")?; | ||
| 77 | + | ||
| 78 | + let state = AppState::new(pool.clone(), &config::biscuit_private_key_hex()?)?; | ||
| 79 | + | ||
| 80 | + // Cache de révocation : chargement initial puis rafraîchissement périodique. | ||
| 81 | + auth::revocation::reload(&pool, &state.revocations) | ||
| 82 | + .await | ||
| 83 | + .context("chargement de la liste de révocation")?; | ||
| 84 | + tokio::spawn(auth::revocation::refresher( | ||
| 85 | + pool.clone(), | ||
| 86 | + state.revocations.clone(), | ||
| 87 | + )); | ||
| 88 | + | ||
| 89 | + let app = routes::router(state); | ||
| 90 | + let addr = format!("0.0.0.0:{}", config::port()); | ||
| 91 | + let listener = tokio::net::TcpListener::bind(&addr) | ||
| 92 | + .await | ||
| 93 | + .with_context(|| format!("bind {addr}"))?; | ||
| 94 | + tracing::info!("écoute sur http://{addr}"); | ||
| 95 | + | ||
| 96 | + axum::serve(listener, app) | ||
| 97 | + .with_graceful_shutdown(async { | ||
| 98 | + let _ = tokio::signal::ctrl_c().await; | ||
| 99 | + }) | ||
| 100 | + .await?; | ||
| 101 | + Ok(()) | ||
| 102 | +} | ||
added
src/period.rs +239 -0 | new file mode 100644 | ||
| @@ -0,0 +1,239 @@ | ||
| 1 | +use std::fmt; | |
| 2 | + | |
| 3 | +use chrono::{Datelike, Duration, NaiveDate, Utc, Weekday}; | |
| 4 | +use chrono_tz::Europe::Paris; | |
| 5 | + | |
| 6 | +/// Aujourd'hui en Europe/Paris — seule intrusion de la timezone dans l'appli | |
| 7 | +/// (les entrées sont stockées en DATE). | |
| 8 | +pub fn paris_today() -> NaiveDate { | |
| 9 | + Utc::now().with_timezone(&Paris).date_naive() | |
| 10 | +} | |
| 11 | + | |
| 12 | +const MONTH_NAMES: [&str; 12] = [ | |
| 13 | + "janvier", | |
| 14 | + "février", | |
| 15 | + "mars", | |
| 16 | + "avril", | |
| 17 | + "mai", | |
| 18 | + "juin", | |
| 19 | + "juillet", | |
| 20 | + "août", | |
| 21 | + "septembre", | |
| 22 | + "octobre", | |
| 23 | + "novembre", | |
| 24 | + "décembre", | |
| 25 | +]; | |
| 26 | + | |
| 27 | +/// Semaine ISO (lundi → dimanche), format query `2026-W12`. | |
| 28 | +#[derive(Clone, Copy, PartialEq, Eq, Debug)] | |
| 29 | +pub struct Week { | |
| 30 | + pub year: i32, | |
| 31 | + pub week: u32, | |
| 32 | +} | |
| 33 | + | |
| 34 | +impl Week { | |
| 35 | + pub fn parse(s: &str) -> Option<Self> { | |
| 36 | + let (y, w) = s.split_once("-W")?; | |
| 37 | + let week = Self { | |
| 38 | + year: y.parse().ok()?, | |
| 39 | + week: w.parse().ok()?, | |
| 40 | + }; | |
| 41 | + // Valide (la semaine 53 n'existe pas toutes les années). | |
| 42 | + NaiveDate::from_isoywd_opt(week.year, week.week, Weekday::Mon)?; | |
| 43 | + Some(week) | |
| 44 | + } | |
| 45 | + | |
| 46 | + pub fn of(date: NaiveDate) -> Self { | |
| 47 | + let iso = date.iso_week(); | |
| 48 | + Self { | |
| 49 | + year: iso.year(), | |
| 50 | + week: iso.week(), | |
| 51 | + } | |
| 52 | + } | |
| 53 | + | |
| 54 | + pub fn current() -> Self { | |
| 55 | + Self::of(paris_today()) | |
| 56 | + } | |
| 57 | + | |
| 58 | + /// [lundi, lundi suivant) | |
| 59 | + pub fn bounds(&self) -> (NaiveDate, NaiveDate) { | |
| 60 | + let start = NaiveDate::from_isoywd_opt(self.year, self.week, Weekday::Mon) | |
| 61 | + .expect("semaine validée au parse"); | |
| 62 | + (start, start + Duration::days(7)) | |
| 63 | + } | |
| 64 | + | |
| 65 | + pub fn prev(&self) -> Self { | |
| 66 | + Self::of(self.bounds().0 - Duration::days(7)) | |
| 67 | + } | |
| 68 | + | |
| 69 | + pub fn next(&self) -> Self { | |
| 70 | + Self::of(self.bounds().0 + Duration::days(7)) | |
| 71 | + } | |
| 72 | + | |
| 73 | + /// « Semaine 12 · 16/03 → 22/03 » | |
| 74 | + pub fn label(&self) -> String { | |
| 75 | + let (start, end) = self.bounds(); | |
| 76 | + let end = end - Duration::days(1); | |
| 77 | + format!( | |
| 78 | + "Semaine {} · {:02}/{:02} → {:02}/{:02}", | |
| 79 | + self.week, | |
| 80 | + start.day(), | |
| 81 | + start.month(), | |
| 82 | + end.day(), | |
| 83 | + end.month() | |
| 84 | + ) | |
| 85 | + } | |
| 86 | +} | |
| 87 | + | |
| 88 | +impl fmt::Display for Week { | |
| 89 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| 90 | + write!(f, "{}-W{:02}", self.year, self.week) | |
| 91 | + } | |
| 92 | +} | |
| 93 | + | |
| 94 | +/// Mois civil, format query `2026-03`. | |
| 95 | +#[derive(Clone, Copy, PartialEq, Eq, Debug)] | |
| 96 | +pub struct Month { | |
| 97 | + pub year: i32, | |
| 98 | + pub month: u32, | |
| 99 | +} | |
| 100 | + | |
| 101 | +impl Month { | |
| 102 | + pub fn parse(s: &str) -> Option<Self> { | |
| 103 | + let (y, m) = s.split_once('-')?; | |
| 104 | + let month = Self { | |
| 105 | + year: y.parse().ok()?, | |
| 106 | + month: m.parse().ok()?, | |
| 107 | + }; | |
| 108 | + (1..=12).contains(&month.month).then_some(month) | |
| 109 | + } | |
| 110 | + | |
| 111 | + pub fn of(date: NaiveDate) -> Self { | |
| 112 | + Self { | |
| 113 | + year: date.year(), | |
| 114 | + month: date.month(), | |
| 115 | + } | |
| 116 | + } | |
| 117 | + | |
| 118 | + pub fn current() -> Self { | |
| 119 | + Self::of(paris_today()) | |
| 120 | + } | |
| 121 | + | |
| 122 | + /// [1er du mois, 1er du mois suivant) | |
| 123 | + pub fn bounds(&self) -> (NaiveDate, NaiveDate) { | |
| 124 | + let start = | |
| 125 | + NaiveDate::from_ymd_opt(self.year, self.month, 1).expect("mois validé au parse"); | |
| 126 | + let end = if self.month == 12 { | |
| 127 | + NaiveDate::from_ymd_opt(self.year + 1, 1, 1) | |
| 128 | + } else { | |
| 129 | + NaiveDate::from_ymd_opt(self.year, self.month + 1, 1) | |
| 130 | + } | |
| 131 | + .expect("1er du mois toujours valide"); | |
| 132 | + (start, end) | |
| 133 | + } | |
| 134 | + | |
| 135 | + pub fn prev(&self) -> Self { | |
| 136 | + if self.month == 1 { | |
| 137 | + Self { | |
| 138 | + year: self.year - 1, | |
| 139 | + month: 12, | |
| 140 | + } | |
| 141 | + } else { | |
| 142 | + Self { | |
| 143 | + year: self.year, | |
| 144 | + month: self.month - 1, | |
| 145 | + } | |
| 146 | + } | |
| 147 | + } | |
| 148 | + | |
| 149 | + pub fn next(&self) -> Self { | |
| 150 | + if self.month == 12 { | |
| 151 | + Self { | |
| 152 | + year: self.year + 1, | |
| 153 | + month: 1, | |
| 154 | + } | |
| 155 | + } else { | |
| 156 | + Self { | |
| 157 | + year: self.year, | |
| 158 | + month: self.month + 1, | |
| 159 | + } | |
| 160 | + } | |
| 161 | + } | |
| 162 | + | |
| 163 | + /// « mars 2026 » | |
| 164 | + pub fn label(&self) -> String { | |
| 165 | + format!("{} {}", MONTH_NAMES[(self.month - 1) as usize], self.year) | |
| 166 | + } | |
| 167 | +} | |
| 168 | + | |
| 169 | +impl fmt::Display for Month { | |
| 170 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| 171 | + write!(f, "{}-{:02}", self.year, self.month) | |
| 172 | + } | |
| 173 | +} | |
| 174 | + | |
| 175 | +#[cfg(test)] | |
| 176 | +mod tests { | |
| 177 | + use super::*; | |
| 178 | + | |
| 179 | + fn d(y: i32, m: u32, d: u32) -> NaiveDate { | |
| 180 | + NaiveDate::from_ymd_opt(y, m, d).unwrap() | |
| 181 | + } | |
| 182 | + | |
| 183 | + #[test] | |
| 184 | + fn week_parse_and_display() { | |
| 185 | + let w = Week::parse("2026-W12").unwrap(); | |
| 186 | + assert_eq!((w.year, w.week), (2026, 12)); | |
| 187 | + assert_eq!(w.to_string(), "2026-W12"); | |
| 188 | + assert!(Week::parse("2026-W54").is_none()); | |
| 189 | + assert!(Week::parse("n'importe quoi").is_none()); | |
| 190 | + // 2026 n'a que 53 semaines ISO ? Non : 2026 en a 53 ; 2025 en a 52. | |
| 191 | + assert!(Week::parse("2025-W53").is_none()); | |
| 192 | + assert!(Week::parse("2026-W53").is_some()); | |
| 193 | + } | |
| 194 | + | |
| 195 | + #[test] | |
| 196 | + fn week_bounds_monday_based() { | |
| 197 | + let (start, end) = Week::parse("2026-W12").unwrap().bounds(); | |
| 198 | + assert_eq!(start, d(2026, 3, 16)); | |
| 199 | + assert_eq!(start.weekday(), Weekday::Mon); | |
| 200 | + assert_eq!(end, d(2026, 3, 23)); | |
| 201 | + } | |
| 202 | + | |
| 203 | + #[test] | |
| 204 | + fn week_year_rollover() { | |
| 205 | + // Le 1er janvier 2026 (jeudi) est en semaine ISO 2026-W01, | |
| 206 | + // qui commence le lundi 29 décembre 2025. | |
| 207 | + let w = Week::of(d(2026, 1, 1)); | |
| 208 | + assert_eq!((w.year, w.week), (2026, 1)); | |
| 209 | + assert_eq!(w.bounds().0, d(2025, 12, 29)); | |
| 210 | + // prev traverse l'année. | |
| 211 | + let p = w.prev(); | |
| 212 | + assert_eq!((p.year, p.week), (2025, 52)); | |
| 213 | + assert_eq!(p.next(), w); | |
| 214 | + } | |
| 215 | + | |
| 216 | + #[test] | |
| 217 | + fn month_parse_bounds_rollover() { | |
| 218 | + let m = Month::parse("2026-03").unwrap(); | |
| 219 | + assert_eq!(m.bounds(), (d(2026, 3, 1), d(2026, 4, 1))); | |
| 220 | + assert_eq!(m.to_string(), "2026-03"); | |
| 221 | + assert!(Month::parse("2026-13").is_none()); | |
| 222 | + | |
| 223 | + let dec = Month::parse("2025-12").unwrap(); | |
| 224 | + assert_eq!(dec.bounds().1, d(2026, 1, 1)); | |
| 225 | + assert_eq!(dec.next(), Month::parse("2026-01").unwrap()); | |
| 226 | + assert_eq!(Month::parse("2026-01").unwrap().prev(), dec); | |
| 227 | + } | |
| 228 | + | |
| 229 | + #[test] | |
| 230 | + fn labels() { | |
| 231 | + assert_eq!(Month::parse("2026-03").unwrap().label(), "mars 2026"); | |
| 232 | + assert!( | |
| 233 | + Week::parse("2026-W12") | |
| 234 | + .unwrap() | |
| 235 | + .label() | |
| 236 | + .starts_with("Semaine 12") | |
| 237 | + ); | |
| 238 | + } | |
| 239 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,239 @@ | |||
| 1 | +use std::fmt; | ||
| 2 | + | ||
| 3 | +use chrono::{Datelike, Duration, NaiveDate, Utc, Weekday}; | ||
| 4 | +use chrono_tz::Europe::Paris; | ||
| 5 | + | ||
| 6 | +/// Aujourd'hui en Europe/Paris — seule intrusion de la timezone dans l'appli | ||
| 7 | +/// (les entrées sont stockées en DATE). | ||
| 8 | +pub fn paris_today() -> NaiveDate { | ||
| 9 | + Utc::now().with_timezone(&Paris).date_naive() | ||
| 10 | +} | ||
| 11 | + | ||
| 12 | +const MONTH_NAMES: [&str; 12] = [ | ||
| 13 | + "janvier", | ||
| 14 | + "février", | ||
| 15 | + "mars", | ||
| 16 | + "avril", | ||
| 17 | + "mai", | ||
| 18 | + "juin", | ||
| 19 | + "juillet", | ||
| 20 | + "août", | ||
| 21 | + "septembre", | ||
| 22 | + "octobre", | ||
| 23 | + "novembre", | ||
| 24 | + "décembre", | ||
| 25 | +]; | ||
| 26 | + | ||
| 27 | +/// Semaine ISO (lundi → dimanche), format query `2026-W12`. | ||
| 28 | +#[derive(Clone, Copy, PartialEq, Eq, Debug)] | ||
| 29 | +pub struct Week { | ||
| 30 | + pub year: i32, | ||
| 31 | + pub week: u32, | ||
| 32 | +} | ||
| 33 | + | ||
| 34 | +impl Week { | ||
| 35 | + pub fn parse(s: &str) -> Option<Self> { | ||
| 36 | + let (y, w) = s.split_once("-W")?; | ||
| 37 | + let week = Self { | ||
| 38 | + year: y.parse().ok()?, | ||
| 39 | + week: w.parse().ok()?, | ||
| 40 | + }; | ||
| 41 | + // Valide (la semaine 53 n'existe pas toutes les années). | ||
| 42 | + NaiveDate::from_isoywd_opt(week.year, week.week, Weekday::Mon)?; | ||
| 43 | + Some(week) | ||
| 44 | + } | ||
| 45 | + | ||
| 46 | + pub fn of(date: NaiveDate) -> Self { | ||
| 47 | + let iso = date.iso_week(); | ||
| 48 | + Self { | ||
| 49 | + year: iso.year(), | ||
| 50 | + week: iso.week(), | ||
| 51 | + } | ||
| 52 | + } | ||
| 53 | + | ||
| 54 | + pub fn current() -> Self { | ||
| 55 | + Self::of(paris_today()) | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + /// [lundi, lundi suivant) | ||
| 59 | + pub fn bounds(&self) -> (NaiveDate, NaiveDate) { | ||
| 60 | + let start = NaiveDate::from_isoywd_opt(self.year, self.week, Weekday::Mon) | ||
| 61 | + .expect("semaine validée au parse"); | ||
| 62 | + (start, start + Duration::days(7)) | ||
| 63 | + } | ||
| 64 | + | ||
| 65 | + pub fn prev(&self) -> Self { | ||
| 66 | + Self::of(self.bounds().0 - Duration::days(7)) | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + pub fn next(&self) -> Self { | ||
| 70 | + Self::of(self.bounds().0 + Duration::days(7)) | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + /// « Semaine 12 · 16/03 → 22/03 » | ||
| 74 | + pub fn label(&self) -> String { | ||
| 75 | + let (start, end) = self.bounds(); | ||
| 76 | + let end = end - Duration::days(1); | ||
| 77 | + format!( | ||
| 78 | + "Semaine {} · {:02}/{:02} → {:02}/{:02}", | ||
| 79 | + self.week, | ||
| 80 | + start.day(), | ||
| 81 | + start.month(), | ||
| 82 | + end.day(), | ||
| 83 | + end.month() | ||
| 84 | + ) | ||
| 85 | + } | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +impl fmt::Display for Week { | ||
| 89 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| 90 | + write!(f, "{}-W{:02}", self.year, self.week) | ||
| 91 | + } | ||
| 92 | +} | ||
| 93 | + | ||
| 94 | +/// Mois civil, format query `2026-03`. | ||
| 95 | +#[derive(Clone, Copy, PartialEq, Eq, Debug)] | ||
| 96 | +pub struct Month { | ||
| 97 | + pub year: i32, | ||
| 98 | + pub month: u32, | ||
| 99 | +} | ||
| 100 | + | ||
| 101 | +impl Month { | ||
| 102 | + pub fn parse(s: &str) -> Option<Self> { | ||
| 103 | + let (y, m) = s.split_once('-')?; | ||
| 104 | + let month = Self { | ||
| 105 | + year: y.parse().ok()?, | ||
| 106 | + month: m.parse().ok()?, | ||
| 107 | + }; | ||
| 108 | + (1..=12).contains(&month.month).then_some(month) | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + pub fn of(date: NaiveDate) -> Self { | ||
| 112 | + Self { | ||
| 113 | + year: date.year(), | ||
| 114 | + month: date.month(), | ||
| 115 | + } | ||
| 116 | + } | ||
| 117 | + | ||
| 118 | + pub fn current() -> Self { | ||
| 119 | + Self::of(paris_today()) | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | + /// [1er du mois, 1er du mois suivant) | ||
| 123 | + pub fn bounds(&self) -> (NaiveDate, NaiveDate) { | ||
| 124 | + let start = | ||
| 125 | + NaiveDate::from_ymd_opt(self.year, self.month, 1).expect("mois validé au parse"); | ||
| 126 | + let end = if self.month == 12 { | ||
| 127 | + NaiveDate::from_ymd_opt(self.year + 1, 1, 1) | ||
| 128 | + } else { | ||
| 129 | + NaiveDate::from_ymd_opt(self.year, self.month + 1, 1) | ||
| 130 | + } | ||
| 131 | + .expect("1er du mois toujours valide"); | ||
| 132 | + (start, end) | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + pub fn prev(&self) -> Self { | ||
| 136 | + if self.month == 1 { | ||
| 137 | + Self { | ||
| 138 | + year: self.year - 1, | ||
| 139 | + month: 12, | ||
| 140 | + } | ||
| 141 | + } else { | ||
| 142 | + Self { | ||
| 143 | + year: self.year, | ||
| 144 | + month: self.month - 1, | ||
| 145 | + } | ||
| 146 | + } | ||
| 147 | + } | ||
| 148 | + | ||
| 149 | + pub fn next(&self) -> Self { | ||
| 150 | + if self.month == 12 { | ||
| 151 | + Self { | ||
| 152 | + year: self.year + 1, | ||
| 153 | + month: 1, | ||
| 154 | + } | ||
| 155 | + } else { | ||
| 156 | + Self { | ||
| 157 | + year: self.year, | ||
| 158 | + month: self.month + 1, | ||
| 159 | + } | ||
| 160 | + } | ||
| 161 | + } | ||
| 162 | + | ||
| 163 | + /// « mars 2026 » | ||
| 164 | + pub fn label(&self) -> String { | ||
| 165 | + format!("{} {}", MONTH_NAMES[(self.month - 1) as usize], self.year) | ||
| 166 | + } | ||
| 167 | +} | ||
| 168 | + | ||
| 169 | +impl fmt::Display for Month { | ||
| 170 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
| 171 | + write!(f, "{}-{:02}", self.year, self.month) | ||
| 172 | + } | ||
| 173 | +} | ||
| 174 | + | ||
| 175 | +#[cfg(test)] | ||
| 176 | +mod tests { | ||
| 177 | + use super::*; | ||
| 178 | + | ||
| 179 | + fn d(y: i32, m: u32, d: u32) -> NaiveDate { | ||
| 180 | + NaiveDate::from_ymd_opt(y, m, d).unwrap() | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + #[test] | ||
| 184 | + fn week_parse_and_display() { | ||
| 185 | + let w = Week::parse("2026-W12").unwrap(); | ||
| 186 | + assert_eq!((w.year, w.week), (2026, 12)); | ||
| 187 | + assert_eq!(w.to_string(), "2026-W12"); | ||
| 188 | + assert!(Week::parse("2026-W54").is_none()); | ||
| 189 | + assert!(Week::parse("n'importe quoi").is_none()); | ||
| 190 | + // 2026 n'a que 53 semaines ISO ? Non : 2026 en a 53 ; 2025 en a 52. | ||
| 191 | + assert!(Week::parse("2025-W53").is_none()); | ||
| 192 | + assert!(Week::parse("2026-W53").is_some()); | ||
| 193 | + } | ||
| 194 | + | ||
| 195 | + #[test] | ||
| 196 | + fn week_bounds_monday_based() { | ||
| 197 | + let (start, end) = Week::parse("2026-W12").unwrap().bounds(); | ||
| 198 | + assert_eq!(start, d(2026, 3, 16)); | ||
| 199 | + assert_eq!(start.weekday(), Weekday::Mon); | ||
| 200 | + assert_eq!(end, d(2026, 3, 23)); | ||
| 201 | + } | ||
| 202 | + | ||
| 203 | + #[test] | ||
| 204 | + fn week_year_rollover() { | ||
| 205 | + // Le 1er janvier 2026 (jeudi) est en semaine ISO 2026-W01, | ||
| 206 | + // qui commence le lundi 29 décembre 2025. | ||
| 207 | + let w = Week::of(d(2026, 1, 1)); | ||
| 208 | + assert_eq!((w.year, w.week), (2026, 1)); | ||
| 209 | + assert_eq!(w.bounds().0, d(2025, 12, 29)); | ||
| 210 | + // prev traverse l'année. | ||
| 211 | + let p = w.prev(); | ||
| 212 | + assert_eq!((p.year, p.week), (2025, 52)); | ||
| 213 | + assert_eq!(p.next(), w); | ||
| 214 | + } | ||
| 215 | + | ||
| 216 | + #[test] | ||
| 217 | + fn month_parse_bounds_rollover() { | ||
| 218 | + let m = Month::parse("2026-03").unwrap(); | ||
| 219 | + assert_eq!(m.bounds(), (d(2026, 3, 1), d(2026, 4, 1))); | ||
| 220 | + assert_eq!(m.to_string(), "2026-03"); | ||
| 221 | + assert!(Month::parse("2026-13").is_none()); | ||
| 222 | + | ||
| 223 | + let dec = Month::parse("2025-12").unwrap(); | ||
| 224 | + assert_eq!(dec.bounds().1, d(2026, 1, 1)); | ||
| 225 | + assert_eq!(dec.next(), Month::parse("2026-01").unwrap()); | ||
| 226 | + assert_eq!(Month::parse("2026-01").unwrap().prev(), dec); | ||
| 227 | + } | ||
| 228 | + | ||
| 229 | + #[test] | ||
| 230 | + fn labels() { | ||
| 231 | + assert_eq!(Month::parse("2026-03").unwrap().label(), "mars 2026"); | ||
| 232 | + assert!( | ||
| 233 | + Week::parse("2026-W12") | ||
| 234 | + .unwrap() | ||
| 235 | + .label() | ||
| 236 | + .starts_with("Semaine 12") | ||
| 237 | + ); | ||
| 238 | + } | ||
| 239 | +} | ||
added
src/render.rs +17 -0 | new file mode 100644 | ||
| @@ -0,0 +1,17 @@ | ||
| 1 | +use askama::Template; | |
| 2 | +use axum::response::{Html, IntoResponse, Response}; | |
| 3 | + | |
| 4 | +use crate::error::AppError; | |
| 5 | + | |
| 6 | +/// Wrapper local IntoResponse pour les templates askama | |
| 7 | +/// (askama_axum est abandonné depuis askama 0.13). | |
| 8 | +pub struct HtmlTemplate<T>(pub T); | |
| 9 | + | |
| 10 | +impl<T: Template> IntoResponse for HtmlTemplate<T> { | |
| 11 | + fn into_response(self) -> Response { | |
| 12 | + match self.0.render() { | |
| 13 | + Ok(html) => Html(html).into_response(), | |
| 14 | + Err(e) => AppError::Template(e).into_response(), | |
| 15 | + } | |
| 16 | + } | |
| 17 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | +use askama::Template; | ||
| 2 | +use axum::response::{Html, IntoResponse, Response}; | ||
| 3 | + | ||
| 4 | +use crate::error::AppError; | ||
| 5 | + | ||
| 6 | +/// Wrapper local IntoResponse pour les templates askama | ||
| 7 | +/// (askama_axum est abandonné depuis askama 0.13). | ||
| 8 | +pub struct HtmlTemplate<T>(pub T); | ||
| 9 | + | ||
| 10 | +impl<T: Template> IntoResponse for HtmlTemplate<T> { | ||
| 11 | + fn into_response(self) -> Response { | ||
| 12 | + match self.0.render() { | ||
| 13 | + Ok(html) => Html(html).into_response(), | ||
| 14 | + Err(e) => AppError::Template(e).into_response(), | ||
| 15 | + } | ||
| 16 | + } | ||
| 17 | +} | ||
added
src/routes/entries.rs +254 -0 | new file mode 100644 | ||
| @@ -0,0 +1,254 @@ | ||
| 1 | +use askama::Template; | |
| 2 | +use axum::Form; | |
| 3 | +use axum::extract::{Path, Query, State}; | |
| 4 | +use axum::response::Redirect; | |
| 5 | +use chrono::NaiveDate; | |
| 6 | +use serde::Deserialize; | |
| 7 | +use uuid::Uuid; | |
| 8 | + | |
| 9 | +use crate::auth::extractor::AuthUser; | |
| 10 | +use crate::charts::fmt_minutes; | |
| 11 | +use crate::db::entries::{self, EntryRow}; | |
| 12 | +use crate::db::tasks::{self, TaskRow}; | |
| 13 | +use crate::error::AppError; | |
| 14 | +use crate::period::paris_today; | |
| 15 | +use crate::render::HtmlTemplate; | |
| 16 | +use crate::state::AppState; | |
| 17 | + | |
| 18 | +pub struct Preset { | |
| 19 | + pub label: &'static str, | |
| 20 | + pub href: String, | |
| 21 | +} | |
| 22 | + | |
| 23 | +/// Option du select des tâches, avec sélection précalculée côté Rust. | |
| 24 | +pub struct TaskOption { | |
| 25 | + pub id: Uuid, | |
| 26 | + pub name: String, | |
| 27 | + pub archived: bool, | |
| 28 | + pub selected: bool, | |
| 29 | +} | |
| 30 | + | |
| 31 | +fn task_options(tasks: Vec<TaskRow>, selected: Option<Uuid>) -> Vec<TaskOption> { | |
| 32 | + tasks | |
| 33 | + .into_iter() | |
| 34 | + .map(|t| TaskOption { | |
| 35 | + selected: selected == Some(t.id), | |
| 36 | + id: t.id, | |
| 37 | + name: t.name, | |
| 38 | + archived: t.archived, | |
| 39 | + }) | |
| 40 | + .collect() | |
| 41 | +} | |
| 42 | + | |
| 43 | +#[derive(Template)] | |
| 44 | +#[template(path = "entry_form.html")] | |
| 45 | +pub struct EntryFormPage { | |
| 46 | + /// Titre + URL cible : partagé entre ajout et édition. | |
| 47 | + pub title: &'static str, | |
| 48 | + pub action: String, | |
| 49 | + pub csrf: String, | |
| 50 | + pub tasks: Vec<TaskOption>, | |
| 51 | + pub minutes: Option<i32>, | |
| 52 | + pub date: NaiveDate, | |
| 53 | + pub presets: Vec<Preset>, | |
| 54 | + pub saved: bool, | |
| 55 | + /// Some(id) en édition : affiche le bouton Supprimer. | |
| 56 | + pub delete_action: Option<String>, | |
| 57 | +} | |
| 58 | + | |
| 59 | +#[derive(Deserialize)] | |
| 60 | +pub struct NewQuery { | |
| 61 | + minutes: Option<i32>, | |
| 62 | + tache: Option<Uuid>, | |
| 63 | + date: Option<NaiveDate>, | |
| 64 | + ok: Option<u8>, | |
| 65 | +} | |
| 66 | + | |
| 67 | +fn presets(date: NaiveDate, tache: Option<Uuid>) -> Vec<Preset> { | |
| 68 | + [15, 30, 45, 60] | |
| 69 | + .into_iter() | |
| 70 | + .map(|m| { | |
| 71 | + let mut href = format!("/temps/nouveau?minutes={m}&date={date}"); | |
| 72 | + if let Some(t) = tache { | |
| 73 | + href.push_str(&format!("&tache={t}")); | |
| 74 | + } | |
| 75 | + Preset { | |
| 76 | + label: match m { | |
| 77 | + 15 => "15 min", | |
| 78 | + 30 => "30 min", | |
| 79 | + 45 => "45 min", | |
| 80 | + _ => "1 h", | |
| 81 | + }, | |
| 82 | + href, | |
| 83 | + } | |
| 84 | + }) | |
| 85 | + .collect() | |
| 86 | +} | |
| 87 | + | |
| 88 | +pub async fn entry_new( | |
| 89 | + State(state): State<AppState>, | |
| 90 | + auth: AuthUser, | |
| 91 | + Query(q): Query<NewQuery>, | |
| 92 | +) -> Result<HtmlTemplate<EntryFormPage>, AppError> { | |
| 93 | + let date = q.date.unwrap_or_else(paris_today); | |
| 94 | + Ok(HtmlTemplate(EntryFormPage { | |
| 95 | + title: "Ajouter un temps", | |
| 96 | + action: "/temps".to_string(), | |
| 97 | + csrf: auth.csrf_token(&state), | |
| 98 | + tasks: task_options(tasks::list_active(&state.pool).await?, q.tache), | |
| 99 | + minutes: q.minutes, | |
| 100 | + date, | |
| 101 | + presets: presets(date, q.tache), | |
| 102 | + saved: q.ok.is_some(), | |
| 103 | + delete_action: None, | |
| 104 | + })) | |
| 105 | +} | |
| 106 | + | |
| 107 | +#[derive(Deserialize)] | |
| 108 | +pub struct EntryForm { | |
| 109 | + minutes: i32, | |
| 110 | + tache: Uuid, | |
| 111 | + date: NaiveDate, | |
| 112 | + csrf: String, | |
| 113 | +} | |
| 114 | + | |
| 115 | +fn validate(form: &EntryForm) -> Result<(), AppError> { | |
| 116 | + if !(1..=1440).contains(&form.minutes) { | |
| 117 | + return Err(AppError::BadRequest( | |
| 118 | + "La durée doit être comprise entre 1 et 1440 minutes.".into(), | |
| 119 | + )); | |
| 120 | + } | |
| 121 | + Ok(()) | |
| 122 | +} | |
| 123 | + | |
| 124 | +pub async fn entry_create( | |
| 125 | + State(state): State<AppState>, | |
| 126 | + auth: AuthUser, | |
| 127 | + Form(form): Form<EntryForm>, | |
| 128 | +) -> Result<Redirect, AppError> { | |
| 129 | + if !auth.check_csrf(&state, &form.csrf) { | |
| 130 | + return Err(AppError::Csrf); | |
| 131 | + } | |
| 132 | + validate(&form)?; | |
| 133 | + entries::insert(&state.pool, auth.id, form.tache, form.minutes, form.date).await?; | |
| 134 | + Ok(Redirect::to("/temps/nouveau?ok=1")) | |
| 135 | +} | |
| 136 | + | |
| 137 | +#[derive(Template)] | |
| 138 | +#[template(path = "entries_list.html")] | |
| 139 | +pub struct EntriesListPage { | |
| 140 | + pub entries: Vec<EntryView>, | |
| 141 | + pub page: i64, | |
| 142 | + pub has_next: bool, | |
| 143 | +} | |
| 144 | + | |
| 145 | +pub struct EntryView { | |
| 146 | + pub id: Uuid, | |
| 147 | + pub task_name: String, | |
| 148 | + pub duration: String, | |
| 149 | + pub date: String, | |
| 150 | +} | |
| 151 | + | |
| 152 | +#[derive(Deserialize)] | |
| 153 | +pub struct PageQuery { | |
| 154 | + page: Option<i64>, | |
| 155 | +} | |
| 156 | + | |
| 157 | +pub async fn entries_list( | |
| 158 | + State(state): State<AppState>, | |
| 159 | + auth: AuthUser, | |
| 160 | + Query(q): Query<PageQuery>, | |
| 161 | +) -> Result<HtmlTemplate<EntriesListPage>, AppError> { | |
| 162 | + let page = q.page.unwrap_or(1).max(1); | |
| 163 | + let (rows, has_next) = entries::list_page(&state.pool, auth.id, page).await?; | |
| 164 | + let entries = rows.into_iter().map(entry_view).collect(); | |
| 165 | + Ok(HtmlTemplate(EntriesListPage { | |
| 166 | + entries, | |
| 167 | + page, | |
| 168 | + has_next, | |
| 169 | + })) | |
| 170 | +} | |
| 171 | + | |
| 172 | +fn entry_view(e: EntryRow) -> EntryView { | |
| 173 | + EntryView { | |
| 174 | + id: e.id, | |
| 175 | + task_name: e.task_name, | |
| 176 | + duration: fmt_minutes(e.minutes as i64), | |
| 177 | + date: e.entry_date.format("%d/%m/%Y").to_string(), | |
| 178 | + } | |
| 179 | +} | |
| 180 | + | |
| 181 | +pub async fn entry_edit( | |
| 182 | + State(state): State<AppState>, | |
| 183 | + auth: AuthUser, | |
| 184 | + Path(id): Path<Uuid>, | |
| 185 | +) -> Result<HtmlTemplate<EntryFormPage>, AppError> { | |
| 186 | + let entry = entries::get_owned(&state.pool, id, auth.id) | |
| 187 | + .await? | |
| 188 | + .ok_or(AppError::NotFound)?; | |
| 189 | + // En édition on liste aussi la tâche de l'entrée même si archivée depuis. | |
| 190 | + let mut task_list = tasks::list_active(&state.pool).await?; | |
| 191 | + if !task_list.iter().any(|t| t.id == entry.task_id) { | |
| 192 | + task_list.push(TaskRow { | |
| 193 | + id: entry.task_id, | |
| 194 | + name: entry.task_name.clone(), | |
| 195 | + archived: true, | |
| 196 | + }); | |
| 197 | + } | |
| 198 | + Ok(HtmlTemplate(EntryFormPage { | |
| 199 | + title: "Modifier un temps", | |
| 200 | + action: format!("/temps/{id}"), | |
| 201 | + csrf: auth.csrf_token(&state), | |
| 202 | + tasks: task_options(task_list, Some(entry.task_id)), | |
| 203 | + minutes: Some(entry.minutes), | |
| 204 | + date: entry.entry_date, | |
| 205 | + presets: Vec::new(), | |
| 206 | + saved: false, | |
| 207 | + delete_action: Some(format!("/temps/{id}/supprimer")), | |
| 208 | + })) | |
| 209 | +} | |
| 210 | + | |
| 211 | +pub async fn entry_update( | |
| 212 | + State(state): State<AppState>, | |
| 213 | + auth: AuthUser, | |
| 214 | + Path(id): Path<Uuid>, | |
| 215 | + Form(form): Form<EntryForm>, | |
| 216 | +) -> Result<Redirect, AppError> { | |
| 217 | + if !auth.check_csrf(&state, &form.csrf) { | |
| 218 | + return Err(AppError::Csrf); | |
| 219 | + } | |
| 220 | + validate(&form)?; | |
| 221 | + let n = entries::update_owned( | |
| 222 | + &state.pool, | |
| 223 | + id, | |
| 224 | + auth.id, | |
| 225 | + form.tache, | |
| 226 | + form.minutes, | |
| 227 | + form.date, | |
| 228 | + ) | |
| 229 | + .await?; | |
| 230 | + if n == 0 { | |
| 231 | + return Err(AppError::NotFound); | |
| 232 | + } | |
| 233 | + Ok(Redirect::to("/temps")) | |
| 234 | +} | |
| 235 | + | |
| 236 | +#[derive(Deserialize)] | |
| 237 | +pub struct CsrfOnly { | |
| 238 | + csrf: String, | |
| 239 | +} | |
| 240 | + | |
| 241 | +pub async fn entry_delete( | |
| 242 | + State(state): State<AppState>, | |
| 243 | + auth: AuthUser, | |
| 244 | + Path(id): Path<Uuid>, | |
| 245 | + Form(form): Form<CsrfOnly>, | |
| 246 | +) -> Result<Redirect, AppError> { | |
| 247 | + if !auth.check_csrf(&state, &form.csrf) { | |
| 248 | + return Err(AppError::Csrf); | |
| 249 | + } | |
| 250 | + if entries::delete_owned(&state.pool, id, auth.id).await? == 0 { | |
| 251 | + return Err(AppError::NotFound); | |
| 252 | + } | |
| 253 | + Ok(Redirect::to("/temps")) | |
| 254 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,254 @@ | |||
| 1 | +use askama::Template; | ||
| 2 | +use axum::Form; | ||
| 3 | +use axum::extract::{Path, Query, State}; | ||
| 4 | +use axum::response::Redirect; | ||
| 5 | +use chrono::NaiveDate; | ||
| 6 | +use serde::Deserialize; | ||
| 7 | +use uuid::Uuid; | ||
| 8 | + | ||
| 9 | +use crate::auth::extractor::AuthUser; | ||
| 10 | +use crate::charts::fmt_minutes; | ||
| 11 | +use crate::db::entries::{self, EntryRow}; | ||
| 12 | +use crate::db::tasks::{self, TaskRow}; | ||
| 13 | +use crate::error::AppError; | ||
| 14 | +use crate::period::paris_today; | ||
| 15 | +use crate::render::HtmlTemplate; | ||
| 16 | +use crate::state::AppState; | ||
| 17 | + | ||
| 18 | +pub struct Preset { | ||
| 19 | + pub label: &'static str, | ||
| 20 | + pub href: String, | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +/// Option du select des tâches, avec sélection précalculée côté Rust. | ||
| 24 | +pub struct TaskOption { | ||
| 25 | + pub id: Uuid, | ||
| 26 | + pub name: String, | ||
| 27 | + pub archived: bool, | ||
| 28 | + pub selected: bool, | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +fn task_options(tasks: Vec<TaskRow>, selected: Option<Uuid>) -> Vec<TaskOption> { | ||
| 32 | + tasks | ||
| 33 | + .into_iter() | ||
| 34 | + .map(|t| TaskOption { | ||
| 35 | + selected: selected == Some(t.id), | ||
| 36 | + id: t.id, | ||
| 37 | + name: t.name, | ||
| 38 | + archived: t.archived, | ||
| 39 | + }) | ||
| 40 | + .collect() | ||
| 41 | +} | ||
| 42 | + | ||
| 43 | +#[derive(Template)] | ||
| 44 | +#[template(path = "entry_form.html")] | ||
| 45 | +pub struct EntryFormPage { | ||
| 46 | + /// Titre + URL cible : partagé entre ajout et édition. | ||
| 47 | + pub title: &'static str, | ||
| 48 | + pub action: String, | ||
| 49 | + pub csrf: String, | ||
| 50 | + pub tasks: Vec<TaskOption>, | ||
| 51 | + pub minutes: Option<i32>, | ||
| 52 | + pub date: NaiveDate, | ||
| 53 | + pub presets: Vec<Preset>, | ||
| 54 | + pub saved: bool, | ||
| 55 | + /// Some(id) en édition : affiche le bouton Supprimer. | ||
| 56 | + pub delete_action: Option<String>, | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +#[derive(Deserialize)] | ||
| 60 | +pub struct NewQuery { | ||
| 61 | + minutes: Option<i32>, | ||
| 62 | + tache: Option<Uuid>, | ||
| 63 | + date: Option<NaiveDate>, | ||
| 64 | + ok: Option<u8>, | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +fn presets(date: NaiveDate, tache: Option<Uuid>) -> Vec<Preset> { | ||
| 68 | + [15, 30, 45, 60] | ||
| 69 | + .into_iter() | ||
| 70 | + .map(|m| { | ||
| 71 | + let mut href = format!("/temps/nouveau?minutes={m}&date={date}"); | ||
| 72 | + if let Some(t) = tache { | ||
| 73 | + href.push_str(&format!("&tache={t}")); | ||
| 74 | + } | ||
| 75 | + Preset { | ||
| 76 | + label: match m { | ||
| 77 | + 15 => "15 min", | ||
| 78 | + 30 => "30 min", | ||
| 79 | + 45 => "45 min", | ||
| 80 | + _ => "1 h", | ||
| 81 | + }, | ||
| 82 | + href, | ||
| 83 | + } | ||
| 84 | + }) | ||
| 85 | + .collect() | ||
| 86 | +} | ||
| 87 | + | ||
| 88 | +pub async fn entry_new( | ||
| 89 | + State(state): State<AppState>, | ||
| 90 | + auth: AuthUser, | ||
| 91 | + Query(q): Query<NewQuery>, | ||
| 92 | +) -> Result<HtmlTemplate<EntryFormPage>, AppError> { | ||
| 93 | + let date = q.date.unwrap_or_else(paris_today); | ||
| 94 | + Ok(HtmlTemplate(EntryFormPage { | ||
| 95 | + title: "Ajouter un temps", | ||
| 96 | + action: "/temps".to_string(), | ||
| 97 | + csrf: auth.csrf_token(&state), | ||
| 98 | + tasks: task_options(tasks::list_active(&state.pool).await?, q.tache), | ||
| 99 | + minutes: q.minutes, | ||
| 100 | + date, | ||
| 101 | + presets: presets(date, q.tache), | ||
| 102 | + saved: q.ok.is_some(), | ||
| 103 | + delete_action: None, | ||
| 104 | + })) | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +#[derive(Deserialize)] | ||
| 108 | +pub struct EntryForm { | ||
| 109 | + minutes: i32, | ||
| 110 | + tache: Uuid, | ||
| 111 | + date: NaiveDate, | ||
| 112 | + csrf: String, | ||
| 113 | +} | ||
| 114 | + | ||
| 115 | +fn validate(form: &EntryForm) -> Result<(), AppError> { | ||
| 116 | + if !(1..=1440).contains(&form.minutes) { | ||
| 117 | + return Err(AppError::BadRequest( | ||
| 118 | + "La durée doit être comprise entre 1 et 1440 minutes.".into(), | ||
| 119 | + )); | ||
| 120 | + } | ||
| 121 | + Ok(()) | ||
| 122 | +} | ||
| 123 | + | ||
| 124 | +pub async fn entry_create( | ||
| 125 | + State(state): State<AppState>, | ||
| 126 | + auth: AuthUser, | ||
| 127 | + Form(form): Form<EntryForm>, | ||
| 128 | +) -> Result<Redirect, AppError> { | ||
| 129 | + if !auth.check_csrf(&state, &form.csrf) { | ||
| 130 | + return Err(AppError::Csrf); | ||
| 131 | + } | ||
| 132 | + validate(&form)?; | ||
| 133 | + entries::insert(&state.pool, auth.id, form.tache, form.minutes, form.date).await?; | ||
| 134 | + Ok(Redirect::to("/temps/nouveau?ok=1")) | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +#[derive(Template)] | ||
| 138 | +#[template(path = "entries_list.html")] | ||
| 139 | +pub struct EntriesListPage { | ||
| 140 | + pub entries: Vec<EntryView>, | ||
| 141 | + pub page: i64, | ||
| 142 | + pub has_next: bool, | ||
| 143 | +} | ||
| 144 | + | ||
| 145 | +pub struct EntryView { | ||
| 146 | + pub id: Uuid, | ||
| 147 | + pub task_name: String, | ||
| 148 | + pub duration: String, | ||
| 149 | + pub date: String, | ||
| 150 | +} | ||
| 151 | + | ||
| 152 | +#[derive(Deserialize)] | ||
| 153 | +pub struct PageQuery { | ||
| 154 | + page: Option<i64>, | ||
| 155 | +} | ||
| 156 | + | ||
| 157 | +pub async fn entries_list( | ||
| 158 | + State(state): State<AppState>, | ||
| 159 | + auth: AuthUser, | ||
| 160 | + Query(q): Query<PageQuery>, | ||
| 161 | +) -> Result<HtmlTemplate<EntriesListPage>, AppError> { | ||
| 162 | + let page = q.page.unwrap_or(1).max(1); | ||
| 163 | + let (rows, has_next) = entries::list_page(&state.pool, auth.id, page).await?; | ||
| 164 | + let entries = rows.into_iter().map(entry_view).collect(); | ||
| 165 | + Ok(HtmlTemplate(EntriesListPage { | ||
| 166 | + entries, | ||
| 167 | + page, | ||
| 168 | + has_next, | ||
| 169 | + })) | ||
| 170 | +} | ||
| 171 | + | ||
| 172 | +fn entry_view(e: EntryRow) -> EntryView { | ||
| 173 | + EntryView { | ||
| 174 | + id: e.id, | ||
| 175 | + task_name: e.task_name, | ||
| 176 | + duration: fmt_minutes(e.minutes as i64), | ||
| 177 | + date: e.entry_date.format("%d/%m/%Y").to_string(), | ||
| 178 | + } | ||
| 179 | +} | ||
| 180 | + | ||
| 181 | +pub async fn entry_edit( | ||
| 182 | + State(state): State<AppState>, | ||
| 183 | + auth: AuthUser, | ||
| 184 | + Path(id): Path<Uuid>, | ||
| 185 | +) -> Result<HtmlTemplate<EntryFormPage>, AppError> { | ||
| 186 | + let entry = entries::get_owned(&state.pool, id, auth.id) | ||
| 187 | + .await? | ||
| 188 | + .ok_or(AppError::NotFound)?; | ||
| 189 | + // En édition on liste aussi la tâche de l'entrée même si archivée depuis. | ||
| 190 | + let mut task_list = tasks::list_active(&state.pool).await?; | ||
| 191 | + if !task_list.iter().any(|t| t.id == entry.task_id) { | ||
| 192 | + task_list.push(TaskRow { | ||
| 193 | + id: entry.task_id, | ||
| 194 | + name: entry.task_name.clone(), | ||
| 195 | + archived: true, | ||
| 196 | + }); | ||
| 197 | + } | ||
| 198 | + Ok(HtmlTemplate(EntryFormPage { | ||
| 199 | + title: "Modifier un temps", | ||
| 200 | + action: format!("/temps/{id}"), | ||
| 201 | + csrf: auth.csrf_token(&state), | ||
| 202 | + tasks: task_options(task_list, Some(entry.task_id)), | ||
| 203 | + minutes: Some(entry.minutes), | ||
| 204 | + date: entry.entry_date, | ||
| 205 | + presets: Vec::new(), | ||
| 206 | + saved: false, | ||
| 207 | + delete_action: Some(format!("/temps/{id}/supprimer")), | ||
| 208 | + })) | ||
| 209 | +} | ||
| 210 | + | ||
| 211 | +pub async fn entry_update( | ||
| 212 | + State(state): State<AppState>, | ||
| 213 | + auth: AuthUser, | ||
| 214 | + Path(id): Path<Uuid>, | ||
| 215 | + Form(form): Form<EntryForm>, | ||
| 216 | +) -> Result<Redirect, AppError> { | ||
| 217 | + if !auth.check_csrf(&state, &form.csrf) { | ||
| 218 | + return Err(AppError::Csrf); | ||
| 219 | + } | ||
| 220 | + validate(&form)?; | ||
| 221 | + let n = entries::update_owned( | ||
| 222 | + &state.pool, | ||
| 223 | + id, | ||
| 224 | + auth.id, | ||
| 225 | + form.tache, | ||
| 226 | + form.minutes, | ||
| 227 | + form.date, | ||
| 228 | + ) | ||
| 229 | + .await?; | ||
| 230 | + if n == 0 { | ||
| 231 | + return Err(AppError::NotFound); | ||
| 232 | + } | ||
| 233 | + Ok(Redirect::to("/temps")) | ||
| 234 | +} | ||
| 235 | + | ||
| 236 | +#[derive(Deserialize)] | ||
| 237 | +pub struct CsrfOnly { | ||
| 238 | + csrf: String, | ||
| 239 | +} | ||
| 240 | + | ||
| 241 | +pub async fn entry_delete( | ||
| 242 | + State(state): State<AppState>, | ||
| 243 | + auth: AuthUser, | ||
| 244 | + Path(id): Path<Uuid>, | ||
| 245 | + Form(form): Form<CsrfOnly>, | ||
| 246 | +) -> Result<Redirect, AppError> { | ||
| 247 | + if !auth.check_csrf(&state, &form.csrf) { | ||
| 248 | + return Err(AppError::Csrf); | ||
| 249 | + } | ||
| 250 | + if entries::delete_owned(&state.pool, id, auth.id).await? == 0 { | ||
| 251 | + return Err(AppError::NotFound); | ||
| 252 | + } | ||
| 253 | + Ok(Redirect::to("/temps")) | ||
| 254 | +} | ||
added
src/routes/home.rs +12 -0 | new file mode 100644 | ||
| @@ -0,0 +1,12 @@ | ||
| 1 | +use askama::Template; | |
| 2 | + | |
| 3 | +use crate::auth::extractor::AuthUser; | |
| 4 | +use crate::render::HtmlTemplate; | |
| 5 | + | |
| 6 | +#[derive(Template)] | |
| 7 | +#[template(path = "home.html")] | |
| 8 | +pub struct HomePage; | |
| 9 | + | |
| 10 | +pub async fn home(_auth: AuthUser) -> HtmlTemplate<HomePage> { | |
| 11 | + HtmlTemplate(HomePage) | |
| 12 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | +use askama::Template; | ||
| 2 | + | ||
| 3 | +use crate::auth::extractor::AuthUser; | ||
| 4 | +use crate::render::HtmlTemplate; | ||
| 5 | + | ||
| 6 | +#[derive(Template)] | ||
| 7 | +#[template(path = "home.html")] | ||
| 8 | +pub struct HomePage; | ||
| 9 | + | ||
| 10 | +pub async fn home(_auth: AuthUser) -> HtmlTemplate<HomePage> { | ||
| 11 | + HtmlTemplate(HomePage) | ||
| 12 | +} | ||
added
src/routes/login.rs +92 -0 | new file mode 100644 | ||
| @@ -0,0 +1,92 @@ | ||
| 1 | +use askama::Template; | |
| 2 | +use axum::extract::{Query, State}; | |
| 3 | +use axum::response::{IntoResponse, Redirect, Response}; | |
| 4 | +use axum::Form; | |
| 5 | +use axum_extra::extract::CookieJar; | |
| 6 | +use axum_extra::extract::cookie::{Cookie, SameSite}; | |
| 7 | +use serde::Deserialize; | |
| 8 | + | |
| 9 | +use crate::auth::extractor::AuthUser; | |
| 10 | +use crate::auth::{SESSION_COOKIE, SESSION_SECONDS, revocation, token}; | |
| 11 | +use crate::db::users; | |
| 12 | +use crate::error::AppError; | |
| 13 | +use crate::render::HtmlTemplate; | |
| 14 | +use crate::state::AppState; | |
| 15 | + | |
| 16 | +#[derive(Template)] | |
| 17 | +#[template(path = "login.html")] | |
| 18 | +pub struct LoginPage { | |
| 19 | + pub error: bool, | |
| 20 | +} | |
| 21 | + | |
| 22 | +#[derive(Deserialize)] | |
| 23 | +pub struct LoginQuery { | |
| 24 | + #[serde(default)] | |
| 25 | + err: Option<u8>, | |
| 26 | +} | |
| 27 | + | |
| 28 | +pub async fn login_page(user: Option<AuthUser>, Query(q): Query<LoginQuery>) -> Response { | |
| 29 | + if user.is_some() { | |
| 30 | + return Redirect::to("/").into_response(); | |
| 31 | + } | |
| 32 | + HtmlTemplate(LoginPage { | |
| 33 | + error: q.err.is_some(), | |
| 34 | + }) | |
| 35 | + .into_response() | |
| 36 | +} | |
| 37 | + | |
| 38 | +#[derive(Deserialize)] | |
| 39 | +pub struct LoginForm { | |
| 40 | + username: String, | |
| 41 | + password: String, | |
| 42 | +} | |
| 43 | + | |
| 44 | +pub async fn login_submit( | |
| 45 | + State(state): State<AppState>, | |
| 46 | + jar: CookieJar, | |
| 47 | + Form(form): Form<LoginForm>, | |
| 48 | +) -> Result<Response, AppError> { | |
| 49 | + let creds = users::find_credentials(&state.pool, form.username.trim()).await?; | |
| 50 | + let Some(creds) = creds else { | |
| 51 | + // Vérification factice pour ne pas révéler l'existence du compte via le timing. | |
| 52 | + let _ = crate::auth::password::verify(&form.password, &DUMMY_HASH); | |
| 53 | + return Ok(Redirect::to("/login?err=1").into_response()); | |
| 54 | + }; | |
| 55 | + if !crate::auth::password::verify(&form.password, &creds.password_hash) { | |
| 56 | + return Ok(Redirect::to("/login?err=1").into_response()); | |
| 57 | + } | |
| 58 | + | |
| 59 | + let token = token::mint(&state.root, creds.id).map_err(AppError::Internal)?; | |
| 60 | + let cookie = Cookie::build((SESSION_COOKIE, token)) | |
| 61 | + .path("/") | |
| 62 | + .http_only(true) | |
| 63 | + .secure(true) | |
| 64 | + .same_site(SameSite::Lax) | |
| 65 | + .max_age(time::Duration::seconds(SESSION_SECONDS)); | |
| 66 | + Ok((jar.add(cookie), Redirect::to("/")).into_response()) | |
| 67 | +} | |
| 68 | + | |
| 69 | +/// Hash argon2id factice — uniquement pour égaliser le temps de réponse. | |
| 70 | +static DUMMY_HASH: std::sync::LazyLock<String> = | |
| 71 | + std::sync::LazyLock::new(|| crate::auth::password::hash("dummy").expect("hash factice")); | |
| 72 | + | |
| 73 | +#[derive(Deserialize)] | |
| 74 | +pub struct LogoutForm { | |
| 75 | + csrf: String, | |
| 76 | +} | |
| 77 | + | |
| 78 | +pub async fn logout( | |
| 79 | + State(state): State<AppState>, | |
| 80 | + auth: AuthUser, | |
| 81 | + jar: CookieJar, | |
| 82 | + Form(form): Form<LogoutForm>, | |
| 83 | +) -> Result<Response, AppError> { | |
| 84 | + if !auth.check_csrf(&state, &form.csrf) { | |
| 85 | + return Err(AppError::Csrf); | |
| 86 | + } | |
| 87 | + revocation::revoke(&state.pool, &state.revocations, &auth.rev_ids) | |
| 88 | + .await | |
| 89 | + .map_err(AppError::Internal)?; | |
| 90 | + let jar = jar.remove(Cookie::build((SESSION_COOKIE, "")).path("/")); | |
| 91 | + Ok((jar, Redirect::to("/login")).into_response()) | |
| 92 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,92 @@ | |||
| 1 | +use askama::Template; | ||
| 2 | +use axum::extract::{Query, State}; | ||
| 3 | +use axum::response::{IntoResponse, Redirect, Response}; | ||
| 4 | +use axum::Form; | ||
| 5 | +use axum_extra::extract::CookieJar; | ||
| 6 | +use axum_extra::extract::cookie::{Cookie, SameSite}; | ||
| 7 | +use serde::Deserialize; | ||
| 8 | + | ||
| 9 | +use crate::auth::extractor::AuthUser; | ||
| 10 | +use crate::auth::{SESSION_COOKIE, SESSION_SECONDS, revocation, token}; | ||
| 11 | +use crate::db::users; | ||
| 12 | +use crate::error::AppError; | ||
| 13 | +use crate::render::HtmlTemplate; | ||
| 14 | +use crate::state::AppState; | ||
| 15 | + | ||
| 16 | +#[derive(Template)] | ||
| 17 | +#[template(path = "login.html")] | ||
| 18 | +pub struct LoginPage { | ||
| 19 | + pub error: bool, | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +#[derive(Deserialize)] | ||
| 23 | +pub struct LoginQuery { | ||
| 24 | + #[serde(default)] | ||
| 25 | + err: Option<u8>, | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +pub async fn login_page(user: Option<AuthUser>, Query(q): Query<LoginQuery>) -> Response { | ||
| 29 | + if user.is_some() { | ||
| 30 | + return Redirect::to("/").into_response(); | ||
| 31 | + } | ||
| 32 | + HtmlTemplate(LoginPage { | ||
| 33 | + error: q.err.is_some(), | ||
| 34 | + }) | ||
| 35 | + .into_response() | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +#[derive(Deserialize)] | ||
| 39 | +pub struct LoginForm { | ||
| 40 | + username: String, | ||
| 41 | + password: String, | ||
| 42 | +} | ||
| 43 | + | ||
| 44 | +pub async fn login_submit( | ||
| 45 | + State(state): State<AppState>, | ||
| 46 | + jar: CookieJar, | ||
| 47 | + Form(form): Form<LoginForm>, | ||
| 48 | +) -> Result<Response, AppError> { | ||
| 49 | + let creds = users::find_credentials(&state.pool, form.username.trim()).await?; | ||
| 50 | + let Some(creds) = creds else { | ||
| 51 | + // Vérification factice pour ne pas révéler l'existence du compte via le timing. | ||
| 52 | + let _ = crate::auth::password::verify(&form.password, &DUMMY_HASH); | ||
| 53 | + return Ok(Redirect::to("/login?err=1").into_response()); | ||
| 54 | + }; | ||
| 55 | + if !crate::auth::password::verify(&form.password, &creds.password_hash) { | ||
| 56 | + return Ok(Redirect::to("/login?err=1").into_response()); | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + let token = token::mint(&state.root, creds.id).map_err(AppError::Internal)?; | ||
| 60 | + let cookie = Cookie::build((SESSION_COOKIE, token)) | ||
| 61 | + .path("/") | ||
| 62 | + .http_only(true) | ||
| 63 | + .secure(true) | ||
| 64 | + .same_site(SameSite::Lax) | ||
| 65 | + .max_age(time::Duration::seconds(SESSION_SECONDS)); | ||
| 66 | + Ok((jar.add(cookie), Redirect::to("/")).into_response()) | ||
| 67 | +} | ||
| 68 | + | ||
| 69 | +/// Hash argon2id factice — uniquement pour égaliser le temps de réponse. | ||
| 70 | +static DUMMY_HASH: std::sync::LazyLock<String> = | ||
| 71 | + std::sync::LazyLock::new(|| crate::auth::password::hash("dummy").expect("hash factice")); | ||
| 72 | + | ||
| 73 | +#[derive(Deserialize)] | ||
| 74 | +pub struct LogoutForm { | ||
| 75 | + csrf: String, | ||
| 76 | +} | ||
| 77 | + | ||
| 78 | +pub async fn logout( | ||
| 79 | + State(state): State<AppState>, | ||
| 80 | + auth: AuthUser, | ||
| 81 | + jar: CookieJar, | ||
| 82 | + Form(form): Form<LogoutForm>, | ||
| 83 | +) -> Result<Response, AppError> { | ||
| 84 | + if !auth.check_csrf(&state, &form.csrf) { | ||
| 85 | + return Err(AppError::Csrf); | ||
| 86 | + } | ||
| 87 | + revocation::revoke(&state.pool, &state.revocations, &auth.rev_ids) | ||
| 88 | + .await | ||
| 89 | + .map_err(AppError::Internal)?; | ||
| 90 | + let jar = jar.remove(Cookie::build((SESSION_COOKIE, "")).path("/")); | ||
| 91 | + Ok((jar, Redirect::to("/login")).into_response()) | ||
| 92 | +} | ||
added
src/routes/mod.rs +47 -0 | new file mode 100644 | ||
| @@ -0,0 +1,47 @@ | ||
| 1 | +mod entries; | |
| 2 | +mod home; | |
| 3 | +mod login; | |
| 4 | +mod settings; | |
| 5 | +mod statics; | |
| 6 | +mod stats; | |
| 7 | + | |
| 8 | +use axum::Router; | |
| 9 | +use axum::routing::{get, post}; | |
| 10 | + | |
| 11 | +use crate::state::AppState; | |
| 12 | + | |
| 13 | +pub fn router(state: AppState) -> Router { | |
| 14 | + Router::new() | |
| 15 | + .route("/", get(home::home)) | |
| 16 | + .route("/login", get(login::login_page).post(login::login_submit)) | |
| 17 | + .route("/logout", post(login::logout)) | |
| 18 | + .route( | |
| 19 | + "/temps", | |
| 20 | + get(entries::entries_list).post(entries::entry_create), | |
| 21 | + ) | |
| 22 | + .route("/temps/nouveau", get(entries::entry_new)) | |
| 23 | + .route("/temps/{id}/modifier", get(entries::entry_edit)) | |
| 24 | + .route("/temps/{id}", post(entries::entry_update)) | |
| 25 | + .route("/temps/{id}/supprimer", post(entries::entry_delete)) | |
| 26 | + .route("/stats", get(stats::stats_page)) | |
| 27 | + .route("/parametres", get(settings::settings_page)) | |
| 28 | + .route("/parametres/taches", post(settings::task_create)) | |
| 29 | + .route("/parametres/taches/{id}", post(settings::task_rename)) | |
| 30 | + .route( | |
| 31 | + "/parametres/taches/{id}/archiver", | |
| 32 | + post(settings::task_toggle_archive), | |
| 33 | + ) | |
| 34 | + .route( | |
| 35 | + "/parametres/mot-de-passe", | |
| 36 | + post(settings::password_change), | |
| 37 | + ) | |
| 38 | + .route("/static/style.css", get(statics::css)) | |
| 39 | + .route("/manifest.json", get(statics::manifest)) | |
| 40 | + .route("/icon-512.png", get(statics::icon)) | |
| 41 | + .fallback(fallback_404) | |
| 42 | + .with_state(state) | |
| 43 | +} | |
| 44 | + | |
| 45 | +async fn fallback_404() -> crate::error::AppError { | |
| 46 | + crate::error::AppError::NotFound | |
| 47 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | +mod entries; | ||
| 2 | +mod home; | ||
| 3 | +mod login; | ||
| 4 | +mod settings; | ||
| 5 | +mod statics; | ||
| 6 | +mod stats; | ||
| 7 | + | ||
| 8 | +use axum::Router; | ||
| 9 | +use axum::routing::{get, post}; | ||
| 10 | + | ||
| 11 | +use crate::state::AppState; | ||
| 12 | + | ||
| 13 | +pub fn router(state: AppState) -> Router { | ||
| 14 | + Router::new() | ||
| 15 | + .route("/", get(home::home)) | ||
| 16 | + .route("/login", get(login::login_page).post(login::login_submit)) | ||
| 17 | + .route("/logout", post(login::logout)) | ||
| 18 | + .route( | ||
| 19 | + "/temps", | ||
| 20 | + get(entries::entries_list).post(entries::entry_create), | ||
| 21 | + ) | ||
| 22 | + .route("/temps/nouveau", get(entries::entry_new)) | ||
| 23 | + .route("/temps/{id}/modifier", get(entries::entry_edit)) | ||
| 24 | + .route("/temps/{id}", post(entries::entry_update)) | ||
| 25 | + .route("/temps/{id}/supprimer", post(entries::entry_delete)) | ||
| 26 | + .route("/stats", get(stats::stats_page)) | ||
| 27 | + .route("/parametres", get(settings::settings_page)) | ||
| 28 | + .route("/parametres/taches", post(settings::task_create)) | ||
| 29 | + .route("/parametres/taches/{id}", post(settings::task_rename)) | ||
| 30 | + .route( | ||
| 31 | + "/parametres/taches/{id}/archiver", | ||
| 32 | + post(settings::task_toggle_archive), | ||
| 33 | + ) | ||
| 34 | + .route( | ||
| 35 | + "/parametres/mot-de-passe", | ||
| 36 | + post(settings::password_change), | ||
| 37 | + ) | ||
| 38 | + .route("/static/style.css", get(statics::css)) | ||
| 39 | + .route("/manifest.json", get(statics::manifest)) | ||
| 40 | + .route("/icon-512.png", get(statics::icon)) | ||
| 41 | + .fallback(fallback_404) | ||
| 42 | + .with_state(state) | ||
| 43 | +} | ||
| 44 | + | ||
| 45 | +async fn fallback_404() -> crate::error::AppError { | ||
| 46 | + crate::error::AppError::NotFound | ||
| 47 | +} | ||
added
src/routes/settings.rs +158 -0 | new file mode 100644 | ||
| @@ -0,0 +1,158 @@ | ||
| 1 | +use askama::Template; | |
| 2 | +use axum::Form; | |
| 3 | +use axum::extract::{Path, Query, State}; | |
| 4 | +use axum::response::Redirect; | |
| 5 | +use serde::Deserialize; | |
| 6 | +use uuid::Uuid; | |
| 7 | + | |
| 8 | +use crate::auth::extractor::AuthUser; | |
| 9 | +use crate::auth::password; | |
| 10 | +use crate::db::{tasks, users}; | |
| 11 | +use crate::db::tasks::TaskRow; | |
| 12 | +use crate::error::AppError; | |
| 13 | +use crate::render::HtmlTemplate; | |
| 14 | +use crate::state::AppState; | |
| 15 | + | |
| 16 | +#[derive(Template)] | |
| 17 | +#[template(path = "settings.html")] | |
| 18 | +pub struct SettingsPage { | |
| 19 | + pub tasks: Vec<TaskRow>, | |
| 20 | + pub csrf: String, | |
| 21 | + pub notice: Option<&'static str>, | |
| 22 | + pub error: Option<&'static str>, | |
| 23 | +} | |
| 24 | + | |
| 25 | +#[derive(Deserialize)] | |
| 26 | +pub struct SettingsQuery { | |
| 27 | + ok: Option<String>, | |
| 28 | + err: Option<String>, | |
| 29 | +} | |
| 30 | + | |
| 31 | +pub async fn settings_page( | |
| 32 | + State(state): State<AppState>, | |
| 33 | + auth: AuthUser, | |
| 34 | + Query(q): Query<SettingsQuery>, | |
| 35 | +) -> Result<HtmlTemplate<SettingsPage>, AppError> { | |
| 36 | + let notice = match q.ok.as_deref() { | |
| 37 | + Some("mdp") => Some("Mot de passe modifié."), | |
| 38 | + Some("tache") => Some("Tâche enregistrée."), | |
| 39 | + _ => None, | |
| 40 | + }; | |
| 41 | + let error = match q.err.as_deref() { | |
| 42 | + Some("mdp") => Some("Ancien mot de passe incorrect."), | |
| 43 | + Some("confirm") => Some("La confirmation ne correspond pas."), | |
| 44 | + Some("court") => Some("Le nouveau mot de passe doit faire au moins 8 caractères."), | |
| 45 | + Some("doublon") => Some("Une tâche porte déjà ce nom."), | |
| 46 | + Some("vide") => Some("Le nom de la tâche ne peut pas être vide."), | |
| 47 | + _ => None, | |
| 48 | + }; | |
| 49 | + Ok(HtmlTemplate(SettingsPage { | |
| 50 | + tasks: tasks::list_all(&state.pool).await?, | |
| 51 | + csrf: auth.csrf_token(&state), | |
| 52 | + notice, | |
| 53 | + error, | |
| 54 | + })) | |
| 55 | +} | |
| 56 | + | |
| 57 | +#[derive(Deserialize)] | |
| 58 | +pub struct TaskForm { | |
| 59 | + name: String, | |
| 60 | + csrf: String, | |
| 61 | +} | |
| 62 | + | |
| 63 | +fn is_unique_violation(e: &sqlx::Error) -> bool { | |
| 64 | + matches!(e, sqlx::Error::Database(db) if db.is_unique_violation()) | |
| 65 | +} | |
| 66 | + | |
| 67 | +pub async fn task_create( | |
| 68 | + State(state): State<AppState>, | |
| 69 | + auth: AuthUser, | |
| 70 | + Form(form): Form<TaskForm>, | |
| 71 | +) -> Result<Redirect, AppError> { | |
| 72 | + if !auth.check_csrf(&state, &form.csrf) { | |
| 73 | + return Err(AppError::Csrf); | |
| 74 | + } | |
| 75 | + let name = form.name.trim(); | |
| 76 | + if name.is_empty() { | |
| 77 | + return Ok(Redirect::to("/parametres?err=vide")); | |
| 78 | + } | |
| 79 | + match tasks::create(&state.pool, name).await { | |
| 80 | + Ok(_) => Ok(Redirect::to("/parametres?ok=tache")), | |
| 81 | + Err(e) if is_unique_violation(&e) => Ok(Redirect::to("/parametres?err=doublon")), | |
| 82 | + Err(e) => Err(e.into()), | |
| 83 | + } | |
| 84 | +} | |
| 85 | + | |
| 86 | +pub async fn task_rename( | |
| 87 | + State(state): State<AppState>, | |
| 88 | + auth: AuthUser, | |
| 89 | + Path(id): Path<Uuid>, | |
| 90 | + Form(form): Form<TaskForm>, | |
| 91 | +) -> Result<Redirect, AppError> { | |
| 92 | + if !auth.check_csrf(&state, &form.csrf) { | |
| 93 | + return Err(AppError::Csrf); | |
| 94 | + } | |
| 95 | + let name = form.name.trim(); | |
| 96 | + if name.is_empty() { | |
| 97 | + return Ok(Redirect::to("/parametres?err=vide")); | |
| 98 | + } | |
| 99 | + match tasks::rename(&state.pool, id, name).await { | |
| 100 | + Ok(0) => Err(AppError::NotFound), | |
| 101 | + Ok(_) => Ok(Redirect::to("/parametres?ok=tache")), | |
| 102 | + Err(e) if is_unique_violation(&e) => Ok(Redirect::to("/parametres?err=doublon")), | |
| 103 | + Err(e) => Err(e.into()), | |
| 104 | + } | |
| 105 | +} | |
| 106 | + | |
| 107 | +#[derive(Deserialize)] | |
| 108 | +pub struct CsrfOnly { | |
| 109 | + csrf: String, | |
| 110 | +} | |
| 111 | + | |
| 112 | +pub async fn task_toggle_archive( | |
| 113 | + State(state): State<AppState>, | |
| 114 | + auth: AuthUser, | |
| 115 | + Path(id): Path<Uuid>, | |
| 116 | + Form(form): Form<CsrfOnly>, | |
| 117 | +) -> Result<Redirect, AppError> { | |
| 118 | + if !auth.check_csrf(&state, &form.csrf) { | |
| 119 | + return Err(AppError::Csrf); | |
| 120 | + } | |
| 121 | + if tasks::toggle_archived(&state.pool, id).await? == 0 { | |
| 122 | + return Err(AppError::NotFound); | |
| 123 | + } | |
| 124 | + Ok(Redirect::to("/parametres?ok=tache")) | |
| 125 | +} | |
| 126 | + | |
| 127 | +#[derive(Deserialize)] | |
| 128 | +pub struct PasswordForm { | |
| 129 | + ancien: String, | |
| 130 | + nouveau: String, | |
| 131 | + confirmation: String, | |
| 132 | + csrf: String, | |
| 133 | +} | |
| 134 | + | |
| 135 | +pub async fn password_change( | |
| 136 | + State(state): State<AppState>, | |
| 137 | + auth: AuthUser, | |
| 138 | + Form(form): Form<PasswordForm>, | |
| 139 | +) -> Result<Redirect, AppError> { | |
| 140 | + if !auth.check_csrf(&state, &form.csrf) { | |
| 141 | + return Err(AppError::Csrf); | |
| 142 | + } | |
| 143 | + if form.nouveau != form.confirmation { | |
| 144 | + return Ok(Redirect::to("/parametres?err=confirm")); | |
| 145 | + } | |
| 146 | + if form.nouveau.len() < 8 { | |
| 147 | + return Ok(Redirect::to("/parametres?err=court")); | |
| 148 | + } | |
| 149 | + let current = users::password_hash(&state.pool, auth.id) | |
| 150 | + .await? | |
| 151 | + .ok_or(AppError::NotFound)?; | |
| 152 | + if !password::verify(&form.ancien, ¤t) { | |
| 153 | + return Ok(Redirect::to("/parametres?err=mdp")); | |
| 154 | + } | |
| 155 | + let new_hash = password::hash(&form.nouveau).map_err(AppError::Internal)?; | |
| 156 | + users::update_password(&state.pool, auth.id, &new_hash).await?; | |
| 157 | + Ok(Redirect::to("/parametres?ok=mdp")) | |
| 158 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,158 @@ | |||
| 1 | +use askama::Template; | ||
| 2 | +use axum::Form; | ||
| 3 | +use axum::extract::{Path, Query, State}; | ||
| 4 | +use axum::response::Redirect; | ||
| 5 | +use serde::Deserialize; | ||
| 6 | +use uuid::Uuid; | ||
| 7 | + | ||
| 8 | +use crate::auth::extractor::AuthUser; | ||
| 9 | +use crate::auth::password; | ||
| 10 | +use crate::db::{tasks, users}; | ||
| 11 | +use crate::db::tasks::TaskRow; | ||
| 12 | +use crate::error::AppError; | ||
| 13 | +use crate::render::HtmlTemplate; | ||
| 14 | +use crate::state::AppState; | ||
| 15 | + | ||
| 16 | +#[derive(Template)] | ||
| 17 | +#[template(path = "settings.html")] | ||
| 18 | +pub struct SettingsPage { | ||
| 19 | + pub tasks: Vec<TaskRow>, | ||
| 20 | + pub csrf: String, | ||
| 21 | + pub notice: Option<&'static str>, | ||
| 22 | + pub error: Option<&'static str>, | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +#[derive(Deserialize)] | ||
| 26 | +pub struct SettingsQuery { | ||
| 27 | + ok: Option<String>, | ||
| 28 | + err: Option<String>, | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +pub async fn settings_page( | ||
| 32 | + State(state): State<AppState>, | ||
| 33 | + auth: AuthUser, | ||
| 34 | + Query(q): Query<SettingsQuery>, | ||
| 35 | +) -> Result<HtmlTemplate<SettingsPage>, AppError> { | ||
| 36 | + let notice = match q.ok.as_deref() { | ||
| 37 | + Some("mdp") => Some("Mot de passe modifié."), | ||
| 38 | + Some("tache") => Some("Tâche enregistrée."), | ||
| 39 | + _ => None, | ||
| 40 | + }; | ||
| 41 | + let error = match q.err.as_deref() { | ||
| 42 | + Some("mdp") => Some("Ancien mot de passe incorrect."), | ||
| 43 | + Some("confirm") => Some("La confirmation ne correspond pas."), | ||
| 44 | + Some("court") => Some("Le nouveau mot de passe doit faire au moins 8 caractères."), | ||
| 45 | + Some("doublon") => Some("Une tâche porte déjà ce nom."), | ||
| 46 | + Some("vide") => Some("Le nom de la tâche ne peut pas être vide."), | ||
| 47 | + _ => None, | ||
| 48 | + }; | ||
| 49 | + Ok(HtmlTemplate(SettingsPage { | ||
| 50 | + tasks: tasks::list_all(&state.pool).await?, | ||
| 51 | + csrf: auth.csrf_token(&state), | ||
| 52 | + notice, | ||
| 53 | + error, | ||
| 54 | + })) | ||
| 55 | +} | ||
| 56 | + | ||
| 57 | +#[derive(Deserialize)] | ||
| 58 | +pub struct TaskForm { | ||
| 59 | + name: String, | ||
| 60 | + csrf: String, | ||
| 61 | +} | ||
| 62 | + | ||
| 63 | +fn is_unique_violation(e: &sqlx::Error) -> bool { | ||
| 64 | + matches!(e, sqlx::Error::Database(db) if db.is_unique_violation()) | ||
| 65 | +} | ||
| 66 | + | ||
| 67 | +pub async fn task_create( | ||
| 68 | + State(state): State<AppState>, | ||
| 69 | + auth: AuthUser, | ||
| 70 | + Form(form): Form<TaskForm>, | ||
| 71 | +) -> Result<Redirect, AppError> { | ||
| 72 | + if !auth.check_csrf(&state, &form.csrf) { | ||
| 73 | + return Err(AppError::Csrf); | ||
| 74 | + } | ||
| 75 | + let name = form.name.trim(); | ||
| 76 | + if name.is_empty() { | ||
| 77 | + return Ok(Redirect::to("/parametres?err=vide")); | ||
| 78 | + } | ||
| 79 | + match tasks::create(&state.pool, name).await { | ||
| 80 | + Ok(_) => Ok(Redirect::to("/parametres?ok=tache")), | ||
| 81 | + Err(e) if is_unique_violation(&e) => Ok(Redirect::to("/parametres?err=doublon")), | ||
| 82 | + Err(e) => Err(e.into()), | ||
| 83 | + } | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +pub async fn task_rename( | ||
| 87 | + State(state): State<AppState>, | ||
| 88 | + auth: AuthUser, | ||
| 89 | + Path(id): Path<Uuid>, | ||
| 90 | + Form(form): Form<TaskForm>, | ||
| 91 | +) -> Result<Redirect, AppError> { | ||
| 92 | + if !auth.check_csrf(&state, &form.csrf) { | ||
| 93 | + return Err(AppError::Csrf); | ||
| 94 | + } | ||
| 95 | + let name = form.name.trim(); | ||
| 96 | + if name.is_empty() { | ||
| 97 | + return Ok(Redirect::to("/parametres?err=vide")); | ||
| 98 | + } | ||
| 99 | + match tasks::rename(&state.pool, id, name).await { | ||
| 100 | + Ok(0) => Err(AppError::NotFound), | ||
| 101 | + Ok(_) => Ok(Redirect::to("/parametres?ok=tache")), | ||
| 102 | + Err(e) if is_unique_violation(&e) => Ok(Redirect::to("/parametres?err=doublon")), | ||
| 103 | + Err(e) => Err(e.into()), | ||
| 104 | + } | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +#[derive(Deserialize)] | ||
| 108 | +pub struct CsrfOnly { | ||
| 109 | + csrf: String, | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +pub async fn task_toggle_archive( | ||
| 113 | + State(state): State<AppState>, | ||
| 114 | + auth: AuthUser, | ||
| 115 | + Path(id): Path<Uuid>, | ||
| 116 | + Form(form): Form<CsrfOnly>, | ||
| 117 | +) -> Result<Redirect, AppError> { | ||
| 118 | + if !auth.check_csrf(&state, &form.csrf) { | ||
| 119 | + return Err(AppError::Csrf); | ||
| 120 | + } | ||
| 121 | + if tasks::toggle_archived(&state.pool, id).await? == 0 { | ||
| 122 | + return Err(AppError::NotFound); | ||
| 123 | + } | ||
| 124 | + Ok(Redirect::to("/parametres?ok=tache")) | ||
| 125 | +} | ||
| 126 | + | ||
| 127 | +#[derive(Deserialize)] | ||
| 128 | +pub struct PasswordForm { | ||
| 129 | + ancien: String, | ||
| 130 | + nouveau: String, | ||
| 131 | + confirmation: String, | ||
| 132 | + csrf: String, | ||
| 133 | +} | ||
| 134 | + | ||
| 135 | +pub async fn password_change( | ||
| 136 | + State(state): State<AppState>, | ||
| 137 | + auth: AuthUser, | ||
| 138 | + Form(form): Form<PasswordForm>, | ||
| 139 | +) -> Result<Redirect, AppError> { | ||
| 140 | + if !auth.check_csrf(&state, &form.csrf) { | ||
| 141 | + return Err(AppError::Csrf); | ||
| 142 | + } | ||
| 143 | + if form.nouveau != form.confirmation { | ||
| 144 | + return Ok(Redirect::to("/parametres?err=confirm")); | ||
| 145 | + } | ||
| 146 | + if form.nouveau.len() < 8 { | ||
| 147 | + return Ok(Redirect::to("/parametres?err=court")); | ||
| 148 | + } | ||
| 149 | + let current = users::password_hash(&state.pool, auth.id) | ||
| 150 | + .await? | ||
| 151 | + .ok_or(AppError::NotFound)?; | ||
| 152 | + if !password::verify(&form.ancien, ¤t) { | ||
| 153 | + return Ok(Redirect::to("/parametres?err=mdp")); | ||
| 154 | + } | ||
| 155 | + let new_hash = password::hash(&form.nouveau).map_err(AppError::Internal)?; | ||
| 156 | + users::update_password(&state.pool, auth.id, &new_hash).await?; | ||
| 157 | + Ok(Redirect::to("/parametres?ok=mdp")) | ||
| 158 | +} | ||
added
src/routes/statics.rs +28 -0 | new file mode 100644 | ||
| @@ -0,0 +1,28 @@ | ||
| 1 | +use axum::http::header::{CACHE_CONTROL, CONTENT_TYPE}; | |
| 2 | +use axum::response::IntoResponse; | |
| 3 | + | |
| 4 | +const CACHE: &str = "public, max-age=86400"; | |
| 5 | + | |
| 6 | +pub async fn css() -> impl IntoResponse { | |
| 7 | + ( | |
| 8 | + [(CONTENT_TYPE, "text/css; charset=utf-8"), (CACHE_CONTROL, CACHE)], | |
| 9 | + include_bytes!("../../assets/style.css").as_slice(), | |
| 10 | + ) | |
| 11 | +} | |
| 12 | + | |
| 13 | +pub async fn manifest() -> impl IntoResponse { | |
| 14 | + ( | |
| 15 | + [ | |
| 16 | + (CONTENT_TYPE, "application/manifest+json"), | |
| 17 | + (CACHE_CONTROL, CACHE), | |
| 18 | + ], | |
| 19 | + include_bytes!("../../assets/manifest.json").as_slice(), | |
| 20 | + ) | |
| 21 | +} | |
| 22 | + | |
| 23 | +pub async fn icon() -> impl IntoResponse { | |
| 24 | + ( | |
| 25 | + [(CONTENT_TYPE, "image/png"), (CACHE_CONTROL, CACHE)], | |
| 26 | + include_bytes!("../../assets/icon-512.png").as_slice(), | |
| 27 | + ) | |
| 28 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | +use axum::http::header::{CACHE_CONTROL, CONTENT_TYPE}; | ||
| 2 | +use axum::response::IntoResponse; | ||
| 3 | + | ||
| 4 | +const CACHE: &str = "public, max-age=86400"; | ||
| 5 | + | ||
| 6 | +pub async fn css() -> impl IntoResponse { | ||
| 7 | + ( | ||
| 8 | + [(CONTENT_TYPE, "text/css; charset=utf-8"), (CACHE_CONTROL, CACHE)], | ||
| 9 | + include_bytes!("../../assets/style.css").as_slice(), | ||
| 10 | + ) | ||
| 11 | +} | ||
| 12 | + | ||
| 13 | +pub async fn manifest() -> impl IntoResponse { | ||
| 14 | + ( | ||
| 15 | + [ | ||
| 16 | + (CONTENT_TYPE, "application/manifest+json"), | ||
| 17 | + (CACHE_CONTROL, CACHE), | ||
| 18 | + ], | ||
| 19 | + include_bytes!("../../assets/manifest.json").as_slice(), | ||
| 20 | + ) | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +pub async fn icon() -> impl IntoResponse { | ||
| 24 | + ( | ||
| 25 | + [(CONTENT_TYPE, "image/png"), (CACHE_CONTROL, CACHE)], | ||
| 26 | + include_bytes!("../../assets/icon-512.png").as_slice(), | ||
| 27 | + ) | ||
| 28 | +} | ||
added
src/routes/stats.rs +76 -0 | new file mode 100644 | ||
| @@ -0,0 +1,76 @@ | ||
| 1 | +use askama::Template; | |
| 2 | +use axum::extract::{Query, State}; | |
| 3 | +use serde::Deserialize; | |
| 4 | + | |
| 5 | +use crate::auth::extractor::AuthUser; | |
| 6 | +use crate::charts::{ChartView, LegendItem, legend}; | |
| 7 | +use crate::db::{stats, users}; | |
| 8 | +use crate::error::AppError; | |
| 9 | +use crate::period::{Month, Week}; | |
| 10 | +use crate::render::HtmlTemplate; | |
| 11 | +use crate::state::AppState; | |
| 12 | + | |
| 13 | +#[derive(Template)] | |
| 14 | +#[template(path = "stats.html")] | |
| 15 | +pub struct StatsPage { | |
| 16 | + pub week_chart: ChartView, | |
| 17 | + pub month_chart: ChartView, | |
| 18 | + pub total_chart: ChartView, | |
| 19 | + pub legend: Vec<LegendItem>, | |
| 20 | + pub week_prev: String, | |
| 21 | + pub week_next: Option<String>, | |
| 22 | + pub month_prev: String, | |
| 23 | + pub month_next: Option<String>, | |
| 24 | +} | |
| 25 | + | |
| 26 | +#[derive(Deserialize)] | |
| 27 | +pub struct StatsQuery { | |
| 28 | + semaine: Option<String>, | |
| 29 | + mois: Option<String>, | |
| 30 | +} | |
| 31 | + | |
| 32 | +fn href(week: Week, month: Month) -> String { | |
| 33 | + format!("/stats?semaine={week}&mois={month}") | |
| 34 | +} | |
| 35 | + | |
| 36 | +pub async fn stats_page( | |
| 37 | + State(state): State<AppState>, | |
| 38 | + _auth: AuthUser, | |
| 39 | + Query(q): Query<StatsQuery>, | |
| 40 | +) -> Result<HtmlTemplate<StatsPage>, AppError> { | |
| 41 | + let week = q | |
| 42 | + .semaine | |
| 43 | + .as_deref() | |
| 44 | + .and_then(Week::parse) | |
| 45 | + .unwrap_or_else(Week::current); | |
| 46 | + let month = q | |
| 47 | + .mois | |
| 48 | + .as_deref() | |
| 49 | + .and_then(Month::parse) | |
| 50 | + .unwrap_or_else(Month::current); | |
| 51 | + | |
| 52 | + let (week_from, week_to) = week.bounds(); | |
| 53 | + let (month_from, month_to) = month.bounds(); | |
| 54 | + | |
| 55 | + let all_users = users::list_all(&state.pool).await?; | |
| 56 | + let week_rows = stats::totals(&state.pool, Some(week_from), Some(week_to)).await?; | |
| 57 | + let month_rows = stats::totals(&state.pool, Some(month_from), Some(month_to)).await?; | |
| 58 | + let total_rows = stats::totals(&state.pool, None, None).await?; | |
| 59 | + | |
| 60 | + let legend = legend(&[&week_rows, &month_rows, &total_rows]); | |
| 61 | + | |
| 62 | + // Les flèches « suivant » disparaissent une fois revenu à la période courante. | |
| 63 | + let week_next = (week != Week::current()).then(|| href(week.next(), month)); | |
| 64 | + let month_next = (month != Month::current()).then(|| href(week, month.next())); | |
| 65 | + | |
| 66 | + Ok(HtmlTemplate(StatsPage { | |
| 67 | + week_chart: ChartView::build(week.label(), &all_users, &week_rows), | |
| 68 | + month_chart: ChartView::build(month.label(), &all_users, &month_rows), | |
| 69 | + total_chart: ChartView::build("Depuis le début".into(), &all_users, &total_rows), | |
| 70 | + legend, | |
| 71 | + week_prev: href(week.prev(), month), | |
| 72 | + week_next, | |
| 73 | + month_prev: href(week, month.prev()), | |
| 74 | + month_next, | |
| 75 | + })) | |
| 76 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,76 @@ | |||
| 1 | +use askama::Template; | ||
| 2 | +use axum::extract::{Query, State}; | ||
| 3 | +use serde::Deserialize; | ||
| 4 | + | ||
| 5 | +use crate::auth::extractor::AuthUser; | ||
| 6 | +use crate::charts::{ChartView, LegendItem, legend}; | ||
| 7 | +use crate::db::{stats, users}; | ||
| 8 | +use crate::error::AppError; | ||
| 9 | +use crate::period::{Month, Week}; | ||
| 10 | +use crate::render::HtmlTemplate; | ||
| 11 | +use crate::state::AppState; | ||
| 12 | + | ||
| 13 | +#[derive(Template)] | ||
| 14 | +#[template(path = "stats.html")] | ||
| 15 | +pub struct StatsPage { | ||
| 16 | + pub week_chart: ChartView, | ||
| 17 | + pub month_chart: ChartView, | ||
| 18 | + pub total_chart: ChartView, | ||
| 19 | + pub legend: Vec<LegendItem>, | ||
| 20 | + pub week_prev: String, | ||
| 21 | + pub week_next: Option<String>, | ||
| 22 | + pub month_prev: String, | ||
| 23 | + pub month_next: Option<String>, | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +#[derive(Deserialize)] | ||
| 27 | +pub struct StatsQuery { | ||
| 28 | + semaine: Option<String>, | ||
| 29 | + mois: Option<String>, | ||
| 30 | +} | ||
| 31 | + | ||
| 32 | +fn href(week: Week, month: Month) -> String { | ||
| 33 | + format!("/stats?semaine={week}&mois={month}") | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +pub async fn stats_page( | ||
| 37 | + State(state): State<AppState>, | ||
| 38 | + _auth: AuthUser, | ||
| 39 | + Query(q): Query<StatsQuery>, | ||
| 40 | +) -> Result<HtmlTemplate<StatsPage>, AppError> { | ||
| 41 | + let week = q | ||
| 42 | + .semaine | ||
| 43 | + .as_deref() | ||
| 44 | + .and_then(Week::parse) | ||
| 45 | + .unwrap_or_else(Week::current); | ||
| 46 | + let month = q | ||
| 47 | + .mois | ||
| 48 | + .as_deref() | ||
| 49 | + .and_then(Month::parse) | ||
| 50 | + .unwrap_or_else(Month::current); | ||
| 51 | + | ||
| 52 | + let (week_from, week_to) = week.bounds(); | ||
| 53 | + let (month_from, month_to) = month.bounds(); | ||
| 54 | + | ||
| 55 | + let all_users = users::list_all(&state.pool).await?; | ||
| 56 | + let week_rows = stats::totals(&state.pool, Some(week_from), Some(week_to)).await?; | ||
| 57 | + let month_rows = stats::totals(&state.pool, Some(month_from), Some(month_to)).await?; | ||
| 58 | + let total_rows = stats::totals(&state.pool, None, None).await?; | ||
| 59 | + | ||
| 60 | + let legend = legend(&[&week_rows, &month_rows, &total_rows]); | ||
| 61 | + | ||
| 62 | + // Les flèches « suivant » disparaissent une fois revenu à la période courante. | ||
| 63 | + let week_next = (week != Week::current()).then(|| href(week.next(), month)); | ||
| 64 | + let month_next = (month != Month::current()).then(|| href(week, month.next())); | ||
| 65 | + | ||
| 66 | + Ok(HtmlTemplate(StatsPage { | ||
| 67 | + week_chart: ChartView::build(week.label(), &all_users, &week_rows), | ||
| 68 | + month_chart: ChartView::build(month.label(), &all_users, &month_rows), | ||
| 69 | + total_chart: ChartView::build("Depuis le début".into(), &all_users, &total_rows), | ||
| 70 | + legend, | ||
| 71 | + week_prev: href(week.prev(), month), | ||
| 72 | + week_next, | ||
| 73 | + month_prev: href(week, month.prev()), | ||
| 74 | + month_next, | ||
| 75 | + })) | ||
| 76 | +} | ||
added
src/state.rs +39 -0 | new file mode 100644 | ||
| @@ -0,0 +1,39 @@ | ||
| 1 | +use std::sync::Arc; | |
| 2 | + | |
| 3 | +use anyhow::Result; | |
| 4 | +use biscuit_auth::{KeyPair, PrivateKey, PublicKey, builder::Algorithm}; | |
| 5 | +use sha2::{Digest, Sha256}; | |
| 6 | +use sqlx::PgPool; | |
| 7 | + | |
| 8 | +use crate::auth::revocation::RevocationCache; | |
| 9 | + | |
| 10 | +#[derive(Clone)] | |
| 11 | +pub struct AppState { | |
| 12 | + pub pool: PgPool, | |
| 13 | + pub root: Arc<KeyPair>, | |
| 14 | + pub public: PublicKey, | |
| 15 | + pub csrf_key: [u8; 32], | |
| 16 | + pub revocations: RevocationCache, | |
| 17 | +} | |
| 18 | + | |
| 19 | +impl AppState { | |
| 20 | + pub fn new(pool: PgPool, private_key_hex: &str) -> Result<Self> { | |
| 21 | + let private = PrivateKey::from_bytes_hex(private_key_hex, Algorithm::Ed25519)?; | |
| 22 | + let root = KeyPair::from(&private); | |
| 23 | + let public = root.public(); | |
| 24 | + | |
| 25 | + // Clé CSRF dérivée de la clé privée : pas d'env var supplémentaire. | |
| 26 | + let mut hasher = Sha256::new(); | |
| 27 | + hasher.update(b"csrf"); | |
| 28 | + hasher.update(private.to_bytes().as_slice()); | |
| 29 | + let csrf_key: [u8; 32] = hasher.finalize().into(); | |
| 30 | + | |
| 31 | + Ok(Self { | |
| 32 | + pool, | |
| 33 | + root: Arc::new(root), | |
| 34 | + public, | |
| 35 | + csrf_key, | |
| 36 | + revocations: RevocationCache::default(), | |
| 37 | + }) | |
| 38 | + } | |
| 39 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | +use std::sync::Arc; | ||
| 2 | + | ||
| 3 | +use anyhow::Result; | ||
| 4 | +use biscuit_auth::{KeyPair, PrivateKey, PublicKey, builder::Algorithm}; | ||
| 5 | +use sha2::{Digest, Sha256}; | ||
| 6 | +use sqlx::PgPool; | ||
| 7 | + | ||
| 8 | +use crate::auth::revocation::RevocationCache; | ||
| 9 | + | ||
| 10 | +#[derive(Clone)] | ||
| 11 | +pub struct AppState { | ||
| 12 | + pub pool: PgPool, | ||
| 13 | + pub root: Arc<KeyPair>, | ||
| 14 | + pub public: PublicKey, | ||
| 15 | + pub csrf_key: [u8; 32], | ||
| 16 | + pub revocations: RevocationCache, | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +impl AppState { | ||
| 20 | + pub fn new(pool: PgPool, private_key_hex: &str) -> Result<Self> { | ||
| 21 | + let private = PrivateKey::from_bytes_hex(private_key_hex, Algorithm::Ed25519)?; | ||
| 22 | + let root = KeyPair::from(&private); | ||
| 23 | + let public = root.public(); | ||
| 24 | + | ||
| 25 | + // Clé CSRF dérivée de la clé privée : pas d'env var supplémentaire. | ||
| 26 | + let mut hasher = Sha256::new(); | ||
| 27 | + hasher.update(b"csrf"); | ||
| 28 | + hasher.update(private.to_bytes().as_slice()); | ||
| 29 | + let csrf_key: [u8; 32] = hasher.finalize().into(); | ||
| 30 | + | ||
| 31 | + Ok(Self { | ||
| 32 | + pool, | ||
| 33 | + root: Arc::new(root), | ||
| 34 | + public, | ||
| 35 | + csrf_key, | ||
| 36 | + revocations: RevocationCache::default(), | ||
| 37 | + }) | ||
| 38 | + } | ||
| 39 | +} | ||
added
templates/base.html +19 -0 | new file mode 100644 | ||
| @@ -0,0 +1,19 @@ | ||
| 1 | +<!doctype html> | |
| 2 | +<html lang="fr"> | |
| 3 | +<head> | |
| 4 | +<meta charset="utf-8"> | |
| 5 | +<meta name="viewport" content="width=device-width, initial-scale=1"> | |
| 6 | +<meta name="theme-color" content="#1d3557"> | |
| 7 | +<link rel="manifest" href="/manifest.json"> | |
| 8 | +<link rel="icon" href="/icon-512.png"> | |
| 9 | +<link rel="apple-touch-icon" href="/icon-512.png"> | |
| 10 | +<link rel="stylesheet" href="/static/style.css"> | |
| 11 | +<title>{% block title %}time-time{% endblock %}</title> | |
| 12 | +</head> | |
| 13 | +<body> | |
| 14 | +<header class="topbar"><a href="/" class="brand">⏱ time-time</a></header> | |
| 15 | +<main class="page"> | |
| 16 | +{% block content %}{% endblock %} | |
| 17 | +</main> | |
| 18 | +</body> | |
| 19 | +</html> | |
| new file mode 100644 | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | +<!doctype html> | ||
| 2 | +<html lang="fr"> | ||
| 3 | +<head> | ||
| 4 | +<meta charset="utf-8"> | ||
| 5 | +<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| 6 | +<meta name="theme-color" content="#1d3557"> | ||
| 7 | +<link rel="manifest" href="/manifest.json"> | ||
| 8 | +<link rel="icon" href="/icon-512.png"> | ||
| 9 | +<link rel="apple-touch-icon" href="/icon-512.png"> | ||
| 10 | +<link rel="stylesheet" href="/static/style.css"> | ||
| 11 | +<title>{% block title %}time-time{% endblock %}</title> | ||
| 12 | +</head> | ||
| 13 | +<body> | ||
| 14 | +<header class="topbar"><a href="/" class="brand">⏱ time-time</a></header> | ||
| 15 | +<main class="page"> | ||
| 16 | +{% block content %}{% endblock %} | ||
| 17 | +</main> | ||
| 18 | +</body> | ||
| 19 | +</html> | ||
added
templates/chart.svg.html +21 -0 | new file mode 100644 | ||
| @@ -0,0 +1,21 @@ | ||
| 1 | +<svg viewBox="0 0 400 260" width="100%" class="chart" role="img" aria-label="{{ chart.title }}"> | |
| 2 | + {% for t in chart.y_ticks %} | |
| 3 | + <line x1="{{ chart.left() }}" x2="{{ chart.right() }}" y1="{{ t.y }}" y2="{{ t.y }}" class="gridline"/> | |
| 4 | + <text x="{{ chart.left() - 4.0 }}" y="{{ t.y + 3.0 }}" text-anchor="end" class="tick">{{ t.label }}</text> | |
| 5 | + {% endfor %} | |
| 6 | + <line x1="{{ chart.left() }}" x2="{{ chart.right() }}" y1="{{ chart.baseline }}" y2="{{ chart.baseline }}" class="axis"/> | |
| 7 | + {% for bar in chart.bars %} | |
| 8 | + {% for s in bar.segments %} | |
| 9 | + <rect x="{{ s.x }}" y="{{ s.y }}" width="{{ s.w }}" height="{{ s.h }}" fill="{{ s.color }}"> | |
| 10 | + <title>{{ s.title }}</title> | |
| 11 | + </rect> | |
| 12 | + {% endfor %} | |
| 13 | + <text x="{{ bar.label_x }}" y="248" text-anchor="middle" class="bar-label">{{ bar.user_label }}</text> | |
| 14 | + {% if !bar.total_label.is_empty() %} | |
| 15 | + <text x="{{ bar.label_x }}" y="{{ bar.total_y }}" text-anchor="middle" class="total-label">{{ bar.total_label }}</text> | |
| 16 | + {% endif %} | |
| 17 | + {% endfor %} | |
| 18 | + {% if chart.empty %} | |
| 19 | + <text x="218" y="130" text-anchor="middle" class="empty-label">Aucun temps sur cette période</text> | |
| 20 | + {% endif %} | |
| 21 | +</svg> | |
| new file mode 100644 | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | +<svg viewBox="0 0 400 260" width="100%" class="chart" role="img" aria-label="{{ chart.title }}"> | ||
| 2 | + {% for t in chart.y_ticks %} | ||
| 3 | + <line x1="{{ chart.left() }}" x2="{{ chart.right() }}" y1="{{ t.y }}" y2="{{ t.y }}" class="gridline"/> | ||
| 4 | + <text x="{{ chart.left() - 4.0 }}" y="{{ t.y + 3.0 }}" text-anchor="end" class="tick">{{ t.label }}</text> | ||
| 5 | + {% endfor %} | ||
| 6 | + <line x1="{{ chart.left() }}" x2="{{ chart.right() }}" y1="{{ chart.baseline }}" y2="{{ chart.baseline }}" class="axis"/> | ||
| 7 | + {% for bar in chart.bars %} | ||
| 8 | + {% for s in bar.segments %} | ||
| 9 | + <rect x="{{ s.x }}" y="{{ s.y }}" width="{{ s.w }}" height="{{ s.h }}" fill="{{ s.color }}"> | ||
| 10 | + <title>{{ s.title }}</title> | ||
| 11 | + </rect> | ||
| 12 | + {% endfor %} | ||
| 13 | + <text x="{{ bar.label_x }}" y="248" text-anchor="middle" class="bar-label">{{ bar.user_label }}</text> | ||
| 14 | + {% if !bar.total_label.is_empty() %} | ||
| 15 | + <text x="{{ bar.label_x }}" y="{{ bar.total_y }}" text-anchor="middle" class="total-label">{{ bar.total_label }}</text> | ||
| 16 | + {% endif %} | ||
| 17 | + {% endfor %} | ||
| 18 | + {% if chart.empty %} | ||
| 19 | + <text x="218" y="130" text-anchor="middle" class="empty-label">Aucun temps sur cette période</text> | ||
| 20 | + {% endif %} | ||
| 21 | +</svg> | ||
added
templates/entries_list.html +25 -0 | new file mode 100644 | ||
| @@ -0,0 +1,25 @@ | ||
| 1 | +{% extends "base.html" %} | |
| 2 | +{% block title %}Mes temps · time-time{% endblock %} | |
| 3 | +{% block content %} | |
| 4 | +<h1>Mes temps</h1> | |
| 5 | +{% if entries.is_empty() %} | |
| 6 | +<p>Aucun temps enregistré{% if page > 1 %} sur cette page{% endif %}.</p> | |
| 7 | +{% else %} | |
| 8 | +<ul class="entries"> | |
| 9 | + {% for e in entries %} | |
| 10 | + <li> | |
| 11 | + <a class="entry" href="/temps/{{ e.id }}/modifier"> | |
| 12 | + <span class="entry-date">{{ e.date }}</span> | |
| 13 | + <span class="entry-task">{{ e.task_name }}</span> | |
| 14 | + <span class="entry-duration">{{ e.duration }}</span> | |
| 15 | + </a> | |
| 16 | + </li> | |
| 17 | + {% endfor %} | |
| 18 | +</ul> | |
| 19 | +{% endif %} | |
| 20 | +<nav class="pager"> | |
| 21 | + {% if page > 1 %}<a class="btn" href="/temps?page={{ page - 1 }}">← Plus récents</a>{% endif %} | |
| 22 | + {% if has_next %}<a class="btn" href="/temps?page={{ page + 1 }}">Plus anciens →</a>{% endif %} | |
| 23 | +</nav> | |
| 24 | +<p><a class="btn" href="/stats">← Statistiques</a></p> | |
| 25 | +{% endblock %} | |
| new file mode 100644 | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | +{% extends "base.html" %} | ||
| 2 | +{% block title %}Mes temps · time-time{% endblock %} | ||
| 3 | +{% block content %} | ||
| 4 | +<h1>Mes temps</h1> | ||
| 5 | +{% if entries.is_empty() %} | ||
| 6 | +<p>Aucun temps enregistré{% if page > 1 %} sur cette page{% endif %}.</p> | ||
| 7 | +{% else %} | ||
| 8 | +<ul class="entries"> | ||
| 9 | + {% for e in entries %} | ||
| 10 | + <li> | ||
| 11 | + <a class="entry" href="/temps/{{ e.id }}/modifier"> | ||
| 12 | + <span class="entry-date">{{ e.date }}</span> | ||
| 13 | + <span class="entry-task">{{ e.task_name }}</span> | ||
| 14 | + <span class="entry-duration">{{ e.duration }}</span> | ||
| 15 | + </a> | ||
| 16 | + </li> | ||
| 17 | + {% endfor %} | ||
| 18 | +</ul> | ||
| 19 | +{% endif %} | ||
| 20 | +<nav class="pager"> | ||
| 21 | + {% if page > 1 %}<a class="btn" href="/temps?page={{ page - 1 }}">← Plus récents</a>{% endif %} | ||
| 22 | + {% if has_next %}<a class="btn" href="/temps?page={{ page + 1 }}">Plus anciens →</a>{% endif %} | ||
| 23 | +</nav> | ||
| 24 | +<p><a class="btn" href="/stats">← Statistiques</a></p> | ||
| 25 | +{% endblock %} | ||
added
templates/entry_form.html +43 -0 | new file mode 100644 | ||
| @@ -0,0 +1,43 @@ | ||
| 1 | +{% extends "base.html" %} | |
| 2 | +{% block title %}{{ title }} · time-time{% endblock %} | |
| 3 | +{% block content %} | |
| 4 | +<h1>{{ title }}</h1> | |
| 5 | +{% if saved %}<p class="flash flash-ok">Temps enregistré ✔</p>{% endif %} | |
| 6 | +{% if tasks.is_empty() %} | |
| 7 | +<p class="flash flash-error">Aucune tâche disponible — créez-en une dans <a href="/parametres">Paramètres</a>.</p> | |
| 8 | +{% else %} | |
| 9 | +<form method="post" action="{{ action }}" class="stack"> | |
| 10 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | |
| 11 | + | |
| 12 | + <label for="minutes">Durée (minutes)</label> | |
| 13 | + <input id="minutes" name="minutes" type="number" inputmode="numeric" | |
| 14 | + min="1" max="1440" required | |
| 15 | + {% if let Some(m) = minutes %}value="{{ m }}"{% endif %}> | |
| 16 | + {% if !presets.is_empty() %} | |
| 17 | + <div class="presets"> | |
| 18 | + {% for p in presets %}<a class="btn btn-preset" href="{{ p.href }}">{{ p.label }}</a>{% endfor %} | |
| 19 | + </div> | |
| 20 | + {% endif %} | |
| 21 | + | |
| 22 | + <label for="tache">Tâche</label> | |
| 23 | + <select id="tache" name="tache" required> | |
| 24 | + {% for t in tasks %} | |
| 25 | + <option value="{{ t.id }}"{% if t.selected %} selected{% endif %}> | |
| 26 | + {{ t.name }}{% if t.archived %} (archivée){% endif %} | |
| 27 | + </option> | |
| 28 | + {% endfor %} | |
| 29 | + </select> | |
| 30 | + | |
| 31 | + <label for="date">Date</label> | |
| 32 | + <input id="date" name="date" type="date" value="{{ date }}" required> | |
| 33 | + | |
| 34 | + <button type="submit" class="btn btn-primary btn-big">Valider</button> | |
| 35 | +</form> | |
| 36 | +{% if let Some(del) = delete_action %} | |
| 37 | +<form method="post" action="{{ del }}" class="stack danger-zone"> | |
| 38 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | |
| 39 | + <button type="submit" class="btn btn-danger">Supprimer ce temps</button> | |
| 40 | +</form> | |
| 41 | +{% endif %} | |
| 42 | +{% endif %} | |
| 43 | +{% endblock %} | |
| new file mode 100644 | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | +{% extends "base.html" %} | ||
| 2 | +{% block title %}{{ title }} · time-time{% endblock %} | ||
| 3 | +{% block content %} | ||
| 4 | +<h1>{{ title }}</h1> | ||
| 5 | +{% if saved %}<p class="flash flash-ok">Temps enregistré ✔</p>{% endif %} | ||
| 6 | +{% if tasks.is_empty() %} | ||
| 7 | +<p class="flash flash-error">Aucune tâche disponible — créez-en une dans <a href="/parametres">Paramètres</a>.</p> | ||
| 8 | +{% else %} | ||
| 9 | +<form method="post" action="{{ action }}" class="stack"> | ||
| 10 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | ||
| 11 | + | ||
| 12 | + <label for="minutes">Durée (minutes)</label> | ||
| 13 | + <input id="minutes" name="minutes" type="number" inputmode="numeric" | ||
| 14 | + min="1" max="1440" required | ||
| 15 | + {% if let Some(m) = minutes %}value="{{ m }}"{% endif %}> | ||
| 16 | + {% if !presets.is_empty() %} | ||
| 17 | + <div class="presets"> | ||
| 18 | + {% for p in presets %}<a class="btn btn-preset" href="{{ p.href }}">{{ p.label }}</a>{% endfor %} | ||
| 19 | + </div> | ||
| 20 | + {% endif %} | ||
| 21 | + | ||
| 22 | + <label for="tache">Tâche</label> | ||
| 23 | + <select id="tache" name="tache" required> | ||
| 24 | + {% for t in tasks %} | ||
| 25 | + <option value="{{ t.id }}"{% if t.selected %} selected{% endif %}> | ||
| 26 | + {{ t.name }}{% if t.archived %} (archivée){% endif %} | ||
| 27 | + </option> | ||
| 28 | + {% endfor %} | ||
| 29 | + </select> | ||
| 30 | + | ||
| 31 | + <label for="date">Date</label> | ||
| 32 | + <input id="date" name="date" type="date" value="{{ date }}" required> | ||
| 33 | + | ||
| 34 | + <button type="submit" class="btn btn-primary btn-big">Valider</button> | ||
| 35 | +</form> | ||
| 36 | +{% if let Some(del) = delete_action %} | ||
| 37 | +<form method="post" action="{{ del }}" class="stack danger-zone"> | ||
| 38 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | ||
| 39 | + <button type="submit" class="btn btn-danger">Supprimer ce temps</button> | ||
| 40 | +</form> | ||
| 41 | +{% endif %} | ||
| 42 | +{% endif %} | ||
| 43 | +{% endblock %} | ||
added
templates/home.html +8 -0 | new file mode 100644 | ||
| @@ -0,0 +1,8 @@ | ||
| 1 | +{% extends "base.html" %} | |
| 2 | +{% block content %} | |
| 3 | +<nav class="stack menu"> | |
| 4 | + <a class="btn btn-primary btn-big" href="/temps/nouveau">➕ Ajouter un temps</a> | |
| 5 | + <a class="btn btn-big" href="/stats">📊 Statistiques</a> | |
| 6 | + <a class="btn btn-big" href="/parametres">⚙️ Paramètres</a> | |
| 7 | +</nav> | |
| 8 | +{% endblock %} | |
| new file mode 100644 | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | +{% extends "base.html" %} | ||
| 2 | +{% block content %} | ||
| 3 | +<nav class="stack menu"> | ||
| 4 | + <a class="btn btn-primary btn-big" href="/temps/nouveau">➕ Ajouter un temps</a> | ||
| 5 | + <a class="btn btn-big" href="/stats">📊 Statistiques</a> | ||
| 6 | + <a class="btn btn-big" href="/parametres">⚙️ Paramètres</a> | ||
| 7 | +</nav> | ||
| 8 | +{% endblock %} | ||
added
templates/login.html +14 -0 | new file mode 100644 | ||
| @@ -0,0 +1,14 @@ | ||
| 1 | +{% extends "base.html" %} | |
| 2 | +{% block title %}Connexion · time-time{% endblock %} | |
| 3 | +{% block content %} | |
| 4 | +<h1>Connexion</h1> | |
| 5 | +{% if error %}<p class="flash flash-error">Identifiants incorrects.</p>{% endif %} | |
| 6 | +<form method="post" action="/login" class="stack"> | |
| 7 | + <label for="username">Nom d'utilisateur</label> | |
| 8 | + <input id="username" name="username" type="text" autocomplete="username" | |
| 9 | + autocapitalize="none" autocorrect="off" required autofocus> | |
| 10 | + <label for="password">Mot de passe</label> | |
| 11 | + <input id="password" name="password" type="password" autocomplete="current-password" required> | |
| 12 | + <button type="submit" class="btn btn-primary">Se connecter</button> | |
| 13 | +</form> | |
| 14 | +{% endblock %} | |
| new file mode 100644 | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | +{% extends "base.html" %} | ||
| 2 | +{% block title %}Connexion · time-time{% endblock %} | ||
| 3 | +{% block content %} | ||
| 4 | +<h1>Connexion</h1> | ||
| 5 | +{% if error %}<p class="flash flash-error">Identifiants incorrects.</p>{% endif %} | ||
| 6 | +<form method="post" action="/login" class="stack"> | ||
| 7 | + <label for="username">Nom d'utilisateur</label> | ||
| 8 | + <input id="username" name="username" type="text" autocomplete="username" | ||
| 9 | + autocapitalize="none" autocorrect="off" required autofocus> | ||
| 10 | + <label for="password">Mot de passe</label> | ||
| 11 | + <input id="password" name="password" type="password" autocomplete="current-password" required> | ||
| 12 | + <button type="submit" class="btn btn-primary">Se connecter</button> | ||
| 13 | +</form> | ||
| 14 | +{% endblock %} | ||
added
templates/settings.html +50 -0 | new file mode 100644 | ||
| @@ -0,0 +1,50 @@ | ||
| 1 | +{% extends "base.html" %} | |
| 2 | +{% block title %}Paramètres · time-time{% endblock %} | |
| 3 | +{% block content %} | |
| 4 | +<h1>Paramètres</h1> | |
| 5 | +{% if let Some(n) = notice %}<p class="flash flash-ok">{{ n }}</p>{% endif %} | |
| 6 | +{% if let Some(e) = error %}<p class="flash flash-error">{{ e }}</p>{% endif %} | |
| 7 | + | |
| 8 | +<h2>Tâches</h2> | |
| 9 | +<ul class="task-list"> | |
| 10 | + {% for t in tasks %} | |
| 11 | + <li class="task {% if t.archived %}task-archived{% endif %}"> | |
| 12 | + <form method="post" action="/parametres/taches/{{ t.id }}" class="task-rename"> | |
| 13 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | |
| 14 | + <input name="name" value="{{ t.name }}" required aria-label="Nom de la tâche"> | |
| 15 | + <button type="submit" class="btn btn-small">Renommer</button> | |
| 16 | + </form> | |
| 17 | + <form method="post" action="/parametres/taches/{{ t.id }}/archiver"> | |
| 18 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | |
| 19 | + <button type="submit" class="btn btn-small"> | |
| 20 | + {% if t.archived %}Restaurer{% else %}Archiver{% endif %} | |
| 21 | + </button> | |
| 22 | + </form> | |
| 23 | + </li> | |
| 24 | + {% endfor %} | |
| 25 | +</ul> | |
| 26 | +<form method="post" action="/parametres/taches" class="task-add"> | |
| 27 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | |
| 28 | + <input name="name" placeholder="Nouvelle tâche" required aria-label="Nouvelle tâche"> | |
| 29 | + <button type="submit" class="btn btn-primary">Créer</button> | |
| 30 | +</form> | |
| 31 | +<p class="hint">Archiver une tâche la retire du formulaire d'ajout ; les temps déjà saisis restent dans les statistiques.</p> | |
| 32 | + | |
| 33 | +<h2>Mot de passe</h2> | |
| 34 | +<form method="post" action="/parametres/mot-de-passe" class="stack"> | |
| 35 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | |
| 36 | + <label for="ancien">Ancien mot de passe</label> | |
| 37 | + <input id="ancien" name="ancien" type="password" autocomplete="current-password" required> | |
| 38 | + <label for="nouveau">Nouveau mot de passe</label> | |
| 39 | + <input id="nouveau" name="nouveau" type="password" autocomplete="new-password" required minlength="8"> | |
| 40 | + <label for="confirmation">Confirmation</label> | |
| 41 | + <input id="confirmation" name="confirmation" type="password" autocomplete="new-password" required minlength="8"> | |
| 42 | + <button type="submit" class="btn btn-primary">Changer le mot de passe</button> | |
| 43 | +</form> | |
| 44 | + | |
| 45 | +<h2>Session</h2> | |
| 46 | +<form method="post" action="/logout"> | |
| 47 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | |
| 48 | + <button type="submit" class="btn btn-danger">Se déconnecter</button> | |
| 49 | +</form> | |
| 50 | +{% endblock %} | |
| new file mode 100644 | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | +{% extends "base.html" %} | ||
| 2 | +{% block title %}Paramètres · time-time{% endblock %} | ||
| 3 | +{% block content %} | ||
| 4 | +<h1>Paramètres</h1> | ||
| 5 | +{% if let Some(n) = notice %}<p class="flash flash-ok">{{ n }}</p>{% endif %} | ||
| 6 | +{% if let Some(e) = error %}<p class="flash flash-error">{{ e }}</p>{% endif %} | ||
| 7 | + | ||
| 8 | +<h2>Tâches</h2> | ||
| 9 | +<ul class="task-list"> | ||
| 10 | + {% for t in tasks %} | ||
| 11 | + <li class="task {% if t.archived %}task-archived{% endif %}"> | ||
| 12 | + <form method="post" action="/parametres/taches/{{ t.id }}" class="task-rename"> | ||
| 13 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | ||
| 14 | + <input name="name" value="{{ t.name }}" required aria-label="Nom de la tâche"> | ||
| 15 | + <button type="submit" class="btn btn-small">Renommer</button> | ||
| 16 | + </form> | ||
| 17 | + <form method="post" action="/parametres/taches/{{ t.id }}/archiver"> | ||
| 18 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | ||
| 19 | + <button type="submit" class="btn btn-small"> | ||
| 20 | + {% if t.archived %}Restaurer{% else %}Archiver{% endif %} | ||
| 21 | + </button> | ||
| 22 | + </form> | ||
| 23 | + </li> | ||
| 24 | + {% endfor %} | ||
| 25 | +</ul> | ||
| 26 | +<form method="post" action="/parametres/taches" class="task-add"> | ||
| 27 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | ||
| 28 | + <input name="name" placeholder="Nouvelle tâche" required aria-label="Nouvelle tâche"> | ||
| 29 | + <button type="submit" class="btn btn-primary">Créer</button> | ||
| 30 | +</form> | ||
| 31 | +<p class="hint">Archiver une tâche la retire du formulaire d'ajout ; les temps déjà saisis restent dans les statistiques.</p> | ||
| 32 | + | ||
| 33 | +<h2>Mot de passe</h2> | ||
| 34 | +<form method="post" action="/parametres/mot-de-passe" class="stack"> | ||
| 35 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | ||
| 36 | + <label for="ancien">Ancien mot de passe</label> | ||
| 37 | + <input id="ancien" name="ancien" type="password" autocomplete="current-password" required> | ||
| 38 | + <label for="nouveau">Nouveau mot de passe</label> | ||
| 39 | + <input id="nouveau" name="nouveau" type="password" autocomplete="new-password" required minlength="8"> | ||
| 40 | + <label for="confirmation">Confirmation</label> | ||
| 41 | + <input id="confirmation" name="confirmation" type="password" autocomplete="new-password" required minlength="8"> | ||
| 42 | + <button type="submit" class="btn btn-primary">Changer le mot de passe</button> | ||
| 43 | +</form> | ||
| 44 | + | ||
| 45 | +<h2>Session</h2> | ||
| 46 | +<form method="post" action="/logout"> | ||
| 47 | + <input type="hidden" name="csrf" value="{{ csrf }}"> | ||
| 48 | + <button type="submit" class="btn btn-danger">Se déconnecter</button> | ||
| 49 | +</form> | ||
| 50 | +{% endblock %} | ||
added
templates/stats.html +42 -0 | new file mode 100644 | ||
| @@ -0,0 +1,42 @@ | ||
| 1 | +{% extends "base.html" %} | |
| 2 | +{% block title %}Statistiques · time-time{% endblock %} | |
| 3 | +{% block content %} | |
| 4 | +<h1>Statistiques</h1> | |
| 5 | + | |
| 6 | +{% if !legend.is_empty() %} | |
| 7 | +<ul class="legend"> | |
| 8 | + {% for item in legend %} | |
| 9 | + <li><span class="swatch" style="background:{{ item.color }}"></span>{{ item.name }}</li> | |
| 10 | + {% endfor %} | |
| 11 | +</ul> | |
| 12 | +{% endif %} | |
| 13 | + | |
| 14 | +<section class="chart-block"> | |
| 15 | + <div class="chart-head"> | |
| 16 | + <a class="btn btn-nav" href="{{ week_prev }}" aria-label="Semaine précédente">←</a> | |
| 17 | + <h2>{{ week_chart.title }}</h2> | |
| 18 | + {% if let Some(next) = week_next %} | |
| 19 | + <a class="btn btn-nav" href="{{ next }}" aria-label="Semaine suivante">→</a> | |
| 20 | + {% else %}<span class="btn-nav-placeholder"></span>{% endif %} | |
| 21 | + </div> | |
| 22 | + {% let chart = week_chart %}{% include "chart.svg.html" %} | |
| 23 | +</section> | |
| 24 | + | |
| 25 | +<section class="chart-block"> | |
| 26 | + <div class="chart-head"> | |
| 27 | + <a class="btn btn-nav" href="{{ month_prev }}" aria-label="Mois précédent">←</a> | |
| 28 | + <h2>{{ month_chart.title }}</h2> | |
| 29 | + {% if let Some(next) = month_next %} | |
| 30 | + <a class="btn btn-nav" href="{{ next }}" aria-label="Mois suivant">→</a> | |
| 31 | + {% else %}<span class="btn-nav-placeholder"></span>{% endif %} | |
| 32 | + </div> | |
| 33 | + {% let chart = month_chart %}{% include "chart.svg.html" %} | |
| 34 | +</section> | |
| 35 | + | |
| 36 | +<section class="chart-block"> | |
| 37 | + <div class="chart-head"><h2>{{ total_chart.title }}</h2></div> | |
| 38 | + {% let chart = total_chart %}{% include "chart.svg.html" %} | |
| 39 | +</section> | |
| 40 | + | |
| 41 | +<p><a class="btn btn-big" href="/temps">✏️ Modifier / supprimer mes temps</a></p> | |
| 42 | +{% endblock %} | |
| new file mode 100644 | |||
| @@ -0,0 +1,42 @@ | |||
| 1 | +{% extends "base.html" %} | ||
| 2 | +{% block title %}Statistiques · time-time{% endblock %} | ||
| 3 | +{% block content %} | ||
| 4 | +<h1>Statistiques</h1> | ||
| 5 | + | ||
| 6 | +{% if !legend.is_empty() %} | ||
| 7 | +<ul class="legend"> | ||
| 8 | + {% for item in legend %} | ||
| 9 | + <li><span class="swatch" style="background:{{ item.color }}"></span>{{ item.name }}</li> | ||
| 10 | + {% endfor %} | ||
| 11 | +</ul> | ||
| 12 | +{% endif %} | ||
| 13 | + | ||
| 14 | +<section class="chart-block"> | ||
| 15 | + <div class="chart-head"> | ||
| 16 | + <a class="btn btn-nav" href="{{ week_prev }}" aria-label="Semaine précédente">←</a> | ||
| 17 | + <h2>{{ week_chart.title }}</h2> | ||
| 18 | + {% if let Some(next) = week_next %} | ||
| 19 | + <a class="btn btn-nav" href="{{ next }}" aria-label="Semaine suivante">→</a> | ||
| 20 | + {% else %}<span class="btn-nav-placeholder"></span>{% endif %} | ||
| 21 | + </div> | ||
| 22 | + {% let chart = week_chart %}{% include "chart.svg.html" %} | ||
| 23 | +</section> | ||
| 24 | + | ||
| 25 | +<section class="chart-block"> | ||
| 26 | + <div class="chart-head"> | ||
| 27 | + <a class="btn btn-nav" href="{{ month_prev }}" aria-label="Mois précédent">←</a> | ||
| 28 | + <h2>{{ month_chart.title }}</h2> | ||
| 29 | + {% if let Some(next) = month_next %} | ||
| 30 | + <a class="btn btn-nav" href="{{ next }}" aria-label="Mois suivant">→</a> | ||
| 31 | + {% else %}<span class="btn-nav-placeholder"></span>{% endif %} | ||
| 32 | + </div> | ||
| 33 | + {% let chart = month_chart %}{% include "chart.svg.html" %} | ||
| 34 | +</section> | ||
| 35 | + | ||
| 36 | +<section class="chart-block"> | ||
| 37 | + <div class="chart-head"><h2>{{ total_chart.title }}</h2></div> | ||
| 38 | + {% let chart = total_chart %}{% include "chart.svg.html" %} | ||
| 39 | +</section> | ||
| 40 | + | ||
| 41 | +<p><a class="btn btn-big" href="/temps">✏️ Modifier / supprimer mes temps</a></p> | ||
| 42 | +{% endblock %} | ||