this repo has no description

Compare changes

Choose any two refs to compare.

+7
.env.template
··· 1 + DB_USER=postgres 2 + DB_PASSWORD=supersecurepassword123987 3 + DB_NAME=advent 4 + DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@localhost:5432/${DB_NAME}" 5 + DOCKER_DB_URL="postgresql://${DB_USER}:${DB_PASSWORD}@host.docker.internal:5432/${DB_NAME}" 6 + REDIS_URL=redis://127.0.0.1:6379/ 7 + PROD=true
+7
.gitignore
··· 1 1 /target 2 + .idea 3 + .env 4 + at-advent.db 5 + at-advent.db-shm 6 + at-advent.db-wal 7 + 8 + .DS_Store
+3
.tangled/workflows/build.yml
··· 8 8 - cargo 9 9 - rustc 10 10 - rustfmt 11 + - pkg-config 12 + - openssl 11 13 12 14 steps: 13 15 - name: build 14 16 command: | 17 + export PKG_CONFIG_PATH="${PKG_CONFIG_PATH:+$PKG_CONFIG_PATH:}$(nix build nixpkgs#openssl.dev --no-link --print-out-paths)/lib/pkgconfig" 15 18 cargo build --all 16 19
+3461 -21
Cargo.lock
··· 18 18 checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 19 19 20 20 [[package]] 21 - name = "at-advent" 22 - version = "0.1.0" 21 + name = "aho-corasick" 22 + version = "1.1.3" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 + dependencies = [ 26 + "memchr", 27 + ] 28 + 29 + [[package]] 30 + name = "allocator-api2" 31 + version = "0.2.21" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 34 + 35 + [[package]] 36 + name = "android-tzdata" 37 + version = "0.1.1" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 40 + 41 + [[package]] 42 + name = "android_system_properties" 43 + version = "0.1.5" 44 + source = "registry+https://github.com/rust-lang/crates.io-index" 45 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 46 + dependencies = [ 47 + "libc", 48 + ] 49 + 50 + [[package]] 51 + name = "askama" 52 + version = "0.14.0" 53 + source = "registry+https://github.com/rust-lang/crates.io-index" 54 + checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" 23 55 dependencies = [ 24 - "axum", 56 + "askama_derive", 57 + "itoa", 58 + "percent-encoding", 59 + "serde", 25 60 "serde_json", 61 + ] 62 + 63 + [[package]] 64 + name = "askama_derive" 65 + version = "0.14.0" 66 + source = "registry+https://github.com/rust-lang/crates.io-index" 67 + checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f" 68 + dependencies = [ 69 + "askama_parser", 70 + "basic-toml", 71 + "memchr", 72 + "proc-macro2", 73 + "quote", 74 + "rustc-hash", 75 + "serde", 76 + "serde_derive", 77 + "syn", 78 + ] 79 + 80 + [[package]] 81 + name = "askama_parser" 82 + version = "0.14.0" 83 + source = "registry+https://github.com/rust-lang/crates.io-index" 84 + checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358" 85 + dependencies = [ 86 + "memchr", 87 + "serde", 88 + "serde_derive", 89 + "winnow", 90 + ] 91 + 92 + [[package]] 93 + name = "async-compression" 94 + version = "0.4.27" 95 + source = "registry+https://github.com/rust-lang/crates.io-index" 96 + checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" 97 + dependencies = [ 98 + "flate2", 99 + "futures-core", 100 + "memchr", 101 + "pin-project-lite", 26 102 "tokio", 27 103 ] 28 104 29 105 [[package]] 106 + name = "async-lock" 107 + version = "3.4.0" 108 + source = "registry+https://github.com/rust-lang/crates.io-index" 109 + checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" 110 + dependencies = [ 111 + "event-listener", 112 + "event-listener-strategy", 113 + "pin-project-lite", 114 + ] 115 + 116 + [[package]] 117 + name = "async-trait" 118 + version = "0.1.88" 119 + source = "registry+https://github.com/rust-lang/crates.io-index" 120 + checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" 121 + dependencies = [ 122 + "proc-macro2", 123 + "quote", 124 + "syn", 125 + ] 126 + 127 + [[package]] 128 + name = "atoi" 129 + version = "2.0.0" 130 + source = "registry+https://github.com/rust-lang/crates.io-index" 131 + checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" 132 + dependencies = [ 133 + "num-traits", 134 + ] 135 + 136 + [[package]] 137 + name = "atrium-api" 138 + version = "0.25.4" 139 + source = "registry+https://github.com/rust-lang/crates.io-index" 140 + checksum = "46355d3245edc7b3160b2a45fe55d09a6963ebd3eee0252feb6b72fb0eb71463" 141 + dependencies = [ 142 + "atrium-common", 143 + "atrium-xrpc", 144 + "chrono", 145 + "http", 146 + "ipld-core", 147 + "langtag", 148 + "regex", 149 + "serde", 150 + "serde_bytes", 151 + "serde_json", 152 + "thiserror 1.0.69", 153 + "tokio", 154 + "trait-variant", 155 + ] 156 + 157 + [[package]] 158 + name = "atrium-common" 159 + version = "0.1.2" 160 + source = "registry+https://github.com/rust-lang/crates.io-index" 161 + checksum = "9ed5610654043faa396a5a15afac0ac646d76aebe45aebd7cef4f8b96b0ab7f4" 162 + dependencies = [ 163 + "dashmap", 164 + "lru", 165 + "moka", 166 + "thiserror 1.0.69", 167 + "tokio", 168 + "trait-variant", 169 + "web-time", 170 + ] 171 + 172 + [[package]] 173 + name = "atrium-identity" 174 + version = "0.1.5" 175 + source = "registry+https://github.com/rust-lang/crates.io-index" 176 + checksum = "c9e2d42bb4dbea038f4f5f45e3af2a89d61a9894a75f06aa550b74a60d2be380" 177 + dependencies = [ 178 + "atrium-api", 179 + "atrium-common", 180 + "atrium-xrpc", 181 + "serde", 182 + "serde_html_form", 183 + "serde_json", 184 + "thiserror 1.0.69", 185 + "trait-variant", 186 + ] 187 + 188 + [[package]] 189 + name = "atrium-oauth" 190 + version = "0.1.3" 191 + source = "registry+https://github.com/rust-lang/crates.io-index" 192 + checksum = "ca22dc4eaf77fd9bf050b21192ac58cd654a437d28e000ec114ebd93a51d36f5" 193 + dependencies = [ 194 + "atrium-api", 195 + "atrium-common", 196 + "atrium-identity", 197 + "atrium-xrpc", 198 + "base64", 199 + "chrono", 200 + "dashmap", 201 + "ecdsa", 202 + "elliptic-curve", 203 + "jose-jwa", 204 + "jose-jwk", 205 + "p256", 206 + "rand 0.8.5", 207 + "reqwest", 208 + "serde", 209 + "serde_html_form", 210 + "serde_json", 211 + "sha2", 212 + "thiserror 1.0.69", 213 + "tokio", 214 + "trait-variant", 215 + ] 216 + 217 + [[package]] 218 + name = "atrium-xrpc" 219 + version = "0.12.3" 220 + source = "registry+https://github.com/rust-lang/crates.io-index" 221 + checksum = "0216ad50ce34e9ff982e171c3659e65dedaa2ed5ac2994524debdc9a9647ffa8" 222 + dependencies = [ 223 + "http", 224 + "serde", 225 + "serde_html_form", 226 + "serde_json", 227 + "thiserror 1.0.69", 228 + "trait-variant", 229 + ] 230 + 231 + [[package]] 30 232 name = "autocfg" 31 233 version = "1.5.0" 32 234 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 98 300 "miniz_oxide", 99 301 "object", 100 302 "rustc-demangle", 101 - "windows-targets", 303 + "windows-targets 0.52.6", 304 + ] 305 + 306 + [[package]] 307 + name = "base-x" 308 + version = "0.2.11" 309 + source = "registry+https://github.com/rust-lang/crates.io-index" 310 + checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 311 + 312 + [[package]] 313 + name = "base16ct" 314 + version = "0.2.0" 315 + source = "registry+https://github.com/rust-lang/crates.io-index" 316 + checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 317 + 318 + [[package]] 319 + name = "base64" 320 + version = "0.22.1" 321 + source = "registry+https://github.com/rust-lang/crates.io-index" 322 + checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 323 + 324 + [[package]] 325 + name = "base64ct" 326 + version = "1.8.0" 327 + source = "registry+https://github.com/rust-lang/crates.io-index" 328 + checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" 329 + 330 + [[package]] 331 + name = "basic-toml" 332 + version = "0.1.10" 333 + source = "registry+https://github.com/rust-lang/crates.io-index" 334 + checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" 335 + dependencies = [ 336 + "serde", 337 + ] 338 + 339 + [[package]] 340 + name = "bb8" 341 + version = "0.9.0" 342 + source = "registry+https://github.com/rust-lang/crates.io-index" 343 + checksum = "212d8b8e1a22743d9241575c6ba822cf9c8fef34771c86ab7e477a4fbfd254e5" 344 + dependencies = [ 345 + "futures-util", 346 + "parking_lot", 347 + "tokio", 348 + ] 349 + 350 + [[package]] 351 + name = "bb8-redis" 352 + version = "0.24.0" 353 + source = "registry+https://github.com/rust-lang/crates.io-index" 354 + checksum = "5143936af5e1eea1a881e3e3d21b6777da6315e5e307bc3d0c2301c44fa37da9" 355 + dependencies = [ 356 + "bb8", 357 + "redis", 102 358 ] 103 359 104 360 [[package]] ··· 106 362 version = "2.9.1" 107 363 source = "registry+https://github.com/rust-lang/crates.io-index" 108 364 checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 365 + dependencies = [ 366 + "serde", 367 + ] 368 + 369 + [[package]] 370 + name = "block-buffer" 371 + version = "0.10.4" 372 + source = "registry+https://github.com/rust-lang/crates.io-index" 373 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 374 + dependencies = [ 375 + "generic-array", 376 + ] 377 + 378 + [[package]] 379 + name = "bstr" 380 + version = "1.12.0" 381 + source = "registry+https://github.com/rust-lang/crates.io-index" 382 + checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" 383 + dependencies = [ 384 + "memchr", 385 + "serde", 386 + ] 387 + 388 + [[package]] 389 + name = "bumpalo" 390 + version = "3.17.0" 391 + source = "registry+https://github.com/rust-lang/crates.io-index" 392 + checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 393 + 394 + [[package]] 395 + name = "byteorder" 396 + version = "1.5.0" 397 + source = "registry+https://github.com/rust-lang/crates.io-index" 398 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 109 399 110 400 [[package]] 111 401 name = "bytes" ··· 114 404 checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 115 405 116 406 [[package]] 407 + name = "cc" 408 + version = "1.2.30" 409 + source = "registry+https://github.com/rust-lang/crates.io-index" 410 + checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" 411 + dependencies = [ 412 + "shlex", 413 + ] 414 + 415 + [[package]] 117 416 name = "cfg-if" 118 417 version = "1.0.1" 119 418 source = "registry+https://github.com/rust-lang/crates.io-index" 120 419 checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 121 420 122 421 [[package]] 422 + name = "chrono" 423 + version = "0.4.41" 424 + source = "registry+https://github.com/rust-lang/crates.io-index" 425 + checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 426 + dependencies = [ 427 + "android-tzdata", 428 + "iana-time-zone", 429 + "js-sys", 430 + "num-traits", 431 + "serde", 432 + "wasm-bindgen", 433 + "windows-link", 434 + ] 435 + 436 + [[package]] 437 + name = "cid" 438 + version = "0.11.1" 439 + source = "registry+https://github.com/rust-lang/crates.io-index" 440 + checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a" 441 + dependencies = [ 442 + "core2", 443 + "multibase", 444 + "multihash", 445 + "serde", 446 + "serde_bytes", 447 + "unsigned-varint", 448 + ] 449 + 450 + [[package]] 451 + name = "combine" 452 + version = "4.6.7" 453 + source = "registry+https://github.com/rust-lang/crates.io-index" 454 + checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 455 + dependencies = [ 456 + "bytes", 457 + "futures-core", 458 + "memchr", 459 + "pin-project-lite", 460 + "tokio", 461 + "tokio-util", 462 + ] 463 + 464 + [[package]] 465 + name = "concurrent-queue" 466 + version = "2.5.0" 467 + source = "registry+https://github.com/rust-lang/crates.io-index" 468 + checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 469 + dependencies = [ 470 + "crossbeam-utils", 471 + ] 472 + 473 + [[package]] 474 + name = "const-oid" 475 + version = "0.9.6" 476 + source = "registry+https://github.com/rust-lang/crates.io-index" 477 + checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 478 + 479 + [[package]] 480 + name = "cookie" 481 + version = "0.18.1" 482 + source = "registry+https://github.com/rust-lang/crates.io-index" 483 + checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" 484 + dependencies = [ 485 + "percent-encoding", 486 + "time", 487 + "version_check", 488 + ] 489 + 490 + [[package]] 491 + name = "core-foundation" 492 + version = "0.9.4" 493 + source = "registry+https://github.com/rust-lang/crates.io-index" 494 + checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 495 + dependencies = [ 496 + "core-foundation-sys", 497 + "libc", 498 + ] 499 + 500 + [[package]] 501 + name = "core-foundation-sys" 502 + version = "0.8.7" 503 + source = "registry+https://github.com/rust-lang/crates.io-index" 504 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 505 + 506 + [[package]] 507 + name = "core2" 508 + version = "0.4.0" 509 + source = "registry+https://github.com/rust-lang/crates.io-index" 510 + checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 511 + dependencies = [ 512 + "memchr", 513 + ] 514 + 515 + [[package]] 516 + name = "cpufeatures" 517 + version = "0.2.17" 518 + source = "registry+https://github.com/rust-lang/crates.io-index" 519 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 520 + dependencies = [ 521 + "libc", 522 + ] 523 + 524 + [[package]] 525 + name = "crc" 526 + version = "3.3.0" 527 + source = "registry+https://github.com/rust-lang/crates.io-index" 528 + checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" 529 + dependencies = [ 530 + "crc-catalog", 531 + ] 532 + 533 + [[package]] 534 + name = "crc-catalog" 535 + version = "2.4.0" 536 + source = "registry+https://github.com/rust-lang/crates.io-index" 537 + checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 538 + 539 + [[package]] 540 + name = "crc32fast" 541 + version = "1.5.0" 542 + source = "registry+https://github.com/rust-lang/crates.io-index" 543 + checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 544 + dependencies = [ 545 + "cfg-if", 546 + ] 547 + 548 + [[package]] 549 + name = "crossbeam-channel" 550 + version = "0.5.15" 551 + source = "registry+https://github.com/rust-lang/crates.io-index" 552 + checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 553 + dependencies = [ 554 + "crossbeam-utils", 555 + ] 556 + 557 + [[package]] 558 + name = "crossbeam-epoch" 559 + version = "0.9.18" 560 + source = "registry+https://github.com/rust-lang/crates.io-index" 561 + checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 562 + dependencies = [ 563 + "crossbeam-utils", 564 + ] 565 + 566 + [[package]] 567 + name = "crossbeam-queue" 568 + version = "0.3.12" 569 + source = "registry+https://github.com/rust-lang/crates.io-index" 570 + checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" 571 + dependencies = [ 572 + "crossbeam-utils", 573 + ] 574 + 575 + [[package]] 576 + name = "crossbeam-utils" 577 + version = "0.8.21" 578 + source = "registry+https://github.com/rust-lang/crates.io-index" 579 + checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 580 + 581 + [[package]] 582 + name = "crypto-bigint" 583 + version = "0.5.5" 584 + source = "registry+https://github.com/rust-lang/crates.io-index" 585 + checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" 586 + dependencies = [ 587 + "generic-array", 588 + "rand_core 0.6.4", 589 + "subtle", 590 + "zeroize", 591 + ] 592 + 593 + [[package]] 594 + name = "crypto-common" 595 + version = "0.1.6" 596 + source = "registry+https://github.com/rust-lang/crates.io-index" 597 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 598 + dependencies = [ 599 + "generic-array", 600 + "typenum", 601 + ] 602 + 603 + [[package]] 604 + name = "darling" 605 + version = "0.20.11" 606 + source = "registry+https://github.com/rust-lang/crates.io-index" 607 + checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 608 + dependencies = [ 609 + "darling_core", 610 + "darling_macro", 611 + ] 612 + 613 + [[package]] 614 + name = "darling_core" 615 + version = "0.20.11" 616 + source = "registry+https://github.com/rust-lang/crates.io-index" 617 + checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 618 + dependencies = [ 619 + "fnv", 620 + "ident_case", 621 + "proc-macro2", 622 + "quote", 623 + "strsim", 624 + "syn", 625 + ] 626 + 627 + [[package]] 628 + name = "darling_macro" 629 + version = "0.20.11" 630 + source = "registry+https://github.com/rust-lang/crates.io-index" 631 + checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 632 + dependencies = [ 633 + "darling_core", 634 + "quote", 635 + "syn", 636 + ] 637 + 638 + [[package]] 639 + name = "dashmap" 640 + version = "6.1.0" 641 + source = "registry+https://github.com/rust-lang/crates.io-index" 642 + checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" 643 + dependencies = [ 644 + "cfg-if", 645 + "crossbeam-utils", 646 + "hashbrown 0.14.5", 647 + "lock_api", 648 + "once_cell", 649 + "parking_lot_core", 650 + ] 651 + 652 + [[package]] 653 + name = "data-encoding" 654 + version = "2.9.0" 655 + source = "registry+https://github.com/rust-lang/crates.io-index" 656 + checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 657 + 658 + [[package]] 659 + name = "data-encoding-macro" 660 + version = "0.1.18" 661 + source = "registry+https://github.com/rust-lang/crates.io-index" 662 + checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" 663 + dependencies = [ 664 + "data-encoding", 665 + "data-encoding-macro-internal", 666 + ] 667 + 668 + [[package]] 669 + name = "data-encoding-macro-internal" 670 + version = "0.1.16" 671 + source = "registry+https://github.com/rust-lang/crates.io-index" 672 + checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" 673 + dependencies = [ 674 + "data-encoding", 675 + "syn", 676 + ] 677 + 678 + [[package]] 679 + name = "der" 680 + version = "0.7.10" 681 + source = "registry+https://github.com/rust-lang/crates.io-index" 682 + checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" 683 + dependencies = [ 684 + "const-oid", 685 + "pem-rfc7468", 686 + "zeroize", 687 + ] 688 + 689 + [[package]] 690 + name = "deranged" 691 + version = "0.4.0" 692 + source = "registry+https://github.com/rust-lang/crates.io-index" 693 + checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" 694 + dependencies = [ 695 + "powerfmt", 696 + "serde", 697 + ] 698 + 699 + [[package]] 700 + name = "derive_builder" 701 + version = "0.20.2" 702 + source = "registry+https://github.com/rust-lang/crates.io-index" 703 + checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" 704 + dependencies = [ 705 + "derive_builder_macro", 706 + ] 707 + 708 + [[package]] 709 + name = "derive_builder_core" 710 + version = "0.20.2" 711 + source = "registry+https://github.com/rust-lang/crates.io-index" 712 + checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" 713 + dependencies = [ 714 + "darling", 715 + "proc-macro2", 716 + "quote", 717 + "syn", 718 + ] 719 + 720 + [[package]] 721 + name = "derive_builder_macro" 722 + version = "0.20.2" 723 + source = "registry+https://github.com/rust-lang/crates.io-index" 724 + checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" 725 + dependencies = [ 726 + "derive_builder_core", 727 + "syn", 728 + ] 729 + 730 + [[package]] 731 + name = "digest" 732 + version = "0.10.7" 733 + source = "registry+https://github.com/rust-lang/crates.io-index" 734 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 735 + dependencies = [ 736 + "block-buffer", 737 + "const-oid", 738 + "crypto-common", 739 + "subtle", 740 + ] 741 + 742 + [[package]] 743 + name = "displaydoc" 744 + version = "0.2.5" 745 + source = "registry+https://github.com/rust-lang/crates.io-index" 746 + checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 747 + dependencies = [ 748 + "proc-macro2", 749 + "quote", 750 + "syn", 751 + ] 752 + 753 + [[package]] 754 + name = "dotenv" 755 + version = "0.15.0" 756 + source = "registry+https://github.com/rust-lang/crates.io-index" 757 + checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 758 + 759 + [[package]] 760 + name = "dotenvy" 761 + version = "0.15.7" 762 + source = "registry+https://github.com/rust-lang/crates.io-index" 763 + checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 764 + 765 + [[package]] 766 + name = "ecdsa" 767 + version = "0.16.9" 768 + source = "registry+https://github.com/rust-lang/crates.io-index" 769 + checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" 770 + dependencies = [ 771 + "der", 772 + "digest", 773 + "elliptic-curve", 774 + "rfc6979", 775 + "signature", 776 + ] 777 + 778 + [[package]] 779 + name = "either" 780 + version = "1.15.0" 781 + source = "registry+https://github.com/rust-lang/crates.io-index" 782 + checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 783 + dependencies = [ 784 + "serde", 785 + ] 786 + 787 + [[package]] 788 + name = "elliptic-curve" 789 + version = "0.13.8" 790 + source = "registry+https://github.com/rust-lang/crates.io-index" 791 + checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 792 + dependencies = [ 793 + "base16ct", 794 + "crypto-bigint", 795 + "digest", 796 + "ff", 797 + "generic-array", 798 + "group", 799 + "rand_core 0.6.4", 800 + "sec1", 801 + "subtle", 802 + "zeroize", 803 + ] 804 + 805 + [[package]] 806 + name = "enum-as-inner" 807 + version = "0.6.1" 808 + source = "registry+https://github.com/rust-lang/crates.io-index" 809 + checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" 810 + dependencies = [ 811 + "heck", 812 + "proc-macro2", 813 + "quote", 814 + "syn", 815 + ] 816 + 817 + [[package]] 818 + name = "equivalent" 819 + version = "1.0.2" 820 + source = "registry+https://github.com/rust-lang/crates.io-index" 821 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 822 + 823 + [[package]] 824 + name = "errno" 825 + version = "0.3.13" 826 + source = "registry+https://github.com/rust-lang/crates.io-index" 827 + checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" 828 + dependencies = [ 829 + "libc", 830 + "windows-sys 0.59.0", 831 + ] 832 + 833 + [[package]] 834 + name = "etcetera" 835 + version = "0.8.0" 836 + source = "registry+https://github.com/rust-lang/crates.io-index" 837 + checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" 838 + dependencies = [ 839 + "cfg-if", 840 + "home", 841 + "windows-sys 0.48.0", 842 + ] 843 + 844 + [[package]] 845 + name = "event-listener" 846 + version = "5.4.0" 847 + source = "registry+https://github.com/rust-lang/crates.io-index" 848 + checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" 849 + dependencies = [ 850 + "concurrent-queue", 851 + "parking", 852 + "pin-project-lite", 853 + ] 854 + 855 + [[package]] 856 + name = "event-listener-strategy" 857 + version = "0.5.4" 858 + source = "registry+https://github.com/rust-lang/crates.io-index" 859 + checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" 860 + dependencies = [ 861 + "event-listener", 862 + "pin-project-lite", 863 + ] 864 + 865 + [[package]] 866 + name = "fastrand" 867 + version = "2.3.0" 868 + source = "registry+https://github.com/rust-lang/crates.io-index" 869 + checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 870 + 871 + [[package]] 872 + name = "ff" 873 + version = "0.13.1" 874 + source = "registry+https://github.com/rust-lang/crates.io-index" 875 + checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" 876 + dependencies = [ 877 + "rand_core 0.6.4", 878 + "subtle", 879 + ] 880 + 881 + [[package]] 882 + name = "flate2" 883 + version = "1.1.2" 884 + source = "registry+https://github.com/rust-lang/crates.io-index" 885 + checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" 886 + dependencies = [ 887 + "crc32fast", 888 + "miniz_oxide", 889 + ] 890 + 891 + [[package]] 892 + name = "flume" 893 + version = "0.11.1" 894 + source = "registry+https://github.com/rust-lang/crates.io-index" 895 + checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" 896 + dependencies = [ 897 + "futures-core", 898 + "futures-sink", 899 + "spin", 900 + ] 901 + 902 + [[package]] 123 903 name = "fnv" 124 904 version = "1.0.7" 125 905 source = "registry+https://github.com/rust-lang/crates.io-index" 126 906 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 127 907 128 908 [[package]] 909 + name = "foldhash" 910 + version = "0.1.5" 911 + source = "registry+https://github.com/rust-lang/crates.io-index" 912 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 913 + 914 + [[package]] 915 + name = "foreign-types" 916 + version = "0.3.2" 917 + source = "registry+https://github.com/rust-lang/crates.io-index" 918 + checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 919 + dependencies = [ 920 + "foreign-types-shared", 921 + ] 922 + 923 + [[package]] 924 + name = "foreign-types-shared" 925 + version = "0.1.1" 926 + source = "registry+https://github.com/rust-lang/crates.io-index" 927 + checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 928 + 929 + [[package]] 129 930 name = "form_urlencoded" 130 931 version = "1.2.1" 131 932 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 135 936 ] 136 937 137 938 [[package]] 939 + name = "futures" 940 + version = "0.3.31" 941 + source = "registry+https://github.com/rust-lang/crates.io-index" 942 + checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 943 + dependencies = [ 944 + "futures-channel", 945 + "futures-core", 946 + "futures-io", 947 + "futures-sink", 948 + "futures-task", 949 + "futures-util", 950 + ] 951 + 952 + [[package]] 138 953 name = "futures-channel" 139 954 version = "0.3.31" 140 955 source = "registry+https://github.com/rust-lang/crates.io-index" 141 956 checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 142 957 dependencies = [ 143 958 "futures-core", 959 + "futures-sink", 144 960 ] 145 961 146 962 [[package]] ··· 150 966 checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 151 967 152 968 [[package]] 969 + name = "futures-executor" 970 + version = "0.3.31" 971 + source = "registry+https://github.com/rust-lang/crates.io-index" 972 + checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 973 + dependencies = [ 974 + "futures-core", 975 + "futures-task", 976 + "futures-util", 977 + ] 978 + 979 + [[package]] 980 + name = "futures-intrusive" 981 + version = "0.5.0" 982 + source = "registry+https://github.com/rust-lang/crates.io-index" 983 + checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" 984 + dependencies = [ 985 + "futures-core", 986 + "lock_api", 987 + "parking_lot", 988 + ] 989 + 990 + [[package]] 991 + name = "futures-io" 992 + version = "0.3.31" 993 + source = "registry+https://github.com/rust-lang/crates.io-index" 994 + checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 995 + 996 + [[package]] 997 + name = "futures-macro" 998 + version = "0.3.31" 999 + source = "registry+https://github.com/rust-lang/crates.io-index" 1000 + checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 1001 + dependencies = [ 1002 + "proc-macro2", 1003 + "quote", 1004 + "syn", 1005 + ] 1006 + 1007 + [[package]] 1008 + name = "futures-sink" 1009 + version = "0.3.31" 1010 + source = "registry+https://github.com/rust-lang/crates.io-index" 1011 + checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 1012 + 1013 + [[package]] 153 1014 name = "futures-task" 154 1015 version = "0.3.31" 155 1016 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 162 1023 checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 163 1024 dependencies = [ 164 1025 "futures-core", 1026 + "futures-io", 1027 + "futures-macro", 1028 + "futures-sink", 165 1029 "futures-task", 1030 + "memchr", 166 1031 "pin-project-lite", 167 1032 "pin-utils", 1033 + "slab", 1034 + ] 1035 + 1036 + [[package]] 1037 + name = "generator" 1038 + version = "0.8.5" 1039 + source = "registry+https://github.com/rust-lang/crates.io-index" 1040 + checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" 1041 + dependencies = [ 1042 + "cc", 1043 + "cfg-if", 1044 + "libc", 1045 + "log", 1046 + "rustversion", 1047 + "windows", 1048 + ] 1049 + 1050 + [[package]] 1051 + name = "generic-array" 1052 + version = "0.14.7" 1053 + source = "registry+https://github.com/rust-lang/crates.io-index" 1054 + checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 1055 + dependencies = [ 1056 + "typenum", 1057 + "version_check", 1058 + "zeroize", 1059 + ] 1060 + 1061 + [[package]] 1062 + name = "getrandom" 1063 + version = "0.2.16" 1064 + source = "registry+https://github.com/rust-lang/crates.io-index" 1065 + checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 1066 + dependencies = [ 1067 + "cfg-if", 1068 + "libc", 1069 + "wasi 0.11.1+wasi-snapshot-preview1", 1070 + ] 1071 + 1072 + [[package]] 1073 + name = "getrandom" 1074 + version = "0.3.3" 1075 + source = "registry+https://github.com/rust-lang/crates.io-index" 1076 + checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 1077 + dependencies = [ 1078 + "cfg-if", 1079 + "libc", 1080 + "r-efi", 1081 + "wasi 0.14.2+wasi-0.2.4", 168 1082 ] 169 1083 170 1084 [[package]] ··· 174 1088 checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 175 1089 176 1090 [[package]] 1091 + name = "globset" 1092 + version = "0.4.16" 1093 + source = "registry+https://github.com/rust-lang/crates.io-index" 1094 + checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" 1095 + dependencies = [ 1096 + "aho-corasick", 1097 + "bstr", 1098 + "log", 1099 + "regex-automata 0.4.9", 1100 + "regex-syntax 0.8.5", 1101 + ] 1102 + 1103 + [[package]] 1104 + name = "group" 1105 + version = "0.13.0" 1106 + source = "registry+https://github.com/rust-lang/crates.io-index" 1107 + checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" 1108 + dependencies = [ 1109 + "ff", 1110 + "rand_core 0.6.4", 1111 + "subtle", 1112 + ] 1113 + 1114 + [[package]] 1115 + name = "handlebars" 1116 + version = "6.3.2" 1117 + source = "registry+https://github.com/rust-lang/crates.io-index" 1118 + checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098" 1119 + dependencies = [ 1120 + "derive_builder", 1121 + "log", 1122 + "num-order", 1123 + "pest", 1124 + "pest_derive", 1125 + "serde", 1126 + "serde_json", 1127 + "thiserror 2.0.12", 1128 + ] 1129 + 1130 + [[package]] 1131 + name = "hashbrown" 1132 + version = "0.14.5" 1133 + source = "registry+https://github.com/rust-lang/crates.io-index" 1134 + checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 1135 + 1136 + [[package]] 1137 + name = "hashbrown" 1138 + version = "0.15.4" 1139 + source = "registry+https://github.com/rust-lang/crates.io-index" 1140 + checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" 1141 + dependencies = [ 1142 + "allocator-api2", 1143 + "equivalent", 1144 + "foldhash", 1145 + ] 1146 + 1147 + [[package]] 1148 + name = "hashlink" 1149 + version = "0.10.0" 1150 + source = "registry+https://github.com/rust-lang/crates.io-index" 1151 + checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" 1152 + dependencies = [ 1153 + "hashbrown 0.15.4", 1154 + ] 1155 + 1156 + [[package]] 1157 + name = "heck" 1158 + version = "0.5.0" 1159 + source = "registry+https://github.com/rust-lang/crates.io-index" 1160 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1161 + 1162 + [[package]] 1163 + name = "hex" 1164 + version = "0.4.3" 1165 + source = "registry+https://github.com/rust-lang/crates.io-index" 1166 + checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1167 + 1168 + [[package]] 1169 + name = "hickory-proto" 1170 + version = "0.24.4" 1171 + source = "registry+https://github.com/rust-lang/crates.io-index" 1172 + checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" 1173 + dependencies = [ 1174 + "async-trait", 1175 + "cfg-if", 1176 + "data-encoding", 1177 + "enum-as-inner", 1178 + "futures-channel", 1179 + "futures-io", 1180 + "futures-util", 1181 + "idna", 1182 + "ipnet", 1183 + "once_cell", 1184 + "rand 0.8.5", 1185 + "thiserror 1.0.69", 1186 + "tinyvec", 1187 + "tokio", 1188 + "tracing", 1189 + "url", 1190 + ] 1191 + 1192 + [[package]] 1193 + name = "hickory-resolver" 1194 + version = "0.24.4" 1195 + source = "registry+https://github.com/rust-lang/crates.io-index" 1196 + checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" 1197 + dependencies = [ 1198 + "cfg-if", 1199 + "futures-util", 1200 + "hickory-proto", 1201 + "ipconfig", 1202 + "lru-cache", 1203 + "once_cell", 1204 + "parking_lot", 1205 + "rand 0.8.5", 1206 + "resolv-conf", 1207 + "smallvec", 1208 + "thiserror 1.0.69", 1209 + "tokio", 1210 + "tracing", 1211 + ] 1212 + 1213 + [[package]] 1214 + name = "hkdf" 1215 + version = "0.12.4" 1216 + source = "registry+https://github.com/rust-lang/crates.io-index" 1217 + checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 1218 + dependencies = [ 1219 + "hmac", 1220 + ] 1221 + 1222 + [[package]] 1223 + name = "hmac" 1224 + version = "0.12.1" 1225 + source = "registry+https://github.com/rust-lang/crates.io-index" 1226 + checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 1227 + dependencies = [ 1228 + "digest", 1229 + ] 1230 + 1231 + [[package]] 1232 + name = "home" 1233 + version = "0.5.11" 1234 + source = "registry+https://github.com/rust-lang/crates.io-index" 1235 + checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 1236 + dependencies = [ 1237 + "windows-sys 0.59.0", 1238 + ] 1239 + 1240 + [[package]] 1241 + name = "html-escape" 1242 + version = "0.2.13" 1243 + source = "registry+https://github.com/rust-lang/crates.io-index" 1244 + checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" 1245 + dependencies = [ 1246 + "utf8-width", 1247 + ] 1248 + 1249 + [[package]] 177 1250 name = "http" 178 1251 version = "1.3.1" 179 1252 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 236 1309 "pin-project-lite", 237 1310 "smallvec", 238 1311 "tokio", 1312 + "want", 1313 + ] 1314 + 1315 + [[package]] 1316 + name = "hyper-tls" 1317 + version = "0.6.0" 1318 + source = "registry+https://github.com/rust-lang/crates.io-index" 1319 + checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 1320 + dependencies = [ 1321 + "bytes", 1322 + "http-body-util", 1323 + "hyper", 1324 + "hyper-util", 1325 + "native-tls", 1326 + "tokio", 1327 + "tokio-native-tls", 1328 + "tower-service", 239 1329 ] 240 1330 241 1331 [[package]] ··· 244 1334 source = "registry+https://github.com/rust-lang/crates.io-index" 245 1335 checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" 246 1336 dependencies = [ 1337 + "base64", 247 1338 "bytes", 1339 + "futures-channel", 248 1340 "futures-core", 1341 + "futures-util", 249 1342 "http", 250 1343 "http-body", 251 1344 "hyper", 1345 + "ipnet", 1346 + "libc", 1347 + "percent-encoding", 252 1348 "pin-project-lite", 1349 + "socket2 0.6.0", 253 1350 "tokio", 254 1351 "tower-service", 1352 + "tracing", 1353 + ] 1354 + 1355 + [[package]] 1356 + name = "hypertext" 1357 + version = "0.12.1" 1358 + source = "registry+https://github.com/rust-lang/crates.io-index" 1359 + checksum = "eb73b82c6a76434fd87a0668ef3ff1a8182512dfb610eef9138169a7e2d3a0ed" 1360 + dependencies = [ 1361 + "html-escape", 1362 + "hypertext-macros", 1363 + "itoa", 1364 + "ryu", 1365 + ] 1366 + 1367 + [[package]] 1368 + name = "hypertext-macros" 1369 + version = "0.12.1" 1370 + source = "registry+https://github.com/rust-lang/crates.io-index" 1371 + checksum = "c120534b9d41bd317a5b111aacc38a34071d15df9462c0e21f6093ade3a03660" 1372 + dependencies = [ 1373 + "html-escape", 1374 + "proc-macro2", 1375 + "quote", 1376 + "syn", 1377 + ] 1378 + 1379 + [[package]] 1380 + name = "iana-time-zone" 1381 + version = "0.1.63" 1382 + source = "registry+https://github.com/rust-lang/crates.io-index" 1383 + checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 1384 + dependencies = [ 1385 + "android_system_properties", 1386 + "core-foundation-sys", 1387 + "iana-time-zone-haiku", 1388 + "js-sys", 1389 + "log", 1390 + "wasm-bindgen", 1391 + "windows-core", 1392 + ] 1393 + 1394 + [[package]] 1395 + name = "iana-time-zone-haiku" 1396 + version = "0.1.2" 1397 + source = "registry+https://github.com/rust-lang/crates.io-index" 1398 + checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1399 + dependencies = [ 1400 + "cc", 1401 + ] 1402 + 1403 + [[package]] 1404 + name = "icu_collections" 1405 + version = "2.0.0" 1406 + source = "registry+https://github.com/rust-lang/crates.io-index" 1407 + checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 1408 + dependencies = [ 1409 + "displaydoc", 1410 + "potential_utf", 1411 + "yoke", 1412 + "zerofrom", 1413 + "zerovec", 1414 + ] 1415 + 1416 + [[package]] 1417 + name = "icu_locale_core" 1418 + version = "2.0.0" 1419 + source = "registry+https://github.com/rust-lang/crates.io-index" 1420 + checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 1421 + dependencies = [ 1422 + "displaydoc", 1423 + "litemap", 1424 + "tinystr", 1425 + "writeable", 1426 + "zerovec", 1427 + ] 1428 + 1429 + [[package]] 1430 + name = "icu_normalizer" 1431 + version = "2.0.0" 1432 + source = "registry+https://github.com/rust-lang/crates.io-index" 1433 + checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 1434 + dependencies = [ 1435 + "displaydoc", 1436 + "icu_collections", 1437 + "icu_normalizer_data", 1438 + "icu_properties", 1439 + "icu_provider", 1440 + "smallvec", 1441 + "zerovec", 1442 + ] 1443 + 1444 + [[package]] 1445 + name = "icu_normalizer_data" 1446 + version = "2.0.0" 1447 + source = "registry+https://github.com/rust-lang/crates.io-index" 1448 + checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 1449 + 1450 + [[package]] 1451 + name = "icu_properties" 1452 + version = "2.0.1" 1453 + source = "registry+https://github.com/rust-lang/crates.io-index" 1454 + checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 1455 + dependencies = [ 1456 + "displaydoc", 1457 + "icu_collections", 1458 + "icu_locale_core", 1459 + "icu_properties_data", 1460 + "icu_provider", 1461 + "potential_utf", 1462 + "zerotrie", 1463 + "zerovec", 1464 + ] 1465 + 1466 + [[package]] 1467 + name = "icu_properties_data" 1468 + version = "2.0.1" 1469 + source = "registry+https://github.com/rust-lang/crates.io-index" 1470 + checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 1471 + 1472 + [[package]] 1473 + name = "icu_provider" 1474 + version = "2.0.0" 1475 + source = "registry+https://github.com/rust-lang/crates.io-index" 1476 + checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 1477 + dependencies = [ 1478 + "displaydoc", 1479 + "icu_locale_core", 1480 + "stable_deref_trait", 1481 + "tinystr", 1482 + "writeable", 1483 + "yoke", 1484 + "zerofrom", 1485 + "zerotrie", 1486 + "zerovec", 1487 + ] 1488 + 1489 + [[package]] 1490 + name = "ident_case" 1491 + version = "1.0.1" 1492 + source = "registry+https://github.com/rust-lang/crates.io-index" 1493 + checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1494 + 1495 + [[package]] 1496 + name = "idna" 1497 + version = "1.0.3" 1498 + source = "registry+https://github.com/rust-lang/crates.io-index" 1499 + checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 1500 + dependencies = [ 1501 + "idna_adapter", 1502 + "smallvec", 1503 + "utf8_iter", 1504 + ] 1505 + 1506 + [[package]] 1507 + name = "idna_adapter" 1508 + version = "1.2.1" 1509 + source = "registry+https://github.com/rust-lang/crates.io-index" 1510 + checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 1511 + dependencies = [ 1512 + "icu_normalizer", 1513 + "icu_properties", 1514 + ] 1515 + 1516 + [[package]] 1517 + name = "indexmap" 1518 + version = "2.10.0" 1519 + source = "registry+https://github.com/rust-lang/crates.io-index" 1520 + checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" 1521 + dependencies = [ 1522 + "equivalent", 1523 + "hashbrown 0.15.4", 255 1524 ] 256 1525 257 1526 [[package]] ··· 266 1535 ] 267 1536 268 1537 [[package]] 1538 + name = "ipconfig" 1539 + version = "0.3.2" 1540 + source = "registry+https://github.com/rust-lang/crates.io-index" 1541 + checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" 1542 + dependencies = [ 1543 + "socket2 0.5.10", 1544 + "widestring", 1545 + "windows-sys 0.48.0", 1546 + "winreg", 1547 + ] 1548 + 1549 + [[package]] 1550 + name = "ipld-core" 1551 + version = "0.4.2" 1552 + source = "registry+https://github.com/rust-lang/crates.io-index" 1553 + checksum = "104718b1cc124d92a6d01ca9c9258a7df311405debb3408c445a36452f9bf8db" 1554 + dependencies = [ 1555 + "cid", 1556 + "serde", 1557 + "serde_bytes", 1558 + ] 1559 + 1560 + [[package]] 1561 + name = "ipnet" 1562 + version = "2.11.0" 1563 + source = "registry+https://github.com/rust-lang/crates.io-index" 1564 + checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1565 + 1566 + [[package]] 1567 + name = "iri-string" 1568 + version = "0.7.8" 1569 + source = "registry+https://github.com/rust-lang/crates.io-index" 1570 + checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" 1571 + dependencies = [ 1572 + "memchr", 1573 + "serde", 1574 + ] 1575 + 1576 + [[package]] 269 1577 name = "itoa" 270 1578 version = "1.0.15" 271 1579 source = "registry+https://github.com/rust-lang/crates.io-index" 272 1580 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 273 1581 274 1582 [[package]] 1583 + name = "jose-b64" 1584 + version = "0.1.2" 1585 + source = "registry+https://github.com/rust-lang/crates.io-index" 1586 + checksum = "bec69375368709666b21c76965ce67549f2d2db7605f1f8707d17c9656801b56" 1587 + dependencies = [ 1588 + "base64ct", 1589 + "serde", 1590 + "subtle", 1591 + "zeroize", 1592 + ] 1593 + 1594 + [[package]] 1595 + name = "jose-jwa" 1596 + version = "0.1.2" 1597 + source = "registry+https://github.com/rust-lang/crates.io-index" 1598 + checksum = "9ab78e053fe886a351d67cf0d194c000f9d0dcb92906eb34d853d7e758a4b3a7" 1599 + dependencies = [ 1600 + "serde", 1601 + ] 1602 + 1603 + [[package]] 1604 + name = "jose-jwk" 1605 + version = "0.1.2" 1606 + source = "registry+https://github.com/rust-lang/crates.io-index" 1607 + checksum = "280fa263807fe0782ecb6f2baadc28dffc04e00558a58e33bfdb801d11fd58e7" 1608 + dependencies = [ 1609 + "jose-b64", 1610 + "jose-jwa", 1611 + "p256", 1612 + "serde", 1613 + "zeroize", 1614 + ] 1615 + 1616 + [[package]] 1617 + name = "js-sys" 1618 + version = "0.3.77" 1619 + source = "registry+https://github.com/rust-lang/crates.io-index" 1620 + checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 1621 + dependencies = [ 1622 + "once_cell", 1623 + "wasm-bindgen", 1624 + ] 1625 + 1626 + [[package]] 1627 + name = "langtag" 1628 + version = "0.3.4" 1629 + source = "registry+https://github.com/rust-lang/crates.io-index" 1630 + checksum = "ed60c85f254d6ae8450cec15eedd921efbc4d1bdf6fcf6202b9a58b403f6f805" 1631 + dependencies = [ 1632 + "serde", 1633 + ] 1634 + 1635 + [[package]] 1636 + name = "lazy_static" 1637 + version = "1.5.0" 1638 + source = "registry+https://github.com/rust-lang/crates.io-index" 1639 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1640 + dependencies = [ 1641 + "spin", 1642 + ] 1643 + 1644 + [[package]] 275 1645 name = "libc" 276 1646 version = "0.2.174" 277 1647 source = "registry+https://github.com/rust-lang/crates.io-index" 278 1648 checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" 279 1649 280 1650 [[package]] 1651 + name = "libm" 1652 + version = "0.2.15" 1653 + source = "registry+https://github.com/rust-lang/crates.io-index" 1654 + checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" 1655 + 1656 + [[package]] 1657 + name = "libsqlite3-sys" 1658 + version = "0.30.1" 1659 + source = "registry+https://github.com/rust-lang/crates.io-index" 1660 + checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" 1661 + dependencies = [ 1662 + "pkg-config", 1663 + "vcpkg", 1664 + ] 1665 + 1666 + [[package]] 1667 + name = "linked-hash-map" 1668 + version = "0.5.6" 1669 + source = "registry+https://github.com/rust-lang/crates.io-index" 1670 + checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" 1671 + 1672 + [[package]] 1673 + name = "linux-raw-sys" 1674 + version = "0.9.4" 1675 + source = "registry+https://github.com/rust-lang/crates.io-index" 1676 + checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 1677 + 1678 + [[package]] 1679 + name = "listener" 1680 + version = "0.1.0" 1681 + 1682 + [[package]] 1683 + name = "litemap" 1684 + version = "0.8.0" 1685 + source = "registry+https://github.com/rust-lang/crates.io-index" 1686 + checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 1687 + 1688 + [[package]] 281 1689 name = "lock_api" 282 1690 version = "0.4.13" 283 1691 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 285 1693 dependencies = [ 286 1694 "autocfg", 287 1695 "scopeguard", 1696 + "serde", 288 1697 ] 289 1698 290 1699 [[package]] ··· 294 1703 checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 295 1704 296 1705 [[package]] 1706 + name = "loom" 1707 + version = "0.7.2" 1708 + source = "registry+https://github.com/rust-lang/crates.io-index" 1709 + checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" 1710 + dependencies = [ 1711 + "cfg-if", 1712 + "generator", 1713 + "scoped-tls", 1714 + "tracing", 1715 + "tracing-subscriber", 1716 + ] 1717 + 1718 + [[package]] 1719 + name = "lru" 1720 + version = "0.12.5" 1721 + source = "registry+https://github.com/rust-lang/crates.io-index" 1722 + checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 1723 + dependencies = [ 1724 + "hashbrown 0.15.4", 1725 + ] 1726 + 1727 + [[package]] 1728 + name = "lru-cache" 1729 + version = "0.1.2" 1730 + source = "registry+https://github.com/rust-lang/crates.io-index" 1731 + checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 1732 + dependencies = [ 1733 + "linked-hash-map", 1734 + ] 1735 + 1736 + [[package]] 1737 + name = "markdown" 1738 + version = "1.0.0" 1739 + source = "registry+https://github.com/rust-lang/crates.io-index" 1740 + checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb" 1741 + dependencies = [ 1742 + "unicode-id", 1743 + ] 1744 + 1745 + [[package]] 1746 + name = "matchers" 1747 + version = "0.1.0" 1748 + source = "registry+https://github.com/rust-lang/crates.io-index" 1749 + checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 1750 + dependencies = [ 1751 + "regex-automata 0.1.10", 1752 + ] 1753 + 1754 + [[package]] 297 1755 name = "matchit" 298 1756 version = "0.8.4" 299 1757 source = "registry+https://github.com/rust-lang/crates.io-index" 300 1758 checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 301 1759 302 1760 [[package]] 1761 + name = "md-5" 1762 + version = "0.10.6" 1763 + source = "registry+https://github.com/rust-lang/crates.io-index" 1764 + checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 1765 + dependencies = [ 1766 + "cfg-if", 1767 + "digest", 1768 + ] 1769 + 1770 + [[package]] 303 1771 name = "memchr" 304 1772 version = "2.7.5" 305 1773 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 327 1795 checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 328 1796 dependencies = [ 329 1797 "libc", 330 - "wasi", 1798 + "wasi 0.11.1+wasi-snapshot-preview1", 331 1799 "windows-sys 0.59.0", 332 1800 ] 333 1801 334 1802 [[package]] 1803 + name = "moka" 1804 + version = "0.12.10" 1805 + source = "registry+https://github.com/rust-lang/crates.io-index" 1806 + checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" 1807 + dependencies = [ 1808 + "async-lock", 1809 + "crossbeam-channel", 1810 + "crossbeam-epoch", 1811 + "crossbeam-utils", 1812 + "event-listener", 1813 + "futures-util", 1814 + "loom", 1815 + "parking_lot", 1816 + "portable-atomic", 1817 + "rustc_version", 1818 + "smallvec", 1819 + "tagptr", 1820 + "thiserror 1.0.69", 1821 + "uuid", 1822 + ] 1823 + 1824 + [[package]] 1825 + name = "multibase" 1826 + version = "0.9.1" 1827 + source = "registry+https://github.com/rust-lang/crates.io-index" 1828 + checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" 1829 + dependencies = [ 1830 + "base-x", 1831 + "data-encoding", 1832 + "data-encoding-macro", 1833 + ] 1834 + 1835 + [[package]] 1836 + name = "multihash" 1837 + version = "0.19.3" 1838 + source = "registry+https://github.com/rust-lang/crates.io-index" 1839 + checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" 1840 + dependencies = [ 1841 + "core2", 1842 + "serde", 1843 + "unsigned-varint", 1844 + ] 1845 + 1846 + [[package]] 1847 + name = "native-tls" 1848 + version = "0.2.14" 1849 + source = "registry+https://github.com/rust-lang/crates.io-index" 1850 + checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 1851 + dependencies = [ 1852 + "libc", 1853 + "log", 1854 + "openssl", 1855 + "openssl-probe", 1856 + "openssl-sys", 1857 + "schannel", 1858 + "security-framework", 1859 + "security-framework-sys", 1860 + "tempfile", 1861 + ] 1862 + 1863 + [[package]] 1864 + name = "nu-ansi-term" 1865 + version = "0.46.0" 1866 + source = "registry+https://github.com/rust-lang/crates.io-index" 1867 + checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1868 + dependencies = [ 1869 + "overload", 1870 + "winapi", 1871 + ] 1872 + 1873 + [[package]] 1874 + name = "num-bigint" 1875 + version = "0.4.6" 1876 + source = "registry+https://github.com/rust-lang/crates.io-index" 1877 + checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 1878 + dependencies = [ 1879 + "num-integer", 1880 + "num-traits", 1881 + ] 1882 + 1883 + [[package]] 1884 + name = "num-bigint-dig" 1885 + version = "0.8.4" 1886 + source = "registry+https://github.com/rust-lang/crates.io-index" 1887 + checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" 1888 + dependencies = [ 1889 + "byteorder", 1890 + "lazy_static", 1891 + "libm", 1892 + "num-integer", 1893 + "num-iter", 1894 + "num-traits", 1895 + "rand 0.8.5", 1896 + "smallvec", 1897 + "zeroize", 1898 + ] 1899 + 1900 + [[package]] 1901 + name = "num-conv" 1902 + version = "0.1.0" 1903 + source = "registry+https://github.com/rust-lang/crates.io-index" 1904 + checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1905 + 1906 + [[package]] 1907 + name = "num-integer" 1908 + version = "0.1.46" 1909 + source = "registry+https://github.com/rust-lang/crates.io-index" 1910 + checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1911 + dependencies = [ 1912 + "num-traits", 1913 + ] 1914 + 1915 + [[package]] 1916 + name = "num-iter" 1917 + version = "0.1.45" 1918 + source = "registry+https://github.com/rust-lang/crates.io-index" 1919 + checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 1920 + dependencies = [ 1921 + "autocfg", 1922 + "num-integer", 1923 + "num-traits", 1924 + ] 1925 + 1926 + [[package]] 1927 + name = "num-modular" 1928 + version = "0.6.1" 1929 + source = "registry+https://github.com/rust-lang/crates.io-index" 1930 + checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" 1931 + 1932 + [[package]] 1933 + name = "num-order" 1934 + version = "1.2.0" 1935 + source = "registry+https://github.com/rust-lang/crates.io-index" 1936 + checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" 1937 + dependencies = [ 1938 + "num-modular", 1939 + ] 1940 + 1941 + [[package]] 1942 + name = "num-traits" 1943 + version = "0.2.19" 1944 + source = "registry+https://github.com/rust-lang/crates.io-index" 1945 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1946 + dependencies = [ 1947 + "autocfg", 1948 + "libm", 1949 + ] 1950 + 1951 + [[package]] 335 1952 name = "object" 336 1953 version = "0.36.7" 337 1954 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 347 1964 checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 348 1965 349 1966 [[package]] 1967 + name = "openssl" 1968 + version = "0.10.73" 1969 + source = "registry+https://github.com/rust-lang/crates.io-index" 1970 + checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" 1971 + dependencies = [ 1972 + "bitflags", 1973 + "cfg-if", 1974 + "foreign-types", 1975 + "libc", 1976 + "once_cell", 1977 + "openssl-macros", 1978 + "openssl-sys", 1979 + ] 1980 + 1981 + [[package]] 1982 + name = "openssl-macros" 1983 + version = "0.1.1" 1984 + source = "registry+https://github.com/rust-lang/crates.io-index" 1985 + checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1986 + dependencies = [ 1987 + "proc-macro2", 1988 + "quote", 1989 + "syn", 1990 + ] 1991 + 1992 + [[package]] 1993 + name = "openssl-probe" 1994 + version = "0.1.6" 1995 + source = "registry+https://github.com/rust-lang/crates.io-index" 1996 + checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1997 + 1998 + [[package]] 1999 + name = "openssl-sys" 2000 + version = "0.9.109" 2001 + source = "registry+https://github.com/rust-lang/crates.io-index" 2002 + checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" 2003 + dependencies = [ 2004 + "cc", 2005 + "libc", 2006 + "pkg-config", 2007 + "vcpkg", 2008 + ] 2009 + 2010 + [[package]] 2011 + name = "overload" 2012 + version = "0.1.1" 2013 + source = "registry+https://github.com/rust-lang/crates.io-index" 2014 + checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 2015 + 2016 + [[package]] 2017 + name = "p256" 2018 + version = "0.13.2" 2019 + source = "registry+https://github.com/rust-lang/crates.io-index" 2020 + checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" 2021 + dependencies = [ 2022 + "ecdsa", 2023 + "elliptic-curve", 2024 + "primeorder", 2025 + "sha2", 2026 + ] 2027 + 2028 + [[package]] 2029 + name = "parking" 2030 + version = "2.2.1" 2031 + source = "registry+https://github.com/rust-lang/crates.io-index" 2032 + checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 2033 + 2034 + [[package]] 350 2035 name = "parking_lot" 351 2036 version = "0.12.4" 352 2037 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 366 2051 "libc", 367 2052 "redox_syscall", 368 2053 "smallvec", 369 - "windows-targets", 2054 + "windows-targets 0.52.6", 2055 + ] 2056 + 2057 + [[package]] 2058 + name = "pem-rfc7468" 2059 + version = "0.7.0" 2060 + source = "registry+https://github.com/rust-lang/crates.io-index" 2061 + checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 2062 + dependencies = [ 2063 + "base64ct", 370 2064 ] 371 2065 372 2066 [[package]] ··· 376 2070 checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 377 2071 378 2072 [[package]] 2073 + name = "pest" 2074 + version = "2.8.1" 2075 + source = "registry+https://github.com/rust-lang/crates.io-index" 2076 + checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" 2077 + dependencies = [ 2078 + "memchr", 2079 + "thiserror 2.0.12", 2080 + "ucd-trie", 2081 + ] 2082 + 2083 + [[package]] 2084 + name = "pest_derive" 2085 + version = "2.8.1" 2086 + source = "registry+https://github.com/rust-lang/crates.io-index" 2087 + checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" 2088 + dependencies = [ 2089 + "pest", 2090 + "pest_generator", 2091 + ] 2092 + 2093 + [[package]] 2094 + name = "pest_generator" 2095 + version = "2.8.1" 2096 + source = "registry+https://github.com/rust-lang/crates.io-index" 2097 + checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" 2098 + dependencies = [ 2099 + "pest", 2100 + "pest_meta", 2101 + "proc-macro2", 2102 + "quote", 2103 + "syn", 2104 + ] 2105 + 2106 + [[package]] 2107 + name = "pest_meta" 2108 + version = "2.8.1" 2109 + source = "registry+https://github.com/rust-lang/crates.io-index" 2110 + checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" 2111 + dependencies = [ 2112 + "pest", 2113 + "sha2", 2114 + ] 2115 + 2116 + [[package]] 379 2117 name = "pin-project-lite" 380 2118 version = "0.2.16" 381 2119 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 388 2126 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 389 2127 390 2128 [[package]] 2129 + name = "pkcs1" 2130 + version = "0.7.5" 2131 + source = "registry+https://github.com/rust-lang/crates.io-index" 2132 + checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 2133 + dependencies = [ 2134 + "der", 2135 + "pkcs8", 2136 + "spki", 2137 + ] 2138 + 2139 + [[package]] 2140 + name = "pkcs8" 2141 + version = "0.10.2" 2142 + source = "registry+https://github.com/rust-lang/crates.io-index" 2143 + checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 2144 + dependencies = [ 2145 + "der", 2146 + "spki", 2147 + ] 2148 + 2149 + [[package]] 2150 + name = "pkg-config" 2151 + version = "0.3.32" 2152 + source = "registry+https://github.com/rust-lang/crates.io-index" 2153 + checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 2154 + 2155 + [[package]] 2156 + name = "pool" 2157 + version = "0.1.4" 2158 + source = "registry+https://github.com/rust-lang/crates.io-index" 2159 + checksum = "c7ac1531a0016945992b4e816e81538dfad0b9f00d280bcb707d711839f1536d" 2160 + 2161 + [[package]] 2162 + name = "portable-atomic" 2163 + version = "1.11.1" 2164 + source = "registry+https://github.com/rust-lang/crates.io-index" 2165 + checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 2166 + 2167 + [[package]] 2168 + name = "potential_utf" 2169 + version = "0.1.2" 2170 + source = "registry+https://github.com/rust-lang/crates.io-index" 2171 + checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" 2172 + dependencies = [ 2173 + "zerovec", 2174 + ] 2175 + 2176 + [[package]] 2177 + name = "powerfmt" 2178 + version = "0.2.0" 2179 + source = "registry+https://github.com/rust-lang/crates.io-index" 2180 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 2181 + 2182 + [[package]] 2183 + name = "ppv-lite86" 2184 + version = "0.2.21" 2185 + source = "registry+https://github.com/rust-lang/crates.io-index" 2186 + checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 2187 + dependencies = [ 2188 + "zerocopy", 2189 + ] 2190 + 2191 + [[package]] 2192 + name = "primeorder" 2193 + version = "0.13.6" 2194 + source = "registry+https://github.com/rust-lang/crates.io-index" 2195 + checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" 2196 + dependencies = [ 2197 + "elliptic-curve", 2198 + ] 2199 + 2200 + [[package]] 391 2201 name = "proc-macro2" 392 2202 version = "1.0.95" 393 2203 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 406 2216 ] 407 2217 408 2218 [[package]] 2219 + name = "r-efi" 2220 + version = "5.3.0" 2221 + source = "registry+https://github.com/rust-lang/crates.io-index" 2222 + checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 2223 + 2224 + [[package]] 2225 + name = "rand" 2226 + version = "0.8.5" 2227 + source = "registry+https://github.com/rust-lang/crates.io-index" 2228 + checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 2229 + dependencies = [ 2230 + "libc", 2231 + "rand_chacha 0.3.1", 2232 + "rand_core 0.6.4", 2233 + ] 2234 + 2235 + [[package]] 2236 + name = "rand" 2237 + version = "0.9.2" 2238 + source = "registry+https://github.com/rust-lang/crates.io-index" 2239 + checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 2240 + dependencies = [ 2241 + "rand_chacha 0.9.0", 2242 + "rand_core 0.9.3", 2243 + ] 2244 + 2245 + [[package]] 2246 + name = "rand_chacha" 2247 + version = "0.3.1" 2248 + source = "registry+https://github.com/rust-lang/crates.io-index" 2249 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 2250 + dependencies = [ 2251 + "ppv-lite86", 2252 + "rand_core 0.6.4", 2253 + ] 2254 + 2255 + [[package]] 2256 + name = "rand_chacha" 2257 + version = "0.9.0" 2258 + source = "registry+https://github.com/rust-lang/crates.io-index" 2259 + checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 2260 + dependencies = [ 2261 + "ppv-lite86", 2262 + "rand_core 0.9.3", 2263 + ] 2264 + 2265 + [[package]] 2266 + name = "rand_core" 2267 + version = "0.6.4" 2268 + source = "registry+https://github.com/rust-lang/crates.io-index" 2269 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 2270 + dependencies = [ 2271 + "getrandom 0.2.16", 2272 + ] 2273 + 2274 + [[package]] 2275 + name = "rand_core" 2276 + version = "0.9.3" 2277 + source = "registry+https://github.com/rust-lang/crates.io-index" 2278 + checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 2279 + dependencies = [ 2280 + "getrandom 0.3.3", 2281 + ] 2282 + 2283 + [[package]] 2284 + name = "redis" 2285 + version = "0.32.4" 2286 + source = "registry+https://github.com/rust-lang/crates.io-index" 2287 + checksum = "e1f66bf4cac9733a23bcdf1e0e01effbaaad208567beba68be8f67e5f4af3ee1" 2288 + dependencies = [ 2289 + "bytes", 2290 + "cfg-if", 2291 + "combine", 2292 + "futures-util", 2293 + "itoa", 2294 + "num-bigint", 2295 + "percent-encoding", 2296 + "pin-project-lite", 2297 + "ryu", 2298 + "sha1_smol", 2299 + "socket2 0.6.0", 2300 + "tokio", 2301 + "tokio-util", 2302 + "url", 2303 + ] 2304 + 2305 + [[package]] 409 2306 name = "redox_syscall" 410 - version = "0.5.13" 2307 + version = "0.5.16" 411 2308 source = "registry+https://github.com/rust-lang/crates.io-index" 412 - checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" 2309 + checksum = "7251471db004e509f4e75a62cca9435365b5ec7bcdff530d612ac7c87c44a792" 413 2310 dependencies = [ 414 2311 "bitflags", 415 2312 ] 416 2313 417 2314 [[package]] 2315 + name = "regex" 2316 + version = "1.11.1" 2317 + source = "registry+https://github.com/rust-lang/crates.io-index" 2318 + checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 2319 + dependencies = [ 2320 + "aho-corasick", 2321 + "memchr", 2322 + "regex-automata 0.4.9", 2323 + "regex-syntax 0.8.5", 2324 + ] 2325 + 2326 + [[package]] 2327 + name = "regex-automata" 2328 + version = "0.1.10" 2329 + source = "registry+https://github.com/rust-lang/crates.io-index" 2330 + checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 2331 + dependencies = [ 2332 + "regex-syntax 0.6.29", 2333 + ] 2334 + 2335 + [[package]] 2336 + name = "regex-automata" 2337 + version = "0.4.9" 2338 + source = "registry+https://github.com/rust-lang/crates.io-index" 2339 + checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 2340 + dependencies = [ 2341 + "aho-corasick", 2342 + "memchr", 2343 + "regex-syntax 0.8.5", 2344 + ] 2345 + 2346 + [[package]] 2347 + name = "regex-syntax" 2348 + version = "0.6.29" 2349 + source = "registry+https://github.com/rust-lang/crates.io-index" 2350 + checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 2351 + 2352 + [[package]] 2353 + name = "regex-syntax" 2354 + version = "0.8.5" 2355 + source = "registry+https://github.com/rust-lang/crates.io-index" 2356 + checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 2357 + 2358 + [[package]] 2359 + name = "reqwest" 2360 + version = "0.12.22" 2361 + source = "registry+https://github.com/rust-lang/crates.io-index" 2362 + checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" 2363 + dependencies = [ 2364 + "async-compression", 2365 + "base64", 2366 + "bytes", 2367 + "futures-core", 2368 + "futures-util", 2369 + "http", 2370 + "http-body", 2371 + "http-body-util", 2372 + "hyper", 2373 + "hyper-tls", 2374 + "hyper-util", 2375 + "js-sys", 2376 + "log", 2377 + "native-tls", 2378 + "percent-encoding", 2379 + "pin-project-lite", 2380 + "rustls-pki-types", 2381 + "serde", 2382 + "serde_json", 2383 + "serde_urlencoded", 2384 + "sync_wrapper", 2385 + "tokio", 2386 + "tokio-native-tls", 2387 + "tokio-util", 2388 + "tower", 2389 + "tower-http", 2390 + "tower-service", 2391 + "url", 2392 + "wasm-bindgen", 2393 + "wasm-bindgen-futures", 2394 + "web-sys", 2395 + ] 2396 + 2397 + [[package]] 2398 + name = "resolv-conf" 2399 + version = "0.7.4" 2400 + source = "registry+https://github.com/rust-lang/crates.io-index" 2401 + checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" 2402 + 2403 + [[package]] 2404 + name = "rfc6979" 2405 + version = "0.4.0" 2406 + source = "registry+https://github.com/rust-lang/crates.io-index" 2407 + checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" 2408 + dependencies = [ 2409 + "hmac", 2410 + "subtle", 2411 + ] 2412 + 2413 + [[package]] 2414 + name = "rsa" 2415 + version = "0.9.8" 2416 + source = "registry+https://github.com/rust-lang/crates.io-index" 2417 + checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" 2418 + dependencies = [ 2419 + "const-oid", 2420 + "digest", 2421 + "num-bigint-dig", 2422 + "num-integer", 2423 + "num-traits", 2424 + "pkcs1", 2425 + "pkcs8", 2426 + "rand_core 0.6.4", 2427 + "signature", 2428 + "spki", 2429 + "subtle", 2430 + "zeroize", 2431 + ] 2432 + 2433 + [[package]] 2434 + name = "rust-embed" 2435 + version = "8.7.2" 2436 + source = "registry+https://github.com/rust-lang/crates.io-index" 2437 + checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" 2438 + dependencies = [ 2439 + "rust-embed-impl", 2440 + "rust-embed-utils", 2441 + "walkdir", 2442 + ] 2443 + 2444 + [[package]] 2445 + name = "rust-embed-impl" 2446 + version = "8.7.2" 2447 + source = "registry+https://github.com/rust-lang/crates.io-index" 2448 + checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" 2449 + dependencies = [ 2450 + "proc-macro2", 2451 + "quote", 2452 + "rust-embed-utils", 2453 + "syn", 2454 + "walkdir", 2455 + ] 2456 + 2457 + [[package]] 2458 + name = "rust-embed-utils" 2459 + version = "8.7.2" 2460 + source = "registry+https://github.com/rust-lang/crates.io-index" 2461 + checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" 2462 + dependencies = [ 2463 + "globset", 2464 + "sha2", 2465 + "walkdir", 2466 + ] 2467 + 2468 + [[package]] 418 2469 name = "rustc-demangle" 419 2470 version = "0.1.25" 420 2471 source = "registry+https://github.com/rust-lang/crates.io-index" 421 2472 checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" 422 2473 423 2474 [[package]] 2475 + name = "rustc-hash" 2476 + version = "2.1.1" 2477 + source = "registry+https://github.com/rust-lang/crates.io-index" 2478 + checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 2479 + 2480 + [[package]] 2481 + name = "rustc_version" 2482 + version = "0.4.1" 2483 + source = "registry+https://github.com/rust-lang/crates.io-index" 2484 + checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" 2485 + dependencies = [ 2486 + "semver", 2487 + ] 2488 + 2489 + [[package]] 2490 + name = "rustix" 2491 + version = "1.0.8" 2492 + source = "registry+https://github.com/rust-lang/crates.io-index" 2493 + checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" 2494 + dependencies = [ 2495 + "bitflags", 2496 + "errno", 2497 + "libc", 2498 + "linux-raw-sys", 2499 + "windows-sys 0.59.0", 2500 + ] 2501 + 2502 + [[package]] 2503 + name = "rustls-pki-types" 2504 + version = "1.12.0" 2505 + source = "registry+https://github.com/rust-lang/crates.io-index" 2506 + checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 2507 + dependencies = [ 2508 + "zeroize", 2509 + ] 2510 + 2511 + [[package]] 424 2512 name = "rustversion" 425 2513 version = "1.0.21" 426 2514 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 433 2521 checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 434 2522 435 2523 [[package]] 2524 + name = "same-file" 2525 + version = "1.0.6" 2526 + source = "registry+https://github.com/rust-lang/crates.io-index" 2527 + checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 2528 + dependencies = [ 2529 + "winapi-util", 2530 + ] 2531 + 2532 + [[package]] 2533 + name = "schannel" 2534 + version = "0.1.27" 2535 + source = "registry+https://github.com/rust-lang/crates.io-index" 2536 + checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 2537 + dependencies = [ 2538 + "windows-sys 0.59.0", 2539 + ] 2540 + 2541 + [[package]] 2542 + name = "scoped-tls" 2543 + version = "1.0.1" 2544 + source = "registry+https://github.com/rust-lang/crates.io-index" 2545 + checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 2546 + 2547 + [[package]] 436 2548 name = "scopeguard" 437 2549 version = "1.2.0" 438 2550 source = "registry+https://github.com/rust-lang/crates.io-index" 439 2551 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 440 2552 441 2553 [[package]] 2554 + name = "sec1" 2555 + version = "0.7.3" 2556 + source = "registry+https://github.com/rust-lang/crates.io-index" 2557 + checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 2558 + dependencies = [ 2559 + "base16ct", 2560 + "der", 2561 + "generic-array", 2562 + "subtle", 2563 + "zeroize", 2564 + ] 2565 + 2566 + [[package]] 2567 + name = "security-framework" 2568 + version = "2.11.1" 2569 + source = "registry+https://github.com/rust-lang/crates.io-index" 2570 + checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 2571 + dependencies = [ 2572 + "bitflags", 2573 + "core-foundation", 2574 + "core-foundation-sys", 2575 + "libc", 2576 + "security-framework-sys", 2577 + ] 2578 + 2579 + [[package]] 2580 + name = "security-framework-sys" 2581 + version = "2.14.0" 2582 + source = "registry+https://github.com/rust-lang/crates.io-index" 2583 + checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 2584 + dependencies = [ 2585 + "core-foundation-sys", 2586 + "libc", 2587 + ] 2588 + 2589 + [[package]] 2590 + name = "semver" 2591 + version = "1.0.26" 2592 + source = "registry+https://github.com/rust-lang/crates.io-index" 2593 + checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" 2594 + 2595 + [[package]] 442 2596 name = "serde" 443 2597 version = "1.0.219" 444 2598 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 448 2602 ] 449 2603 450 2604 [[package]] 2605 + name = "serde_bytes" 2606 + version = "0.11.17" 2607 + source = "registry+https://github.com/rust-lang/crates.io-index" 2608 + checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" 2609 + dependencies = [ 2610 + "serde", 2611 + ] 2612 + 2613 + [[package]] 451 2614 name = "serde_derive" 452 2615 version = "1.0.219" 453 2616 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 456 2619 "proc-macro2", 457 2620 "quote", 458 2621 "syn", 2622 + ] 2623 + 2624 + [[package]] 2625 + name = "serde_html_form" 2626 + version = "0.2.7" 2627 + source = "registry+https://github.com/rust-lang/crates.io-index" 2628 + checksum = "9d2de91cf02bbc07cde38891769ccd5d4f073d22a40683aa4bc7a95781aaa2c4" 2629 + dependencies = [ 2630 + "form_urlencoded", 2631 + "indexmap", 2632 + "itoa", 2633 + "ryu", 2634 + "serde", 459 2635 ] 460 2636 461 2637 [[package]] ··· 493 2669 ] 494 2670 495 2671 [[package]] 2672 + name = "sha1" 2673 + version = "0.10.6" 2674 + source = "registry+https://github.com/rust-lang/crates.io-index" 2675 + checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 2676 + dependencies = [ 2677 + "cfg-if", 2678 + "cpufeatures", 2679 + "digest", 2680 + ] 2681 + 2682 + [[package]] 2683 + name = "sha1_smol" 2684 + version = "1.0.1" 2685 + source = "registry+https://github.com/rust-lang/crates.io-index" 2686 + checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" 2687 + 2688 + [[package]] 2689 + name = "sha2" 2690 + version = "0.10.9" 2691 + source = "registry+https://github.com/rust-lang/crates.io-index" 2692 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 2693 + dependencies = [ 2694 + "cfg-if", 2695 + "cpufeatures", 2696 + "digest", 2697 + ] 2698 + 2699 + [[package]] 2700 + name = "sharded-slab" 2701 + version = "0.1.7" 2702 + source = "registry+https://github.com/rust-lang/crates.io-index" 2703 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 2704 + dependencies = [ 2705 + "lazy_static", 2706 + ] 2707 + 2708 + [[package]] 2709 + name = "shared" 2710 + version = "0.1.0" 2711 + dependencies = [ 2712 + "async-trait", 2713 + "atrium-api", 2714 + "atrium-common", 2715 + "atrium-identity", 2716 + "atrium-oauth", 2717 + "axum", 2718 + "bb8", 2719 + "bb8-redis", 2720 + "handlebars", 2721 + "hickory-resolver", 2722 + "log", 2723 + "markdown", 2724 + "rand 0.9.2", 2725 + "rust-embed", 2726 + "serde", 2727 + "serde_json", 2728 + "sqlx", 2729 + "thiserror 1.0.69", 2730 + ] 2731 + 2732 + [[package]] 2733 + name = "shlex" 2734 + version = "1.3.0" 2735 + source = "registry+https://github.com/rust-lang/crates.io-index" 2736 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 2737 + 2738 + [[package]] 496 2739 name = "signal-hook-registry" 497 2740 version = "1.4.5" 498 2741 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 502 2745 ] 503 2746 504 2747 [[package]] 2748 + name = "signature" 2749 + version = "2.2.0" 2750 + source = "registry+https://github.com/rust-lang/crates.io-index" 2751 + checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 2752 + dependencies = [ 2753 + "digest", 2754 + "rand_core 0.6.4", 2755 + ] 2756 + 2757 + [[package]] 505 2758 name = "slab" 506 2759 version = "0.4.10" 507 2760 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 512 2765 version = "1.15.1" 513 2766 source = "registry+https://github.com/rust-lang/crates.io-index" 514 2767 checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 2768 + dependencies = [ 2769 + "serde", 2770 + ] 515 2771 516 2772 [[package]] 517 2773 name = "socket2" ··· 524 2780 ] 525 2781 526 2782 [[package]] 2783 + name = "socket2" 2784 + version = "0.6.0" 2785 + source = "registry+https://github.com/rust-lang/crates.io-index" 2786 + checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" 2787 + dependencies = [ 2788 + "libc", 2789 + "windows-sys 0.59.0", 2790 + ] 2791 + 2792 + [[package]] 2793 + name = "spin" 2794 + version = "0.9.8" 2795 + source = "registry+https://github.com/rust-lang/crates.io-index" 2796 + checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 2797 + dependencies = [ 2798 + "lock_api", 2799 + ] 2800 + 2801 + [[package]] 2802 + name = "spki" 2803 + version = "0.7.3" 2804 + source = "registry+https://github.com/rust-lang/crates.io-index" 2805 + checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 2806 + dependencies = [ 2807 + "base64ct", 2808 + "der", 2809 + ] 2810 + 2811 + [[package]] 2812 + name = "sqlx" 2813 + version = "0.8.6" 2814 + source = "registry+https://github.com/rust-lang/crates.io-index" 2815 + checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" 2816 + dependencies = [ 2817 + "sqlx-core", 2818 + "sqlx-macros", 2819 + "sqlx-mysql", 2820 + "sqlx-postgres", 2821 + "sqlx-sqlite", 2822 + ] 2823 + 2824 + [[package]] 2825 + name = "sqlx-core" 2826 + version = "0.8.6" 2827 + source = "registry+https://github.com/rust-lang/crates.io-index" 2828 + checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" 2829 + dependencies = [ 2830 + "base64", 2831 + "bytes", 2832 + "chrono", 2833 + "crc", 2834 + "crossbeam-queue", 2835 + "either", 2836 + "event-listener", 2837 + "futures-core", 2838 + "futures-intrusive", 2839 + "futures-io", 2840 + "futures-util", 2841 + "hashbrown 0.15.4", 2842 + "hashlink", 2843 + "indexmap", 2844 + "log", 2845 + "memchr", 2846 + "once_cell", 2847 + "percent-encoding", 2848 + "serde", 2849 + "serde_json", 2850 + "sha2", 2851 + "smallvec", 2852 + "thiserror 2.0.12", 2853 + "tokio", 2854 + "tokio-stream", 2855 + "tracing", 2856 + "url", 2857 + ] 2858 + 2859 + [[package]] 2860 + name = "sqlx-macros" 2861 + version = "0.8.6" 2862 + source = "registry+https://github.com/rust-lang/crates.io-index" 2863 + checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" 2864 + dependencies = [ 2865 + "proc-macro2", 2866 + "quote", 2867 + "sqlx-core", 2868 + "sqlx-macros-core", 2869 + "syn", 2870 + ] 2871 + 2872 + [[package]] 2873 + name = "sqlx-macros-core" 2874 + version = "0.8.6" 2875 + source = "registry+https://github.com/rust-lang/crates.io-index" 2876 + checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" 2877 + dependencies = [ 2878 + "dotenvy", 2879 + "either", 2880 + "heck", 2881 + "hex", 2882 + "once_cell", 2883 + "proc-macro2", 2884 + "quote", 2885 + "serde", 2886 + "serde_json", 2887 + "sha2", 2888 + "sqlx-core", 2889 + "sqlx-mysql", 2890 + "sqlx-postgres", 2891 + "sqlx-sqlite", 2892 + "syn", 2893 + "tokio", 2894 + "url", 2895 + ] 2896 + 2897 + [[package]] 2898 + name = "sqlx-mysql" 2899 + version = "0.8.6" 2900 + source = "registry+https://github.com/rust-lang/crates.io-index" 2901 + checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" 2902 + dependencies = [ 2903 + "atoi", 2904 + "base64", 2905 + "bitflags", 2906 + "byteorder", 2907 + "bytes", 2908 + "chrono", 2909 + "crc", 2910 + "digest", 2911 + "dotenvy", 2912 + "either", 2913 + "futures-channel", 2914 + "futures-core", 2915 + "futures-io", 2916 + "futures-util", 2917 + "generic-array", 2918 + "hex", 2919 + "hkdf", 2920 + "hmac", 2921 + "itoa", 2922 + "log", 2923 + "md-5", 2924 + "memchr", 2925 + "once_cell", 2926 + "percent-encoding", 2927 + "rand 0.8.5", 2928 + "rsa", 2929 + "serde", 2930 + "sha1", 2931 + "sha2", 2932 + "smallvec", 2933 + "sqlx-core", 2934 + "stringprep", 2935 + "thiserror 2.0.12", 2936 + "tracing", 2937 + "whoami", 2938 + ] 2939 + 2940 + [[package]] 2941 + name = "sqlx-postgres" 2942 + version = "0.8.6" 2943 + source = "registry+https://github.com/rust-lang/crates.io-index" 2944 + checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" 2945 + dependencies = [ 2946 + "atoi", 2947 + "base64", 2948 + "bitflags", 2949 + "byteorder", 2950 + "chrono", 2951 + "crc", 2952 + "dotenvy", 2953 + "etcetera", 2954 + "futures-channel", 2955 + "futures-core", 2956 + "futures-util", 2957 + "hex", 2958 + "hkdf", 2959 + "hmac", 2960 + "home", 2961 + "itoa", 2962 + "log", 2963 + "md-5", 2964 + "memchr", 2965 + "once_cell", 2966 + "rand 0.8.5", 2967 + "serde", 2968 + "serde_json", 2969 + "sha2", 2970 + "smallvec", 2971 + "sqlx-core", 2972 + "stringprep", 2973 + "thiserror 2.0.12", 2974 + "tracing", 2975 + "whoami", 2976 + ] 2977 + 2978 + [[package]] 2979 + name = "sqlx-sqlite" 2980 + version = "0.8.6" 2981 + source = "registry+https://github.com/rust-lang/crates.io-index" 2982 + checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" 2983 + dependencies = [ 2984 + "atoi", 2985 + "chrono", 2986 + "flume", 2987 + "futures-channel", 2988 + "futures-core", 2989 + "futures-executor", 2990 + "futures-intrusive", 2991 + "futures-util", 2992 + "libsqlite3-sys", 2993 + "log", 2994 + "percent-encoding", 2995 + "serde", 2996 + "serde_urlencoded", 2997 + "sqlx-core", 2998 + "thiserror 2.0.12", 2999 + "tracing", 3000 + "url", 3001 + ] 3002 + 3003 + [[package]] 3004 + name = "stable_deref_trait" 3005 + version = "1.2.0" 3006 + source = "registry+https://github.com/rust-lang/crates.io-index" 3007 + checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 3008 + 3009 + [[package]] 3010 + name = "stringprep" 3011 + version = "0.1.5" 3012 + source = "registry+https://github.com/rust-lang/crates.io-index" 3013 + checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" 3014 + dependencies = [ 3015 + "unicode-bidi", 3016 + "unicode-normalization", 3017 + "unicode-properties", 3018 + ] 3019 + 3020 + [[package]] 3021 + name = "strsim" 3022 + version = "0.11.1" 3023 + source = "registry+https://github.com/rust-lang/crates.io-index" 3024 + checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 3025 + 3026 + [[package]] 3027 + name = "subtle" 3028 + version = "2.6.1" 3029 + source = "registry+https://github.com/rust-lang/crates.io-index" 3030 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 3031 + 3032 + [[package]] 527 3033 name = "syn" 528 3034 version = "2.0.104" 529 3035 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 539 3045 version = "1.0.2" 540 3046 source = "registry+https://github.com/rust-lang/crates.io-index" 541 3047 checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 3048 + dependencies = [ 3049 + "futures-core", 3050 + ] 3051 + 3052 + [[package]] 3053 + name = "synstructure" 3054 + version = "0.13.2" 3055 + source = "registry+https://github.com/rust-lang/crates.io-index" 3056 + checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 3057 + dependencies = [ 3058 + "proc-macro2", 3059 + "quote", 3060 + "syn", 3061 + ] 3062 + 3063 + [[package]] 3064 + name = "tagptr" 3065 + version = "0.2.0" 3066 + source = "registry+https://github.com/rust-lang/crates.io-index" 3067 + checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" 3068 + 3069 + [[package]] 3070 + name = "tempfile" 3071 + version = "3.20.0" 3072 + source = "registry+https://github.com/rust-lang/crates.io-index" 3073 + checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" 3074 + dependencies = [ 3075 + "fastrand", 3076 + "getrandom 0.3.3", 3077 + "once_cell", 3078 + "rustix", 3079 + "windows-sys 0.59.0", 3080 + ] 3081 + 3082 + [[package]] 3083 + name = "thiserror" 3084 + version = "1.0.69" 3085 + source = "registry+https://github.com/rust-lang/crates.io-index" 3086 + checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 3087 + dependencies = [ 3088 + "thiserror-impl 1.0.69", 3089 + ] 3090 + 3091 + [[package]] 3092 + name = "thiserror" 3093 + version = "2.0.12" 3094 + source = "registry+https://github.com/rust-lang/crates.io-index" 3095 + checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 3096 + dependencies = [ 3097 + "thiserror-impl 2.0.12", 3098 + ] 3099 + 3100 + [[package]] 3101 + name = "thiserror-impl" 3102 + version = "1.0.69" 3103 + source = "registry+https://github.com/rust-lang/crates.io-index" 3104 + checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 3105 + dependencies = [ 3106 + "proc-macro2", 3107 + "quote", 3108 + "syn", 3109 + ] 3110 + 3111 + [[package]] 3112 + name = "thiserror-impl" 3113 + version = "2.0.12" 3114 + source = "registry+https://github.com/rust-lang/crates.io-index" 3115 + checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 3116 + dependencies = [ 3117 + "proc-macro2", 3118 + "quote", 3119 + "syn", 3120 + ] 3121 + 3122 + [[package]] 3123 + name = "thread_local" 3124 + version = "1.1.9" 3125 + source = "registry+https://github.com/rust-lang/crates.io-index" 3126 + checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 3127 + dependencies = [ 3128 + "cfg-if", 3129 + ] 3130 + 3131 + [[package]] 3132 + name = "time" 3133 + version = "0.3.41" 3134 + source = "registry+https://github.com/rust-lang/crates.io-index" 3135 + checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" 3136 + dependencies = [ 3137 + "deranged", 3138 + "itoa", 3139 + "num-conv", 3140 + "powerfmt", 3141 + "serde", 3142 + "time-core", 3143 + "time-macros", 3144 + ] 3145 + 3146 + [[package]] 3147 + name = "time-core" 3148 + version = "0.1.4" 3149 + source = "registry+https://github.com/rust-lang/crates.io-index" 3150 + checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" 3151 + 3152 + [[package]] 3153 + name = "time-macros" 3154 + version = "0.2.22" 3155 + source = "registry+https://github.com/rust-lang/crates.io-index" 3156 + checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" 3157 + dependencies = [ 3158 + "num-conv", 3159 + "time-core", 3160 + ] 3161 + 3162 + [[package]] 3163 + name = "tinystr" 3164 + version = "0.8.1" 3165 + source = "registry+https://github.com/rust-lang/crates.io-index" 3166 + checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 3167 + dependencies = [ 3168 + "displaydoc", 3169 + "zerovec", 3170 + ] 3171 + 3172 + [[package]] 3173 + name = "tinyvec" 3174 + version = "1.9.0" 3175 + source = "registry+https://github.com/rust-lang/crates.io-index" 3176 + checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" 3177 + dependencies = [ 3178 + "tinyvec_macros", 3179 + ] 3180 + 3181 + [[package]] 3182 + name = "tinyvec_macros" 3183 + version = "0.1.1" 3184 + source = "registry+https://github.com/rust-lang/crates.io-index" 3185 + checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 542 3186 543 3187 [[package]] 544 3188 name = "tokio" 545 - version = "1.46.1" 3189 + version = "1.47.0" 546 3190 source = "registry+https://github.com/rust-lang/crates.io-index" 547 - checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" 3191 + checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" 548 3192 dependencies = [ 549 3193 "backtrace", 550 3194 "bytes", ··· 555 3199 "pin-project-lite", 556 3200 "signal-hook-registry", 557 3201 "slab", 558 - "socket2", 3202 + "socket2 0.6.0", 559 3203 "tokio-macros", 560 - "windows-sys 0.52.0", 3204 + "windows-sys 0.59.0", 561 3205 ] 562 3206 563 3207 [[package]] ··· 572 3216 ] 573 3217 574 3218 [[package]] 3219 + name = "tokio-native-tls" 3220 + version = "0.3.1" 3221 + source = "registry+https://github.com/rust-lang/crates.io-index" 3222 + checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 3223 + dependencies = [ 3224 + "native-tls", 3225 + "tokio", 3226 + ] 3227 + 3228 + [[package]] 3229 + name = "tokio-stream" 3230 + version = "0.1.17" 3231 + source = "registry+https://github.com/rust-lang/crates.io-index" 3232 + checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" 3233 + dependencies = [ 3234 + "futures-core", 3235 + "pin-project-lite", 3236 + "tokio", 3237 + ] 3238 + 3239 + [[package]] 3240 + name = "tokio-util" 3241 + version = "0.7.15" 3242 + source = "registry+https://github.com/rust-lang/crates.io-index" 3243 + checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" 3244 + dependencies = [ 3245 + "bytes", 3246 + "futures-core", 3247 + "futures-sink", 3248 + "pin-project-lite", 3249 + "tokio", 3250 + ] 3251 + 3252 + [[package]] 575 3253 name = "tower" 576 3254 version = "0.5.2" 577 3255 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 588 3266 ] 589 3267 590 3268 [[package]] 3269 + name = "tower-cookies" 3270 + version = "0.11.0" 3271 + source = "registry+https://github.com/rust-lang/crates.io-index" 3272 + checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36" 3273 + dependencies = [ 3274 + "axum-core", 3275 + "cookie", 3276 + "futures-util", 3277 + "http", 3278 + "parking_lot", 3279 + "pin-project-lite", 3280 + "tower-layer", 3281 + "tower-service", 3282 + ] 3283 + 3284 + [[package]] 3285 + name = "tower-http" 3286 + version = "0.6.6" 3287 + source = "registry+https://github.com/rust-lang/crates.io-index" 3288 + checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" 3289 + dependencies = [ 3290 + "bitflags", 3291 + "bytes", 3292 + "futures-util", 3293 + "http", 3294 + "http-body", 3295 + "iri-string", 3296 + "pin-project-lite", 3297 + "tower", 3298 + "tower-layer", 3299 + "tower-service", 3300 + "tracing", 3301 + ] 3302 + 3303 + [[package]] 591 3304 name = "tower-layer" 592 3305 version = "0.3.3" 593 3306 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 600 3313 checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 601 3314 602 3315 [[package]] 3316 + name = "tower-sessions" 3317 + version = "0.14.0" 3318 + source = "registry+https://github.com/rust-lang/crates.io-index" 3319 + checksum = "43a05911f23e8fae446005fe9b7b97e66d95b6db589dc1c4d59f6a2d4d4927d3" 3320 + dependencies = [ 3321 + "async-trait", 3322 + "http", 3323 + "time", 3324 + "tokio", 3325 + "tower-cookies", 3326 + "tower-layer", 3327 + "tower-service", 3328 + "tower-sessions-core", 3329 + "tower-sessions-memory-store", 3330 + "tracing", 3331 + ] 3332 + 3333 + [[package]] 3334 + name = "tower-sessions-core" 3335 + version = "0.14.0" 3336 + source = "registry+https://github.com/rust-lang/crates.io-index" 3337 + checksum = "ce8cce604865576b7751b7a6bc3058f754569a60d689328bb74c52b1d87e355b" 3338 + dependencies = [ 3339 + "async-trait", 3340 + "axum-core", 3341 + "base64", 3342 + "futures", 3343 + "http", 3344 + "parking_lot", 3345 + "rand 0.8.5", 3346 + "serde", 3347 + "serde_json", 3348 + "thiserror 2.0.12", 3349 + "time", 3350 + "tokio", 3351 + "tracing", 3352 + ] 3353 + 3354 + [[package]] 3355 + name = "tower-sessions-memory-store" 3356 + version = "0.14.0" 3357 + source = "registry+https://github.com/rust-lang/crates.io-index" 3358 + checksum = "fb05909f2e1420135a831dd5df9f5596d69196d0a64c3499ca474c4bd3d33242" 3359 + dependencies = [ 3360 + "async-trait", 3361 + "time", 3362 + "tokio", 3363 + "tower-sessions-core", 3364 + ] 3365 + 3366 + [[package]] 603 3367 name = "tracing" 604 3368 version = "0.1.41" 605 3369 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 607 3371 dependencies = [ 608 3372 "log", 609 3373 "pin-project-lite", 3374 + "tracing-attributes", 610 3375 "tracing-core", 611 3376 ] 612 3377 613 3378 [[package]] 3379 + name = "tracing-attributes" 3380 + version = "0.1.28" 3381 + source = "registry+https://github.com/rust-lang/crates.io-index" 3382 + checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 3383 + dependencies = [ 3384 + "proc-macro2", 3385 + "quote", 3386 + "syn", 3387 + ] 3388 + 3389 + [[package]] 614 3390 name = "tracing-core" 615 3391 version = "0.1.34" 616 3392 source = "registry+https://github.com/rust-lang/crates.io-index" 617 3393 checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 618 3394 dependencies = [ 619 3395 "once_cell", 3396 + "valuable", 3397 + ] 3398 + 3399 + [[package]] 3400 + name = "tracing-log" 3401 + version = "0.2.0" 3402 + source = "registry+https://github.com/rust-lang/crates.io-index" 3403 + checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 3404 + dependencies = [ 3405 + "log", 3406 + "once_cell", 3407 + "tracing-core", 3408 + ] 3409 + 3410 + [[package]] 3411 + name = "tracing-subscriber" 3412 + version = "0.3.19" 3413 + source = "registry+https://github.com/rust-lang/crates.io-index" 3414 + checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 3415 + dependencies = [ 3416 + "matchers", 3417 + "nu-ansi-term", 3418 + "once_cell", 3419 + "regex", 3420 + "sharded-slab", 3421 + "smallvec", 3422 + "thread_local", 3423 + "tracing", 3424 + "tracing-core", 3425 + "tracing-log", 620 3426 ] 621 3427 622 3428 [[package]] 3429 + name = "trait-variant" 3430 + version = "0.1.2" 3431 + source = "registry+https://github.com/rust-lang/crates.io-index" 3432 + checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" 3433 + dependencies = [ 3434 + "proc-macro2", 3435 + "quote", 3436 + "syn", 3437 + ] 3438 + 3439 + [[package]] 3440 + name = "try-lock" 3441 + version = "0.2.5" 3442 + source = "registry+https://github.com/rust-lang/crates.io-index" 3443 + checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 3444 + 3445 + [[package]] 3446 + name = "typenum" 3447 + version = "1.18.0" 3448 + source = "registry+https://github.com/rust-lang/crates.io-index" 3449 + checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 3450 + 3451 + [[package]] 3452 + name = "ucd-trie" 3453 + version = "0.1.7" 3454 + source = "registry+https://github.com/rust-lang/crates.io-index" 3455 + checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" 3456 + 3457 + [[package]] 3458 + name = "unicode-bidi" 3459 + version = "0.3.18" 3460 + source = "registry+https://github.com/rust-lang/crates.io-index" 3461 + checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" 3462 + 3463 + [[package]] 3464 + name = "unicode-id" 3465 + version = "0.3.5" 3466 + source = "registry+https://github.com/rust-lang/crates.io-index" 3467 + checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561" 3468 + 3469 + [[package]] 623 3470 name = "unicode-ident" 624 3471 version = "1.0.18" 625 3472 source = "registry+https://github.com/rust-lang/crates.io-index" 626 3473 checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 627 3474 628 3475 [[package]] 3476 + name = "unicode-normalization" 3477 + version = "0.1.24" 3478 + source = "registry+https://github.com/rust-lang/crates.io-index" 3479 + checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 3480 + dependencies = [ 3481 + "tinyvec", 3482 + ] 3483 + 3484 + [[package]] 3485 + name = "unicode-properties" 3486 + version = "0.1.3" 3487 + source = "registry+https://github.com/rust-lang/crates.io-index" 3488 + checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" 3489 + 3490 + [[package]] 3491 + name = "unsigned-varint" 3492 + version = "0.8.0" 3493 + source = "registry+https://github.com/rust-lang/crates.io-index" 3494 + checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" 3495 + 3496 + [[package]] 3497 + name = "url" 3498 + version = "2.5.4" 3499 + source = "registry+https://github.com/rust-lang/crates.io-index" 3500 + checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 3501 + dependencies = [ 3502 + "form_urlencoded", 3503 + "idna", 3504 + "percent-encoding", 3505 + ] 3506 + 3507 + [[package]] 3508 + name = "utf8-width" 3509 + version = "0.1.7" 3510 + source = "registry+https://github.com/rust-lang/crates.io-index" 3511 + checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" 3512 + 3513 + [[package]] 3514 + name = "utf8_iter" 3515 + version = "1.0.4" 3516 + source = "registry+https://github.com/rust-lang/crates.io-index" 3517 + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 3518 + 3519 + [[package]] 3520 + name = "uuid" 3521 + version = "1.17.0" 3522 + source = "registry+https://github.com/rust-lang/crates.io-index" 3523 + checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" 3524 + dependencies = [ 3525 + "getrandom 0.3.3", 3526 + "js-sys", 3527 + "wasm-bindgen", 3528 + ] 3529 + 3530 + [[package]] 3531 + name = "valuable" 3532 + version = "0.1.1" 3533 + source = "registry+https://github.com/rust-lang/crates.io-index" 3534 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 3535 + 3536 + [[package]] 3537 + name = "vcpkg" 3538 + version = "0.2.15" 3539 + source = "registry+https://github.com/rust-lang/crates.io-index" 3540 + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 3541 + 3542 + [[package]] 3543 + name = "version_check" 3544 + version = "0.9.5" 3545 + source = "registry+https://github.com/rust-lang/crates.io-index" 3546 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 3547 + 3548 + [[package]] 3549 + name = "walkdir" 3550 + version = "2.5.0" 3551 + source = "registry+https://github.com/rust-lang/crates.io-index" 3552 + checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 3553 + dependencies = [ 3554 + "same-file", 3555 + "winapi-util", 3556 + ] 3557 + 3558 + [[package]] 3559 + name = "want" 3560 + version = "0.3.1" 3561 + source = "registry+https://github.com/rust-lang/crates.io-index" 3562 + checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 3563 + dependencies = [ 3564 + "try-lock", 3565 + ] 3566 + 3567 + [[package]] 629 3568 name = "wasi" 630 3569 version = "0.11.1+wasi-snapshot-preview1" 631 3570 source = "registry+https://github.com/rust-lang/crates.io-index" 632 3571 checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 633 3572 634 3573 [[package]] 3574 + name = "wasi" 3575 + version = "0.14.2+wasi-0.2.4" 3576 + source = "registry+https://github.com/rust-lang/crates.io-index" 3577 + checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 3578 + dependencies = [ 3579 + "wit-bindgen-rt", 3580 + ] 3581 + 3582 + [[package]] 3583 + name = "wasite" 3584 + version = "0.1.0" 3585 + source = "registry+https://github.com/rust-lang/crates.io-index" 3586 + checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 3587 + 3588 + [[package]] 3589 + name = "wasm-bindgen" 3590 + version = "0.2.100" 3591 + source = "registry+https://github.com/rust-lang/crates.io-index" 3592 + checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 3593 + dependencies = [ 3594 + "cfg-if", 3595 + "once_cell", 3596 + "rustversion", 3597 + "wasm-bindgen-macro", 3598 + ] 3599 + 3600 + [[package]] 3601 + name = "wasm-bindgen-backend" 3602 + version = "0.2.100" 3603 + source = "registry+https://github.com/rust-lang/crates.io-index" 3604 + checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 3605 + dependencies = [ 3606 + "bumpalo", 3607 + "log", 3608 + "proc-macro2", 3609 + "quote", 3610 + "syn", 3611 + "wasm-bindgen-shared", 3612 + ] 3613 + 3614 + [[package]] 3615 + name = "wasm-bindgen-futures" 3616 + version = "0.4.50" 3617 + source = "registry+https://github.com/rust-lang/crates.io-index" 3618 + checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 3619 + dependencies = [ 3620 + "cfg-if", 3621 + "js-sys", 3622 + "once_cell", 3623 + "wasm-bindgen", 3624 + "web-sys", 3625 + ] 3626 + 3627 + [[package]] 3628 + name = "wasm-bindgen-macro" 3629 + version = "0.2.100" 3630 + source = "registry+https://github.com/rust-lang/crates.io-index" 3631 + checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 3632 + dependencies = [ 3633 + "quote", 3634 + "wasm-bindgen-macro-support", 3635 + ] 3636 + 3637 + [[package]] 3638 + name = "wasm-bindgen-macro-support" 3639 + version = "0.2.100" 3640 + source = "registry+https://github.com/rust-lang/crates.io-index" 3641 + checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 3642 + dependencies = [ 3643 + "proc-macro2", 3644 + "quote", 3645 + "syn", 3646 + "wasm-bindgen-backend", 3647 + "wasm-bindgen-shared", 3648 + ] 3649 + 3650 + [[package]] 3651 + name = "wasm-bindgen-shared" 3652 + version = "0.2.100" 3653 + source = "registry+https://github.com/rust-lang/crates.io-index" 3654 + checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 3655 + dependencies = [ 3656 + "unicode-ident", 3657 + ] 3658 + 3659 + [[package]] 3660 + name = "web" 3661 + version = "0.1.0" 3662 + dependencies = [ 3663 + "askama", 3664 + "async-trait", 3665 + "atrium-api", 3666 + "atrium-common", 3667 + "atrium-identity", 3668 + "atrium-oauth", 3669 + "axum", 3670 + "bb8", 3671 + "bb8-redis", 3672 + "chrono", 3673 + "dotenv", 3674 + "hypertext", 3675 + "log", 3676 + "pool", 3677 + "redis", 3678 + "serde", 3679 + "serde_json", 3680 + "shared", 3681 + "sqlx", 3682 + "tokio", 3683 + "tower-http", 3684 + "tower-sessions", 3685 + "tracing", 3686 + "tracing-subscriber", 3687 + ] 3688 + 3689 + [[package]] 3690 + name = "web-sys" 3691 + version = "0.3.77" 3692 + source = "registry+https://github.com/rust-lang/crates.io-index" 3693 + checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 3694 + dependencies = [ 3695 + "js-sys", 3696 + "wasm-bindgen", 3697 + ] 3698 + 3699 + [[package]] 3700 + name = "web-time" 3701 + version = "1.1.0" 3702 + source = "registry+https://github.com/rust-lang/crates.io-index" 3703 + checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 3704 + dependencies = [ 3705 + "js-sys", 3706 + "wasm-bindgen", 3707 + ] 3708 + 3709 + [[package]] 3710 + name = "whoami" 3711 + version = "1.6.0" 3712 + source = "registry+https://github.com/rust-lang/crates.io-index" 3713 + checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" 3714 + dependencies = [ 3715 + "redox_syscall", 3716 + "wasite", 3717 + ] 3718 + 3719 + [[package]] 3720 + name = "widestring" 3721 + version = "1.2.0" 3722 + source = "registry+https://github.com/rust-lang/crates.io-index" 3723 + checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" 3724 + 3725 + [[package]] 3726 + name = "winapi" 3727 + version = "0.3.9" 3728 + source = "registry+https://github.com/rust-lang/crates.io-index" 3729 + checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 3730 + dependencies = [ 3731 + "winapi-i686-pc-windows-gnu", 3732 + "winapi-x86_64-pc-windows-gnu", 3733 + ] 3734 + 3735 + [[package]] 3736 + name = "winapi-i686-pc-windows-gnu" 3737 + version = "0.4.0" 3738 + source = "registry+https://github.com/rust-lang/crates.io-index" 3739 + checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 3740 + 3741 + [[package]] 3742 + name = "winapi-util" 3743 + version = "0.1.10" 3744 + source = "registry+https://github.com/rust-lang/crates.io-index" 3745 + checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" 3746 + dependencies = [ 3747 + "windows-sys 0.59.0", 3748 + ] 3749 + 3750 + [[package]] 3751 + name = "winapi-x86_64-pc-windows-gnu" 3752 + version = "0.4.0" 3753 + source = "registry+https://github.com/rust-lang/crates.io-index" 3754 + checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 3755 + 3756 + [[package]] 3757 + name = "windows" 3758 + version = "0.61.3" 3759 + source = "registry+https://github.com/rust-lang/crates.io-index" 3760 + checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" 3761 + dependencies = [ 3762 + "windows-collections", 3763 + "windows-core", 3764 + "windows-future", 3765 + "windows-link", 3766 + "windows-numerics", 3767 + ] 3768 + 3769 + [[package]] 3770 + name = "windows-collections" 3771 + version = "0.2.0" 3772 + source = "registry+https://github.com/rust-lang/crates.io-index" 3773 + checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" 3774 + dependencies = [ 3775 + "windows-core", 3776 + ] 3777 + 3778 + [[package]] 3779 + name = "windows-core" 3780 + version = "0.61.2" 3781 + source = "registry+https://github.com/rust-lang/crates.io-index" 3782 + checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 3783 + dependencies = [ 3784 + "windows-implement", 3785 + "windows-interface", 3786 + "windows-link", 3787 + "windows-result", 3788 + "windows-strings", 3789 + ] 3790 + 3791 + [[package]] 3792 + name = "windows-future" 3793 + version = "0.2.1" 3794 + source = "registry+https://github.com/rust-lang/crates.io-index" 3795 + checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" 3796 + dependencies = [ 3797 + "windows-core", 3798 + "windows-link", 3799 + "windows-threading", 3800 + ] 3801 + 3802 + [[package]] 3803 + name = "windows-implement" 3804 + version = "0.60.0" 3805 + source = "registry+https://github.com/rust-lang/crates.io-index" 3806 + checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 3807 + dependencies = [ 3808 + "proc-macro2", 3809 + "quote", 3810 + "syn", 3811 + ] 3812 + 3813 + [[package]] 3814 + name = "windows-interface" 3815 + version = "0.59.1" 3816 + source = "registry+https://github.com/rust-lang/crates.io-index" 3817 + checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 3818 + dependencies = [ 3819 + "proc-macro2", 3820 + "quote", 3821 + "syn", 3822 + ] 3823 + 3824 + [[package]] 3825 + name = "windows-link" 3826 + version = "0.1.3" 3827 + source = "registry+https://github.com/rust-lang/crates.io-index" 3828 + checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 3829 + 3830 + [[package]] 3831 + name = "windows-numerics" 3832 + version = "0.2.0" 3833 + source = "registry+https://github.com/rust-lang/crates.io-index" 3834 + checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" 3835 + dependencies = [ 3836 + "windows-core", 3837 + "windows-link", 3838 + ] 3839 + 3840 + [[package]] 3841 + name = "windows-result" 3842 + version = "0.3.4" 3843 + source = "registry+https://github.com/rust-lang/crates.io-index" 3844 + checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 3845 + dependencies = [ 3846 + "windows-link", 3847 + ] 3848 + 3849 + [[package]] 3850 + name = "windows-strings" 3851 + version = "0.4.2" 3852 + source = "registry+https://github.com/rust-lang/crates.io-index" 3853 + checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 3854 + dependencies = [ 3855 + "windows-link", 3856 + ] 3857 + 3858 + [[package]] 3859 + name = "windows-sys" 3860 + version = "0.48.0" 3861 + source = "registry+https://github.com/rust-lang/crates.io-index" 3862 + checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 3863 + dependencies = [ 3864 + "windows-targets 0.48.5", 3865 + ] 3866 + 3867 + [[package]] 635 3868 name = "windows-sys" 636 3869 version = "0.52.0" 637 3870 source = "registry+https://github.com/rust-lang/crates.io-index" 638 3871 checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 639 3872 dependencies = [ 640 - "windows-targets", 3873 + "windows-targets 0.52.6", 641 3874 ] 642 3875 643 3876 [[package]] ··· 646 3879 source = "registry+https://github.com/rust-lang/crates.io-index" 647 3880 checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 648 3881 dependencies = [ 649 - "windows-targets", 3882 + "windows-targets 0.52.6", 3883 + ] 3884 + 3885 + [[package]] 3886 + name = "windows-targets" 3887 + version = "0.48.5" 3888 + source = "registry+https://github.com/rust-lang/crates.io-index" 3889 + checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 3890 + dependencies = [ 3891 + "windows_aarch64_gnullvm 0.48.5", 3892 + "windows_aarch64_msvc 0.48.5", 3893 + "windows_i686_gnu 0.48.5", 3894 + "windows_i686_msvc 0.48.5", 3895 + "windows_x86_64_gnu 0.48.5", 3896 + "windows_x86_64_gnullvm 0.48.5", 3897 + "windows_x86_64_msvc 0.48.5", 650 3898 ] 651 3899 652 3900 [[package]] ··· 655 3903 source = "registry+https://github.com/rust-lang/crates.io-index" 656 3904 checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 657 3905 dependencies = [ 658 - "windows_aarch64_gnullvm", 659 - "windows_aarch64_msvc", 660 - "windows_i686_gnu", 3906 + "windows_aarch64_gnullvm 0.52.6", 3907 + "windows_aarch64_msvc 0.52.6", 3908 + "windows_i686_gnu 0.52.6", 661 3909 "windows_i686_gnullvm", 662 - "windows_i686_msvc", 663 - "windows_x86_64_gnu", 664 - "windows_x86_64_gnullvm", 665 - "windows_x86_64_msvc", 3910 + "windows_i686_msvc 0.52.6", 3911 + "windows_x86_64_gnu 0.52.6", 3912 + "windows_x86_64_gnullvm 0.52.6", 3913 + "windows_x86_64_msvc 0.52.6", 3914 + ] 3915 + 3916 + [[package]] 3917 + name = "windows-threading" 3918 + version = "0.1.0" 3919 + source = "registry+https://github.com/rust-lang/crates.io-index" 3920 + checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" 3921 + dependencies = [ 3922 + "windows-link", 666 3923 ] 667 3924 668 3925 [[package]] 669 3926 name = "windows_aarch64_gnullvm" 3927 + version = "0.48.5" 3928 + source = "registry+https://github.com/rust-lang/crates.io-index" 3929 + checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 3930 + 3931 + [[package]] 3932 + name = "windows_aarch64_gnullvm" 670 3933 version = "0.52.6" 671 3934 source = "registry+https://github.com/rust-lang/crates.io-index" 672 3935 checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 673 3936 674 3937 [[package]] 675 3938 name = "windows_aarch64_msvc" 3939 + version = "0.48.5" 3940 + source = "registry+https://github.com/rust-lang/crates.io-index" 3941 + checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 3942 + 3943 + [[package]] 3944 + name = "windows_aarch64_msvc" 676 3945 version = "0.52.6" 677 3946 source = "registry+https://github.com/rust-lang/crates.io-index" 678 3947 checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 679 3948 680 3949 [[package]] 681 3950 name = "windows_i686_gnu" 3951 + version = "0.48.5" 3952 + source = "registry+https://github.com/rust-lang/crates.io-index" 3953 + checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 3954 + 3955 + [[package]] 3956 + name = "windows_i686_gnu" 682 3957 version = "0.52.6" 683 3958 source = "registry+https://github.com/rust-lang/crates.io-index" 684 3959 checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" ··· 691 3966 692 3967 [[package]] 693 3968 name = "windows_i686_msvc" 3969 + version = "0.48.5" 3970 + source = "registry+https://github.com/rust-lang/crates.io-index" 3971 + checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 3972 + 3973 + [[package]] 3974 + name = "windows_i686_msvc" 694 3975 version = "0.52.6" 695 3976 source = "registry+https://github.com/rust-lang/crates.io-index" 696 3977 checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 697 3978 698 3979 [[package]] 699 3980 name = "windows_x86_64_gnu" 3981 + version = "0.48.5" 3982 + source = "registry+https://github.com/rust-lang/crates.io-index" 3983 + checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 3984 + 3985 + [[package]] 3986 + name = "windows_x86_64_gnu" 700 3987 version = "0.52.6" 701 3988 source = "registry+https://github.com/rust-lang/crates.io-index" 702 3989 checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 703 3990 704 3991 [[package]] 705 3992 name = "windows_x86_64_gnullvm" 3993 + version = "0.48.5" 3994 + source = "registry+https://github.com/rust-lang/crates.io-index" 3995 + checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 3996 + 3997 + [[package]] 3998 + name = "windows_x86_64_gnullvm" 706 3999 version = "0.52.6" 707 4000 source = "registry+https://github.com/rust-lang/crates.io-index" 708 4001 checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 4002 + 4003 + [[package]] 4004 + name = "windows_x86_64_msvc" 4005 + version = "0.48.5" 4006 + source = "registry+https://github.com/rust-lang/crates.io-index" 4007 + checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 709 4008 710 4009 [[package]] 711 4010 name = "windows_x86_64_msvc" 712 4011 version = "0.52.6" 713 4012 source = "registry+https://github.com/rust-lang/crates.io-index" 714 4013 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 4014 + 4015 + [[package]] 4016 + name = "winnow" 4017 + version = "0.7.13" 4018 + source = "registry+https://github.com/rust-lang/crates.io-index" 4019 + checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" 4020 + dependencies = [ 4021 + "memchr", 4022 + ] 4023 + 4024 + [[package]] 4025 + name = "winreg" 4026 + version = "0.50.0" 4027 + source = "registry+https://github.com/rust-lang/crates.io-index" 4028 + checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 4029 + dependencies = [ 4030 + "cfg-if", 4031 + "windows-sys 0.48.0", 4032 + ] 4033 + 4034 + [[package]] 4035 + name = "wit-bindgen-rt" 4036 + version = "0.39.0" 4037 + source = "registry+https://github.com/rust-lang/crates.io-index" 4038 + checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 4039 + dependencies = [ 4040 + "bitflags", 4041 + ] 4042 + 4043 + [[package]] 4044 + name = "writeable" 4045 + version = "0.6.1" 4046 + source = "registry+https://github.com/rust-lang/crates.io-index" 4047 + checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 4048 + 4049 + [[package]] 4050 + name = "yoke" 4051 + version = "0.8.0" 4052 + source = "registry+https://github.com/rust-lang/crates.io-index" 4053 + checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 4054 + dependencies = [ 4055 + "serde", 4056 + "stable_deref_trait", 4057 + "yoke-derive", 4058 + "zerofrom", 4059 + ] 4060 + 4061 + [[package]] 4062 + name = "yoke-derive" 4063 + version = "0.8.0" 4064 + source = "registry+https://github.com/rust-lang/crates.io-index" 4065 + checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 4066 + dependencies = [ 4067 + "proc-macro2", 4068 + "quote", 4069 + "syn", 4070 + "synstructure", 4071 + ] 4072 + 4073 + [[package]] 4074 + name = "zerocopy" 4075 + version = "0.8.26" 4076 + source = "registry+https://github.com/rust-lang/crates.io-index" 4077 + checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" 4078 + dependencies = [ 4079 + "zerocopy-derive", 4080 + ] 4081 + 4082 + [[package]] 4083 + name = "zerocopy-derive" 4084 + version = "0.8.26" 4085 + source = "registry+https://github.com/rust-lang/crates.io-index" 4086 + checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" 4087 + dependencies = [ 4088 + "proc-macro2", 4089 + "quote", 4090 + "syn", 4091 + ] 4092 + 4093 + [[package]] 4094 + name = "zerofrom" 4095 + version = "0.1.6" 4096 + source = "registry+https://github.com/rust-lang/crates.io-index" 4097 + checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 4098 + dependencies = [ 4099 + "zerofrom-derive", 4100 + ] 4101 + 4102 + [[package]] 4103 + name = "zerofrom-derive" 4104 + version = "0.1.6" 4105 + source = "registry+https://github.com/rust-lang/crates.io-index" 4106 + checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 4107 + dependencies = [ 4108 + "proc-macro2", 4109 + "quote", 4110 + "syn", 4111 + "synstructure", 4112 + ] 4113 + 4114 + [[package]] 4115 + name = "zeroize" 4116 + version = "1.8.1" 4117 + source = "registry+https://github.com/rust-lang/crates.io-index" 4118 + checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 4119 + dependencies = [ 4120 + "serde", 4121 + ] 4122 + 4123 + [[package]] 4124 + name = "zerotrie" 4125 + version = "0.2.2" 4126 + source = "registry+https://github.com/rust-lang/crates.io-index" 4127 + checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 4128 + dependencies = [ 4129 + "displaydoc", 4130 + "yoke", 4131 + "zerofrom", 4132 + ] 4133 + 4134 + [[package]] 4135 + name = "zerovec" 4136 + version = "0.11.2" 4137 + source = "registry+https://github.com/rust-lang/crates.io-index" 4138 + checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" 4139 + dependencies = [ 4140 + "yoke", 4141 + "zerofrom", 4142 + "zerovec-derive", 4143 + ] 4144 + 4145 + [[package]] 4146 + name = "zerovec-derive" 4147 + version = "0.11.1" 4148 + source = "registry+https://github.com/rust-lang/crates.io-index" 4149 + checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 4150 + dependencies = [ 4151 + "proc-macro2", 4152 + "quote", 4153 + "syn", 4154 + ]
+23 -5
Cargo.toml
··· 1 - [package] 2 - name = "at-advent" 3 - version = "0.1.0" 4 - edition = "2024" 1 + [workspace] 2 + members = ["listener", "shared", "web"] 3 + resolver = "2" 5 4 6 - [dependencies] 5 + 6 + [workspace.dependencies] 7 7 axum = "0.8.4" 8 + atrium-common = "0.1.2" 9 + atrium-api = "0.25.4" 10 + atrium-identity = "0.1.5" 11 + atrium-oauth = "0.1.3" 12 + chrono = { version = "0.4", features = ["serde", "now"] } 13 + hickory-resolver = "0.24.1" 14 + dotenv = "0.15.0" 15 + log = "0.4.24" 16 + serde = { version = "1.0.219", features = ["derive"] } 8 17 serde_json = "1.0.141" 18 + shared = { path = "./shared" } 19 + sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "chrono", "macros"] } 20 + tracing = "0.1.41" 21 + tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } 22 + bb8 = "0.9.0" 23 + bb8-redis = "0.24.0" 24 + redis = "0.32.4" 9 25 tokio = { version = "1.46.1", features = ["full"] } 26 + markdown = "1.0.0" 27 + rust-embed = { version = "8.7.2", features = ["include-exclude"] }
+31
README.md
··· 1 + # at://advent 2 + 3 + An upcoming Holiday Advent Calendar for atprotocol theme challenges 4 + 5 + [Read the initial draft plans here!](docs/initial_plans.md) 6 + 7 + # Quick setup 8 + 9 + WIP 10 + 11 + ## Project break down 12 + 13 + - [./web](./web) - An axum web service to host the advent website 14 + - [./listener](./listener) - A JetStream listener 15 + - [./shared](./shared) - Shared code between the 2, dbs, cache, etc 16 + 17 + ## DB/Cache 18 + 19 + _All commands assume you're at the root of the project_ 20 + 21 + ## Setup 22 + 23 + 1. Make a copy of [.env.template](.env.template) and name it [.env](.env) 24 + 2. Install postgres/redis, or can use docker compose with `docker compose -f compose.dev.yml up` 25 + 3. Install sqlx-cli `cargo install sqlx-cli` 26 + 4. Run `sqlx migrate run` 27 + 28 + ## Adding a new migration 29 + 30 + 1. Create a new one with `sqlx migrate add {migration name}` 31 + 2. Go to the file created in [./migrations](./migrations) and write the sql for the migration
+35
compose.dev.yml
··· 1 + services: 2 + postgres: 3 + image: postgres:latest 4 + restart: unless-stopped 5 + environment: 6 + POSTGRES_USER: ${DB_USER} 7 + POSTGRES_PASSWORD: ${DB_PASSWORD} 8 + POSTGRES_DB: ${DB_NAME} 9 + ports: 10 + - "5432:5432" 11 + volumes: 12 + - advent-postgres-data:/var/lib/postgresql/data 13 + extra_hosts: 14 + - "host.docker.internal:host-gateway" 15 + networks: 16 + - advent-network 17 + redis: 18 + image: 'redis:alpine' 19 + restart: unless-stopped 20 + ports: 21 + - '${FORWARD_REDIS_PORT:-6379}:6379' 22 + volumes: 23 + - 'advent-redis:/data' 24 + networks: 25 + - advent-network 26 + healthcheck: 27 + test: [ "CMD", "redis-cli", "ping" ] 28 + retries: 3 29 + timeout: 5s 30 + volumes: 31 + advent-postgres-data: 32 + advent-redis: 33 + networks: 34 + advent-network: 35 + driver: bridge
+78
docs/initial_plans.md
··· 1 + ## The Idea 2 + An advent of code for atproto with daily or every other day challenges for users to learn atprotocol. It will not be exactly code challenges, but more so challenges that can be verified automatically. I would like for it to be something we set up and can just enjoy when it goes live without much intervention 3 + 4 + ## Audience 5 + This will most likely target somewhat technical users, but mostly beginners. I think we should start with the simplest thing and go with beginners, if we have enough time and people working on the project the challenges could be 2 part. First is no base knowledge of atprotocol is needed, the 2nd one is a bit more challenging 6 + 7 + ## Venue/Promo/Sign up 8 + I'm thinking a shared Bluesky account that posts about the challenge when they happen and allows people to talk about it on there. We can do a discord, but if we keep it all on bsky I think people would appreciate that 9 + 10 + We host a site that users can sign into and they go there to either verify or view the history of their challenges and if they got it 11 + 12 + ## Rewards 13 + * Labeler that puts things like 1/25(solved 1 of 25)? 14 + * AT Protocol Badges? 15 + * Possible leaderboard? 16 + * Easter egg label for a hidden easter egg 17 + 18 + ## Challenge Ideas 19 + - Write a lexicon and publish to your repo. (ex. i have `dev.baileytownsend.health.steps`) just have to include the users handle, maybe even have them create `dev.baileytownsend.advent`? 20 + - Part 2 could be posting a `com.atproto.lexicon.schema` of the schema to verify along with a dns record? 21 + - Create a TID from a given time and clock id 22 + - Post a blob that is an HTML web page 23 + - find a did's prev handle (explore the plc) 24 + - Hide a tag or something similar in a bsky post 25 + - Advance - host a custom feed with a pinned post 26 + - Advance - host an endpoint to resolve various things handle -> did, did -> handle, public key for user, etc 27 + - Super easy - do atproto app records. Star a repo on tangled, play a game of at://2048, etc. Help spread awareness of atproto apps 28 + 29 + ## Challenge flow for user 30 + 1. New challenge at 00:00 UTC. Account auto posts it and it's unlocked 31 + 2. User goes to the advent site, reads what it is 32 + 1. Will also include some hints like "maybe read this atproto doc link" 33 + 3. Creates a record,dns, record, or enter a value to verify they completed the challenge 34 + 4. If successful we write a `app.advent.day.one` record to their pds, and a `app.advent.verified` record to the host account pds that can be used to verified that we did think they completed it 35 + 5. user gets a updated label 36 + 6. they can share a post that says they completed it 37 + 38 + 39 + ## Features 40 + The minimal features to make sure we get something out there 41 + * 12 challenges for 1-24 on every other day? 42 + * Automatic new challenges and verifying so we can enjoy the event 43 + * Unlocks at 00:00utc and shows on the site 44 + * advent account posts about it 45 + * Beginner friendly 46 + * Site to host challenges and backend to verify either manually or via jet stream? 47 + * labeler 48 + 49 + extended features 50 + * 24-48 challenges 1-2 a day. First is beginner friendly second builds on it and is a bit harder 51 + * leaderboard of who has the most done and who did it the fastest 52 + * account post first person daily to do it even if we dont do a leaderboard may be atleast a fun in the middle thing 53 + 54 + 55 + ## Technical 56 + 57 + * main site - We will be using Rust with axum and host it as a static site with a templating engine. My rust status sphere example has a working oauth login for static sites so it should be plug and play 58 + * JetStream listener- probably rust too 59 + * Labeler - i've never made one but i think theres a ts template so may just use it 60 + * https://gist.github.com/goeo-/58b75fa8661e54278a7b6274ad021160 61 + 62 + 63 + ## Sign ups 64 + ### Backend/Jetstream work 65 + * oppi.li 66 + * placeholder 67 + ### Challenge creator 68 + * placeholder 69 + * placeholder 70 + ### Frontend designer/creator 71 + * placeholder 72 + * placeholder 73 + ## Account admin (someone who posts to the account hyping it up) 74 + * placeholder 75 + ## Mascot 76 + * psingletary 77 + ## infra/hosting/ops 78 + * phil
+6
listener/Cargo.toml
··· 1 + [package] 2 + name = "listener" 3 + version = "0.1.0" 4 + edition = "2024" 5 + 6 + [dependencies]
+3
listener/src/main.rs
··· 1 + fn main() { 2 + println!("Hello, world!"); 3 + }
+6
migrations/20250727151941_test_table.sql
··· 1 + -- Add migration script here 2 + CREATE TABLE IF NOT EXISTS test_table 3 + ( 4 + id BIGSERIAL PRIMARY KEY, 5 + test TEXT NOT NULL 6 + );
+17
migrations/20250904073900_create_challenges.sql
··· 1 + -- Advent challenges table 2 + CREATE TABLE IF NOT EXISTS challenges ( 3 + id BIGSERIAL PRIMARY KEY, 4 + user_did TEXT NOT NULL, 5 + day INT NOT NULL, 6 + time_started TIMESTAMPTZ NOT NULL DEFAULT NOW(), 7 + time_challenge_one_completed TIMESTAMPTZ NULL, 8 + time_challenge_two_completed TIMESTAMPTZ NULL, 9 + verification_code_one TEXT NULL, 10 + verification_code_two TEXT NULL, 11 + CONSTRAINT challenges_user_day_unique UNIQUE(user_did, day), 12 + CONSTRAINT challenges_day_range CHECK (day >= 1 AND day <= 25) 13 + ); 14 + 15 + -- Indexes to speed up common lookups 16 + CREATE INDEX IF NOT EXISTS idx_challenges_user_did ON challenges(user_did); 17 + CREATE INDEX IF NOT EXISTS idx_challenges_day ON challenges(day);
+24
shared/Cargo.toml
··· 1 + [package] 2 + name = "shared" 3 + version = "0.1.0" 4 + edition = "2024" 5 + 6 + [dependencies] 7 + axum.workspace = true 8 + atrium-common.workspace = true 9 + atrium-api.workspace = true 10 + atrium-identity.workspace = true 11 + atrium-oauth.workspace = true 12 + bb8.workspace = true 13 + bb8-redis.workspace = true 14 + hickory-resolver.workspace = true 15 + serde = { version = "1.0.219", features = ["derive"] } 16 + sqlx.workspace = true 17 + thiserror = "1.0.69" 18 + serde_json.workspace = true 19 + log.workspace = true 20 + rust-embed.workspace = true 21 + markdown.workspace = true 22 + rand = "0.9.2" 23 + handlebars = { version = "6.3.2" } 24 + async-trait = "0.1.88"
+15
shared/challenges_markdown/one/part_one.md
··· 1 + Hey! Welcome to at://advent! A 25 day challenge to learn atproto with a new set of challenges every day. 2 + 3 + (Pretend this is going into more details explaining everything) 4 + 5 + Starting out simple, create a record at the collection `codes.advent.challenge.day` 6 + with the record key `1` and put this as the record. 7 + 8 + ```json 9 + { 10 + "$type": "codes.advent.challenge.day", 11 + "partOne": "{{code}}" 12 + } 13 + ``` 14 + 15 + [//]: # (<input type="file" id="part_one_input" placeholder="Enter your code here" />)
+7
shared/challenges_markdown/one/part_two.md
··· 1 + Great job beating Part 1! Now onto Part 2. 2 + 3 + Keeping it simple proof of concept, blah, blah will have a real one here another time. Add a new field `partTwo` to the 4 + record with the value `{{code}}` 5 + 6 + 7 + [//]: # (<input type="file" id="part_one_input" placeholder="Enter your code here" />)
+28
shared/lexicons/codes/advent/day.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "codes.advent.challenge.day", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "any", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "partOne" 12 + ], 13 + "properties": { 14 + "partOne": { 15 + "type": "string" 16 + }, 17 + "partTwo": { 18 + "type": "string" 19 + }, 20 + "createdAt": { 21 + "type": "string", 22 + "format": "datetime" 23 + } 24 + } 25 + } 26 + } 27 + } 28 + }
+188
shared/src/advent/challenges/day_one.rs
··· 1 + use crate::OAuthAgentType; 2 + use crate::advent::day::Day; 3 + use crate::advent::{AdventChallenge, AdventError, ChallengeCheckResponse}; 4 + use crate::atrium::safe_check_unknown_record_parse; 5 + use crate::lexicons::codes::advent; 6 + use async_trait::async_trait; 7 + use atrium_api::types::Collection; 8 + use sqlx::PgPool; 9 + 10 + pub struct DayOne { 11 + pub pool: PgPool, 12 + pub oauth_client: Option<OAuthAgentType>, 13 + } 14 + 15 + #[async_trait] 16 + impl AdventChallenge for DayOne { 17 + fn pool(&self) -> &PgPool { 18 + &self.pool 19 + } 20 + 21 + fn day(&self) -> Day { 22 + Day::One 23 + } 24 + 25 + fn has_part_two(&self) -> bool { 26 + true 27 + } 28 + 29 + async fn check_part_one( 30 + &self, 31 + did: String, 32 + _verification_code: Option<String>, 33 + ) -> Result<ChallengeCheckResponse, AdventError> { 34 + let client = self 35 + .oauth_client 36 + .as_ref() 37 + .ok_or(AdventError::ShouldNotHappen( 38 + "No oauth client. This should not happen".to_string(), 39 + ))?; 40 + 41 + let record_res = client 42 + .api 43 + .com 44 + .atproto 45 + .repo 46 + .get_record( 47 + atrium_api::com::atproto::repo::get_record::ParametersData { 48 + cid: None, 49 + collection: advent::challenge::Day::NSID.parse().unwrap(), 50 + repo: did.parse().unwrap(), 51 + rkey: "1".parse().unwrap(), 52 + } 53 + .into(), 54 + ) 55 + .await; 56 + 57 + let record = match record_res { 58 + Ok(r) => r, 59 + Err(e) => { 60 + log::error!("Error getting record: {}", e); 61 + return Ok(ChallengeCheckResponse::Incorrect("Does not appear to be a record in your repo in the collection codes.advent.challenge.day with the record key of 1".to_string())); 62 + } 63 + }; 64 + 65 + let Some(challenge) = self.get_days_challenge(&did).await? else { 66 + log::error!("Could not find a challenge record for day: 1 for the user: {did:?}"); 67 + return Err(AdventError::ShouldNotHappen( 68 + "Could not find challenge record".to_string(), 69 + )); 70 + }; 71 + 72 + let record_data = match safe_check_unknown_record_parse::<advent::challenge::day::RecordData>( 73 + record.value.clone(), 74 + ) { 75 + Ok(rd) => rd, 76 + Err(e) => { 77 + log::error!("Error parsing record: {e}"); 78 + return Ok(ChallengeCheckResponse::Incorrect(format!( 79 + "There is a record at the correct location, but it does not seem like it is correct. Try again:\n{e}" 80 + ))); 81 + } 82 + }; 83 + 84 + let verification_code = 85 + challenge 86 + .verification_code_one 87 + .ok_or(AdventError::ShouldNotHappen( 88 + "no verification code for day 1 challenge :/".to_string(), 89 + ))?; 90 + 91 + Ok(if record_data.part_one == verification_code { 92 + ChallengeCheckResponse::Correct 93 + } else { 94 + ChallengeCheckResponse::Incorrect(format!( 95 + "The code {} is incorrect", 96 + record_data.part_one 97 + )) 98 + }) 99 + } 100 + 101 + ///TODO this is just a straight copy and paste of part one since it's a proof of concept needs to share code better between the two 102 + async fn check_part_two( 103 + &self, 104 + did: String, 105 + _verification_code: Option<String>, 106 + ) -> Result<ChallengeCheckResponse, AdventError> { 107 + match &self.oauth_client { 108 + None => Err(AdventError::ShouldNotHappen( 109 + "No oauth client. This should not happen".to_string(), 110 + )), 111 + Some(client) => { 112 + match client 113 + .api 114 + .com 115 + .atproto 116 + .repo 117 + .get_record( 118 + atrium_api::com::atproto::repo::get_record::ParametersData { 119 + cid: None, 120 + collection: advent::challenge::Day::NSID.parse().unwrap(), 121 + repo: did.parse().unwrap(), 122 + rkey: "1".parse().unwrap(), 123 + } 124 + .into(), 125 + ) 126 + .await 127 + { 128 + Ok(record) => { 129 + //TODO trouble, and make it double 130 + let challenge = self.get_days_challenge(&did).await?; 131 + 132 + match challenge { 133 + None => { 134 + log::error!( 135 + "Could not find a challenge record for day: {} for the user: {}", 136 + self.day(), 137 + did.clone() 138 + ); 139 + Err(AdventError::ShouldNotHappen( 140 + "Could not find a challenge record".to_string(), 141 + )) 142 + } 143 + Some(challenge) => { 144 + let parse_record_result = 145 + safe_check_unknown_record_parse::< 146 + advent::challenge::day::RecordData, 147 + >(record.value.clone()); 148 + 149 + match parse_record_result { 150 + Ok(record_data) => match record_data.part_two { 151 + None => { 152 + Ok(ChallengeCheckResponse::Incorrect("The record is there, it's the right kind. But aren't you forgetting something?".to_string())) 153 + } 154 + Some(part_two_code) => { 155 + match part_two_code 156 + == challenge 157 + .verification_code_two 158 + .unwrap_or("".to_string()) 159 + { 160 + true => Ok(ChallengeCheckResponse::Correct), 161 + false => { 162 + Ok(ChallengeCheckResponse::Incorrect(format!( 163 + "The code {} is incorrect", 164 + record_data.part_one 165 + ))) 166 + } 167 + } 168 + } 169 + }, 170 + Err(err) => { 171 + log::error!("Error parsing record: {}", err); 172 + Ok(ChallengeCheckResponse::Incorrect(format!( 173 + "There is a record at the correct location, but it does not seem like it is correct. Try again:\n{err}" 174 + ))) 175 + } 176 + } 177 + } 178 + } 179 + } 180 + Err(err) => { 181 + log::error!("Error getting record: {}", err); 182 + Ok(ChallengeCheckResponse::Incorrect("Does not appear to be a record in your repo in the collection codes.advent.challenge.day with the record key of 1".to_string())) 183 + } 184 + } 185 + } 186 + } 187 + } 188 + }
+33
shared/src/advent/challenges/day_two.rs
··· 1 + use crate::OAuthAgentType; 2 + use crate::advent::day::Day; 3 + use crate::advent::{AdventChallenge, AdventError, ChallengeCheckResponse}; 4 + use async_trait::async_trait; 5 + use sqlx::PgPool; 6 + 7 + pub struct DayTwo { 8 + pub pool: PgPool, 9 + pub oauth_client: Option<OAuthAgentType>, 10 + } 11 + 12 + #[async_trait] 13 + impl AdventChallenge for DayTwo { 14 + fn pool(&self) -> &PgPool { 15 + &self.pool 16 + } 17 + 18 + fn day(&self) -> Day { 19 + Day::Two 20 + } 21 + 22 + fn has_part_two(&self) -> bool { 23 + false 24 + } 25 + 26 + async fn check_part_one( 27 + &self, 28 + _did: String, 29 + _verification_code: Option<String>, 30 + ) -> Result<ChallengeCheckResponse, AdventError> { 31 + todo!() 32 + } 33 + }
+2
shared/src/advent/challenges/mod.rs
··· 1 + pub mod day_one; 2 + pub mod day_two;
+171
shared/src/advent/day.rs
··· 1 + /// Decided to just go with an enum for the day. Seems a bit silly, but seemed the easiest way to do matches and translate from "1" to "One" etc without a dependency on a crate. 2 + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 3 + pub enum Day { 4 + One = 1, 5 + Two = 2, 6 + Three = 3, 7 + Four = 4, 8 + Five = 5, 9 + Six = 6, 10 + Seven = 7, 11 + Eight = 8, 12 + Nine = 9, 13 + Ten = 10, 14 + Eleven = 11, 15 + Twelve = 12, 16 + Thirteen = 13, 17 + Fourteen = 14, 18 + Fifteen = 15, 19 + Sixteen = 16, 20 + Seventeen = 17, 21 + Eighteen = 18, 22 + Nineteen = 19, 23 + Twenty = 20, 24 + TwentyOne = 21, 25 + TwentyTwo = 22, 26 + TwentyThree = 23, 27 + TwentyFour = 24, 28 + TwentyFive = 25, 29 + } 30 + 31 + impl From<Day> for u8 { 32 + fn from(day: Day) -> Self { 33 + match day { 34 + Day::One => 1, 35 + Day::Two => 2, 36 + Day::Three => 3, 37 + Day::Four => 4, 38 + Day::Five => 5, 39 + Day::Six => 6, 40 + Day::Seven => 7, 41 + Day::Eight => 8, 42 + Day::Nine => 9, 43 + Day::Ten => 10, 44 + Day::Eleven => 11, 45 + Day::Twelve => 12, 46 + Day::Thirteen => 13, 47 + Day::Fourteen => 14, 48 + Day::Fifteen => 15, 49 + Day::Sixteen => 16, 50 + Day::Seventeen => 17, 51 + Day::Eighteen => 18, 52 + Day::Nineteen => 19, 53 + Day::Twenty => 20, 54 + Day::TwentyOne => 21, 55 + Day::TwentyTwo => 22, 56 + Day::TwentyThree => 23, 57 + Day::TwentyFour => 24, 58 + Day::TwentyFive => 25, 59 + } 60 + } 61 + } 62 + 63 + impl core::fmt::Display for Day { 64 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 65 + f.write_str(match self { 66 + Day::One => "One", 67 + Day::Two => "Two", 68 + Day::Three => "Three", 69 + Day::Four => "Four", 70 + Day::Five => "Five", 71 + Day::Six => "Six", 72 + Day::Seven => "Seven", 73 + Day::Eight => "Eight", 74 + Day::Nine => "Nine", 75 + Day::Ten => "Ten", 76 + Day::Eleven => "Eleven", 77 + Day::Twelve => "Twelve", 78 + Day::Thirteen => "Thirteen", 79 + Day::Fourteen => "Fourteen", 80 + Day::Fifteen => "Fifteen", 81 + Day::Sixteen => "Sixteen", 82 + Day::Seventeen => "Seventeen", 83 + Day::Eighteen => "Eighteen", 84 + Day::Nineteen => "Nineteen", 85 + Day::Twenty => "Twenty", 86 + Day::TwentyOne => "TwentyOne", 87 + Day::TwentyTwo => "TwentyTwo", 88 + Day::TwentyThree => "TwentyThree", 89 + Day::TwentyFour => "TwentyFour", 90 + Day::TwentyFive => "TwentyFive", 91 + }) 92 + } 93 + } 94 + 95 + impl From<Day> for String { 96 + fn from(day: Day) -> Self { 97 + day.to_string() 98 + } 99 + } 100 + 101 + impl core::convert::From<u8> for Day { 102 + fn from(value: u8) -> Self { 103 + match value { 104 + 1 => Day::One, 105 + 2 => Day::Two, 106 + 3 => Day::Three, 107 + 4 => Day::Four, 108 + 5 => Day::Five, 109 + 6 => Day::Six, 110 + 7 => Day::Seven, 111 + 8 => Day::Eight, 112 + 9 => Day::Nine, 113 + 10 => Day::Ten, 114 + 11 => Day::Eleven, 115 + 12 => Day::Twelve, 116 + 13 => Day::Thirteen, 117 + 14 => Day::Fourteen, 118 + 15 => Day::Fifteen, 119 + 16 => Day::Sixteen, 120 + 17 => Day::Seventeen, 121 + 18 => Day::Eighteen, 122 + 19 => Day::Nineteen, 123 + 20 => Day::Twenty, 124 + 21 => Day::TwentyOne, 125 + 22 => Day::TwentyTwo, 126 + 23 => Day::TwentyThree, 127 + 24 => Day::TwentyFour, 128 + 25 => Day::TwentyFive, 129 + _ => panic!("day out of range (1..=25)"), 130 + } 131 + } 132 + } 133 + 134 + impl core::convert::From<&str> for Day { 135 + fn from(s: &str) -> Self { 136 + match s { 137 + "One" => Day::One, 138 + "Two" => Day::Two, 139 + "Three" => Day::Three, 140 + "Four" => Day::Four, 141 + "Five" => Day::Five, 142 + "Six" => Day::Six, 143 + "Seven" => Day::Seven, 144 + "Eight" => Day::Eight, 145 + "Nine" => Day::Nine, 146 + "Ten" => Day::Ten, 147 + "Eleven" => Day::Eleven, 148 + "Twelve" => Day::Twelve, 149 + "Thirteen" => Day::Thirteen, 150 + "Fourteen" => Day::Fourteen, 151 + "Fifteen" => Day::Fifteen, 152 + "Sixteen" => Day::Sixteen, 153 + "Seventeen" => Day::Seventeen, 154 + "Eighteen" => Day::Eighteen, 155 + "Nineteen" => Day::Nineteen, 156 + "Twenty" => Day::Twenty, 157 + "TwentyOne" => Day::TwentyOne, 158 + "TwentyTwo" => Day::TwentyTwo, 159 + "TwentyThree" => Day::TwentyThree, 160 + "TwentyFour" => Day::TwentyFour, 161 + "TwentyFive" => Day::TwentyFive, 162 + _ => panic!("unknown day string"), 163 + } 164 + } 165 + } 166 + 167 + impl core::convert::From<String> for Day { 168 + fn from(value: String) -> Self { 169 + Day::from(value.as_str()) 170 + } 171 + }
+326
shared/src/advent/mod.rs
··· 1 + pub mod challenges; 2 + pub mod day; 3 + 4 + use crate::advent::day::Day; 5 + use crate::assets::ChallengesMarkdown; 6 + use crate::models::db_models::ChallengeProgress; 7 + use async_trait::async_trait; 8 + use handlebars::{Handlebars, RenderError}; 9 + use markdown::{CompileOptions, Options}; 10 + use rand::distr::{Alphanumeric, SampleString}; 11 + use rust_embed::EmbeddedFile; 12 + use serde_json::json; 13 + use sqlx::PgPool; 14 + use std::str::Utf8Error; 15 + use thiserror::Error; 16 + 17 + #[derive(Debug, Error)] 18 + pub enum AdventError { 19 + #[error("Database error: {0}")] 20 + Database(#[from] sqlx::Error), 21 + #[error("Io error: {0}")] 22 + Io(#[from] std::io::Error), 23 + #[error("Invalid day: {0}. Day must be between 1 and 25")] 24 + InvalidDay(i32), 25 + #[error("This challenge only has a single challenge")] 26 + NoPartTwo, 27 + #[error("UTF-8 error: {0}")] 28 + Utf8Error(#[from] Utf8Error), 29 + #[error("Render error: {0}")] 30 + RenderError(#[from] RenderError), 31 + #[error("This was not designed to happen: {0}")] 32 + ShouldNotHappen(String), 33 + } 34 + 35 + pub enum AdventAction { 36 + //If Part one is done it shows both, if not it only shows part one 37 + // the Option<String> here is the did of the user. If none only show part one no matter what 38 + ViewChallenge(Option<String>), 39 + 40 + //The strings here are the users did's 41 + StartPartOne(String), 42 + StartPartTwo(String), 43 + 44 + //TODO should I have like SubmitPartOne(String) and it's the code? 45 + //These actions will be locked behind logged in 46 + SubmitPartOne, 47 + SubmitPartTwo, 48 + } 49 + 50 + pub enum AdventPart { 51 + One, 52 + Two, 53 + } 54 + 55 + pub enum CompletionStatus { 56 + ///None of the day's challenges have been completed 57 + None, 58 + ///PartOne of the day's challenges has been completed 59 + PartOne, 60 + ///PartTwo of the day's challenges has been completed 61 + //i dont think this was needed 62 + // PartTwo, 63 + ///Both of the day's challenges have been completed 64 + Both, 65 + } 66 + 67 + pub enum ChallengeCheckResponse { 68 + Correct, 69 + ///Error message on why it was incorrect 70 + Incorrect(String), 71 + } 72 + 73 + pub enum AdventActionResult { 74 + ShowPartOne, 75 + // If partone is completed, this will be shown 76 + ShowPartTwo, 77 + 78 + CorrectSubmission(AdventPart), 79 + IncorrectSubmission, 80 + 81 + Completed, 82 + 83 + Error(String), 84 + } 85 + 86 + #[async_trait] 87 + pub trait AdventChallenge { 88 + /// The db pool in case the challenge needs extra access 89 + fn pool(&self) -> &PgPool; 90 + 91 + /// The day of the challenge 1-25 92 + fn day(&self) -> Day; 93 + 94 + fn get_day_markdown_file(&self, part: AdventPart) -> Result<Option<EmbeddedFile>, AdventError> { 95 + let day = self.day().to_string().to_ascii_lowercase(); 96 + let path = match part { 97 + AdventPart::One => format!("{day}/part_one.md"), 98 + AdventPart::Two => format!("{day}/part_two.md"), 99 + }; 100 + match ChallengesMarkdown::get(path.as_str()) { 101 + None => { 102 + log::error!("Missing the part one challenge file for day: {}", day); 103 + Ok(None) 104 + } 105 + Some(day_one_file) => Ok(Some(day_one_file)), 106 + } 107 + } 108 + 109 + /// Does the day have a part two challenge? 110 + fn has_part_two(&self) -> bool; 111 + //Commenting this out and just going leave it to who makes the impl. This is less code to write, 112 + // but really it's a faster response to just have the author put true or false 113 + // 114 + // { 115 + // match self.get_day_markdown_file(AdventPart::Two) { 116 + // Ok(_) => true, 117 + // Err(_) => false, 118 + // } 119 + // } 120 + 121 + /// The text Markdown for challenge 1 122 + fn markdown_text_part_one( 123 + &self, 124 + verification_code: Option<String>, 125 + ) -> Result<String, AdventError> { 126 + let day = self.day(); 127 + 128 + //May acutally leave this unwrap or put a panic in case it doesn't exist since it is needed to have a part one 129 + match self.get_day_markdown_file(AdventPart::One)? { 130 + None => { 131 + log::error!("Missing the part one challenge file for day: {}", day); 132 + Ok("Someone let the admins know this page is missing. No, this is not an Easter egg, it's an actual bug".to_string()) 133 + } 134 + Some(day_one_file) => { 135 + //TODO probably should be a shared variable, but prototyping 136 + let reg = Handlebars::new(); 137 + 138 + let day_one_text = std::str::from_utf8(day_one_file.data.as_ref())?; 139 + let code = verification_code.unwrap_or_else(|| "Login to get a code".to_string()); 140 + let handlebar_rendered = 141 + reg.render_template(day_one_text, &json!({"code": code}))?; 142 + 143 + Ok( 144 + markdown::to_html_with_options(&handlebar_rendered, &get_markdown_options()) 145 + .unwrap(), 146 + ) 147 + } 148 + } 149 + } 150 + 151 + /// The text Markdown for challenge 2, could be None 152 + fn markdown_text_part_two( 153 + &self, 154 + verification_code: Option<String>, 155 + ) -> Result<Option<String>, AdventError> { 156 + match self.get_day_markdown_file(AdventPart::Two)? { 157 + None => Ok(None), 158 + Some(day_two_file) => { 159 + //TODO probably should be a shared variable, but prototyping 160 + let reg = Handlebars::new(); 161 + 162 + let day_two_text = std::str::from_utf8(day_two_file.data.as_ref())?; 163 + let code = verification_code.unwrap_or_else(|| "Login to get a code".to_string()); 164 + let handlebar_rendered = 165 + reg.render_template(day_two_text, &json!({"code": code}))?; 166 + 167 + Ok(Some( 168 + markdown::to_html_with_options(&handlebar_rendered, &get_markdown_options()) 169 + .unwrap(), 170 + )) 171 + } 172 + } 173 + } 174 + 175 + /// Checks to see if the day's challenge had been started for the user 176 + async fn day_started(&self, did: &str) -> Result<bool, AdventError> { 177 + let exists = sqlx::query_scalar::<_, i64>( 178 + "SELECT id FROM challenges WHERE user_did = $1 AND day = $2 LIMIT 1", 179 + ) 180 + .bind(did) 181 + .bind(self.day() as i16) 182 + .fetch_optional(self.pool()) 183 + .await?; 184 + Ok(exists.is_some()) 185 + } 186 + 187 + async fn get_days_challenge( 188 + &self, 189 + did: &str, 190 + ) -> Result<Option<ChallengeProgress>, AdventError> { 191 + Ok(sqlx::query_as::<_, ChallengeProgress>( 192 + "SELECT * FROM challenges WHERE user_did = $1 AND day = $2", 193 + ) 194 + .bind(did) 195 + .bind(self.day() as i16) 196 + .fetch_optional(self.pool()) 197 + .await?) 198 + } 199 + 200 + async fn start_challenge(&self, did: String, part: AdventPart) -> Result<String, AdventError> { 201 + let code = get_random_token(); 202 + match part { 203 + AdventPart::One => sqlx::query( 204 + "INSERT INTO challenges (user_did, day, time_started, verification_code_one) 205 + VALUES ($1, $2, NOW(), $3) 206 + ON CONFLICT (user_did, day) 207 + DO UPDATE SET verification_code_one = $3 208 + WHERE challenges.user_did = $1 AND challenges.day = $2", 209 + ), 210 + //TODO just going leave these as an update. It should never ideally be an insert 211 + AdventPart::Two => sqlx::query( 212 + "UPDATE challenges 213 + SET verification_code_two = $3 214 + WHERE challenges.user_did = $1 AND challenges.day = $2", 215 + ), 216 + } 217 + .bind(did) 218 + .bind(self.day() as i16) 219 + .bind(code.clone()) 220 + .execute(self.pool()) 221 + .await?; 222 + Ok(code) 223 + } 224 + 225 + /// Marks the challenge as completed. 226 + async fn complete_part_one(&self, did: String) -> Result<(), AdventError> { 227 + sqlx::query( 228 + "UPDATE challenges 229 + SET time_challenge_one_completed = COALESCE(time_challenge_one_completed, NOW()) 230 + WHERE user_did = $1 AND day = $2", 231 + ) 232 + .bind(did) 233 + .bind(self.day() as i16) 234 + .execute(self.pool()) 235 + .await?; 236 + Ok(()) 237 + } 238 + 239 + /// Marks the challenge as completed. 240 + async fn complete_part_two(&self, did: String) -> Result<(), AdventError> { 241 + sqlx::query( 242 + "UPDATE challenges 243 + SET time_challenge_two_completed = COALESCE(time_challenge_two_completed, NOW()) 244 + WHERE user_did = $1 AND day = $2", 245 + ) 246 + .bind(did) 247 + .bind(self.day() as i16) 248 + .execute(self.pool()) 249 + .await?; 250 + Ok(()) 251 + } 252 + 253 + async fn get_completed_status( 254 + &self, 255 + did: Option<String>, 256 + ) -> Result<CompletionStatus, AdventError> { 257 + match did { 258 + None => Ok(CompletionStatus::None), 259 + Some(did) => { 260 + let day = self.day() as i32; 261 + let result = sqlx::query!( 262 + "SELECT time_challenge_one_completed, time_challenge_two_completed 263 + FROM challenges 264 + WHERE user_did = $1 AND day = $2", 265 + did, 266 + day 267 + ) 268 + .fetch_optional(self.pool()) 269 + .await?; 270 + 271 + Ok(match result { 272 + None => CompletionStatus::None, 273 + Some(row) => match ( 274 + row.time_challenge_one_completed, 275 + row.time_challenge_two_completed, 276 + ) { 277 + (None, None) => CompletionStatus::None, 278 + (Some(_), None) => CompletionStatus::PartOne, 279 + (Some(_), Some(_)) => CompletionStatus::Both, 280 + _ => panic!( 281 + "This should never happen as in part one shouldn't be not done but 2 is" 282 + ), 283 + }, 284 + }) 285 + } 286 + } 287 + } 288 + 289 + ///This is where the magic happens, aka logic to check if the user got it. Verification code is optional cause sometiems you may need to find it somewhere, sometimes maybe the backend does 290 + async fn check_part_one( 291 + &self, 292 + did: String, 293 + verification_code: Option<String>, 294 + ) -> Result<ChallengeCheckResponse, AdventError>; 295 + 296 + ///This is where the magic happens, aka logic to check if the user got it. Verification code is optional cause sometiems you may need to find it somewhere, sometimes maybe the backend does 297 + /// part two does have a hard error if its called and there is not a part 2 298 + async fn check_part_two( 299 + &self, 300 + _did: String, 301 + _verification_code: Option<String>, 302 + ) -> Result<ChallengeCheckResponse, AdventError> { 303 + unimplemented!("Second day challenges are optional") 304 + } 305 + } 306 + 307 + pub fn get_random_token() -> String { 308 + let mut rng = rand::rng(); 309 + 310 + let full_code = Alphanumeric.sample_string(&mut rng, 10); 311 + 312 + let slice_one = &full_code[0..5].to_ascii_uppercase(); 313 + let slice_two = &full_code[5..10].to_ascii_uppercase(); 314 + format!("{slice_one}-{slice_two}") 315 + } 316 + 317 + fn get_markdown_options() -> Options { 318 + Options { 319 + parse: Default::default(), 320 + compile: CompileOptions { 321 + //Setting this to allow HTML in the markdown. So pleas be careful what you put in there 322 + allow_dangerous_html: true, 323 + ..Default::default() 324 + }, 325 + } 326 + }
+6
shared/src/assets.rs
··· 1 + use rust_embed::Embed; 2 + 3 + #[derive(Embed)] 4 + #[folder = "challenges_markdown/"] 5 + #[include = "*.md"] 6 + pub struct ChallengesMarkdown;
+31
shared/src/atrium/dns_resolver.rs
··· 1 + use atrium_identity::handle::DnsTxtResolver; 2 + use hickory_resolver::TokioAsyncResolver; 3 + 4 + /// Setup for dns resolver for the handle resolver 5 + pub struct HickoryDnsTxtResolver { 6 + resolver: hickory_resolver::TokioAsyncResolver, 7 + } 8 + 9 + impl Default for HickoryDnsTxtResolver { 10 + fn default() -> Self { 11 + Self { 12 + resolver: TokioAsyncResolver::tokio_from_system_conf() 13 + .expect("failed to create resolver"), 14 + } 15 + } 16 + } 17 + 18 + impl DnsTxtResolver for HickoryDnsTxtResolver { 19 + async fn resolve( 20 + &self, 21 + query: &str, 22 + ) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync + 'static>> { 23 + Ok(self 24 + .resolver 25 + .txt_lookup(query) 26 + .await? 27 + .iter() 28 + .map(|txt| txt.to_string()) 29 + .collect()) 30 + } 31 + }
+17
shared/src/atrium/mod.rs
··· 1 + use atrium_api::types::Unknown; 2 + use serde::de; 3 + 4 + pub mod dns_resolver; 5 + pub mod stores; 6 + 7 + /// Safely parses an unknown record into a type. If it fails, it logs the error and returns an error. 8 + pub fn safe_check_unknown_record_parse<T>(unknown: Unknown) -> serde_json::Result<T> 9 + where 10 + T: de::DeserializeOwned, 11 + { 12 + let json = serde_json::to_vec(&unknown).map_err(|err| { 13 + log::error!("Error getting the bytes of a record: {}", err); 14 + err 15 + })?; 16 + serde_json::from_slice::<T>(&json) 17 + }
+146
shared/src/atrium/stores.rs
··· 1 + /// Storage impls to persis OAuth sessions if you are not using the memory stores 2 + /// https://github.com/bluesky-social/statusphere-example-app/blob/main/src/auth/storage.ts 3 + use crate::cache::{ 4 + ATRIUM_SESSION_STORE_PREFIX, ATRIUM_STATE_STORE_KEY, Cache, create_prefixed_key, 5 + }; 6 + use atrium_api::types::string::Did; 7 + use atrium_common::store::Store; 8 + use atrium_oauth::store::session::SessionStore; 9 + use atrium_oauth::store::state::StateStore; 10 + use bb8::Pool; 11 + use bb8_redis::RedisConnectionManager; 12 + use bb8_redis::redis::AsyncCommands; 13 + use serde::Serialize; 14 + use serde::de::DeserializeOwned; 15 + use std::fmt::Debug; 16 + use std::hash::Hash; 17 + use thiserror::Error; 18 + 19 + #[derive(Error, Debug)] 20 + pub enum AtriumStoreError { 21 + #[error("No session found")] 22 + NoSessionFound, 23 + #[error("Redis error: {0}")] 24 + RedisError(#[from] bb8_redis::redis::RedisError), 25 + #[error("Serialization error: {0}")] 26 + SerializationError(#[from] serde_json::Error), 27 + #[error("Database error: {0}")] 28 + DatabaseError(#[from] sqlx::Error), 29 + #[error("Pool error: {0}")] 30 + PoolError(#[from] bb8::RunError<bb8_redis::redis::RedisError>), 31 + } 32 + 33 + impl SessionStore for AtriumSessionStore {} 34 + 35 + pub struct AtriumSessionStore { 36 + cache_pool: Pool<RedisConnectionManager>, 37 + } 38 + 39 + impl AtriumSessionStore { 40 + pub fn new(pool: Pool<RedisConnectionManager>) -> Self { 41 + Self { cache_pool: pool } 42 + } 43 + } 44 + 45 + impl<K, V> Store<K, V> for AtriumSessionStore 46 + where 47 + K: Debug + Eq + Hash + Send + Sync + 'static + From<Did> + AsRef<str>, 48 + V: Debug + Clone + Send + Sync + 'static + Serialize + DeserializeOwned, 49 + { 50 + type Error = AtriumStoreError; 51 + async fn get(&self, key: &K) -> Result<Option<V>, Self::Error> { 52 + let key = create_prefixed_key(ATRIUM_SESSION_STORE_PREFIX, key.as_ref()); 53 + let mut cache = self.cache_pool.get().await?; 54 + let value: Option<String> = cache.get(key).await?; 55 + match value { 56 + Some(json_str) => { 57 + let deserialized: V = serde_json::from_str(&json_str)?; 58 + Ok(Some(deserialized)) 59 + } 60 + None => Ok(None), 61 + } 62 + } 63 + 64 + async fn set(&self, key: K, value: V) -> Result<(), Self::Error> { 65 + let cache_key = create_prefixed_key(ATRIUM_SESSION_STORE_PREFIX, key.as_ref()); 66 + let json_value = serde_json::to_string(&value)?; 67 + let mut cache = self.cache_pool.get().await?; 68 + let _: () = cache.set(cache_key, json_value).await?; 69 + Ok(()) 70 + } 71 + 72 + async fn del(&self, key: &K) -> Result<(), Self::Error> { 73 + let cache_key = create_prefixed_key(ATRIUM_SESSION_STORE_PREFIX, key.as_ref()); 74 + let mut cache = self.cache_pool.get().await?; 75 + let _: usize = cache.del(cache_key).await?; 76 + Ok(()) 77 + } 78 + 79 + async fn clear(&self) -> Result<(), Self::Error> { 80 + let pattern = format!("{}*", ATRIUM_SESSION_STORE_PREFIX); 81 + let mut cache = self.cache_pool.get().await?; 82 + let keys: Vec<String> = cache.keys(pattern).await?; 83 + if !keys.is_empty() { 84 + let _: usize = cache.del(keys).await?; 85 + } 86 + Ok(()) 87 + } 88 + } 89 + 90 + impl StateStore for AtriumStateStore {} 91 + 92 + pub struct AtriumStateStore { 93 + cache_pool: Pool<RedisConnectionManager>, 94 + } 95 + 96 + impl AtriumStateStore { 97 + pub fn new(pool: Pool<RedisConnectionManager>) -> Self { 98 + Self { cache_pool: pool } 99 + } 100 + } 101 + 102 + impl<K, V> Store<K, V> for AtriumStateStore 103 + where 104 + K: Debug + Eq + Hash + Send + Sync + 'static + From<Did> + AsRef<str>, 105 + V: Debug + Clone + Send + Sync + 'static + Serialize + DeserializeOwned, 106 + { 107 + type Error = AtriumStoreError; 108 + async fn get(&self, key: &K) -> Result<Option<V>, Self::Error> { 109 + let key = create_prefixed_key(ATRIUM_STATE_STORE_KEY, key.as_ref()); 110 + let mut cache = self.cache_pool.get().await?; 111 + let value: Option<String> = cache.get(key).await?; 112 + match value { 113 + Some(json_str) => { 114 + let deserialized: V = serde_json::from_str(&json_str)?; 115 + Ok(Some(deserialized)) 116 + } 117 + None => Ok(None), 118 + } 119 + } 120 + 121 + async fn set(&self, key: K, value: V) -> Result<(), Self::Error> { 122 + let cache_key = create_prefixed_key(ATRIUM_STATE_STORE_KEY, key.as_ref()); 123 + let mut cache = Cache::new(self.cache_pool.get().await?); 124 + let _ = cache 125 + .write_to_cache_with_seconds(&cache_key, value, 3_6000) 126 + .await?; 127 + Ok(()) 128 + } 129 + 130 + async fn del(&self, key: &K) -> Result<(), Self::Error> { 131 + let cache_key = create_prefixed_key(ATRIUM_STATE_STORE_KEY, key.as_ref()); 132 + let mut cache = self.cache_pool.get().await?; 133 + let _: usize = cache.del(cache_key).await?; 134 + Ok(()) 135 + } 136 + 137 + async fn clear(&self) -> Result<(), Self::Error> { 138 + let pattern = format!("{}*", ATRIUM_STATE_STORE_KEY); 139 + let mut cache = self.cache_pool.get().await?; 140 + let keys: Vec<String> = cache.keys(pattern).await?; 141 + if !keys.is_empty() { 142 + let _: usize = cache.del(keys).await?; 143 + } 144 + Ok(()) 145 + } 146 + }
+158
shared/src/cache.rs
··· 1 + use crate::web_helpers::internal_error; 2 + use atrium_api::xrpc::http::StatusCode; 3 + use atrium_api::xrpc::http::request::Parts; 4 + use axum::extract::{FromRef, FromRequestParts}; 5 + use bb8::{Pool, PooledConnection}; 6 + use bb8_redis::RedisConnectionManager; 7 + use bb8_redis::redis::{AsyncCommands, FromRedisValue, RedisResult}; 8 + use serde::{Deserialize, Serialize}; 9 + use thiserror::Error; 10 + 11 + pub const ATRIUM_SESSION_STORE_PREFIX: &str = "atrium_session:"; 12 + pub const ATRIUM_STATE_STORE_KEY: &str = "atrium_state:"; 13 + 14 + pub const TOWER_SESSION_KEY: &str = "tower_session:"; 15 + 16 + pub fn create_prefixed_key(prefix: &str, key: &str) -> String { 17 + format!("{}{}", prefix, key) 18 + } 19 + 20 + pub struct Cache<'a> { 21 + pub redis_pool: PooledConnection<'a, RedisConnectionManager>, 22 + } 23 + 24 + #[derive(Debug, Error)] 25 + pub enum RedisFetchErrors { 26 + FromDbError, 27 + ParseError, 28 + Other(String), 29 + } 30 + 31 + impl std::fmt::Display for RedisFetchErrors { 32 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 33 + match self { 34 + RedisFetchErrors::FromDbError => write!(f, "Error fetching from Redis database"), 35 + RedisFetchErrors::ParseError => write!(f, "Error parsing Redis data"), 36 + RedisFetchErrors::Other(msg) => write!(f, "Other error: {}", msg), 37 + } 38 + } 39 + } 40 + 41 + impl<'a> Cache<'a> { 42 + pub fn new(redis_pool: PooledConnection<'a, RedisConnectionManager>) -> Self { 43 + Self { redis_pool } 44 + } 45 + 46 + /// Writes a value to the cache at the given key 47 + pub async fn write_to_cache<T: Serialize>( 48 + &mut self, 49 + redis_key: String, 50 + data: T, 51 + ) -> RedisResult<String> { 52 + self.redis_pool 53 + .set_ex( 54 + redis_key.clone(), 55 + serde_json::to_string(&data).unwrap(), 56 + 3600, 57 + ) 58 + .await 59 + } 60 + 61 + /// Writes to the cache with an expiration in seconds. After x seconds it clears that key 62 + pub async fn write_to_cache_with_seconds<T: Serialize>( 63 + &mut self, 64 + redis_key: &str, 65 + data: T, 66 + seconds: u64, 67 + ) -> RedisResult<()> { 68 + self.redis_pool 69 + .set_ex(redis_key, serde_json::to_string(&data).unwrap(), seconds) 70 + .await 71 + } 72 + 73 + /// Fetches a saved JSON object from the cache 74 + pub async fn fetch_redis_json_object<T: for<'de> Deserialize<'de>>( 75 + &mut self, 76 + redis_key: &str, 77 + ) -> Result<Option<T>, RedisFetchErrors> { 78 + let val: RedisResult<Option<String>> = self.redis_pool.get(redis_key).await; 79 + 80 + match val { 81 + Ok(val) => match val { 82 + None => Ok(None), 83 + Some(val) => Ok(serde_json::from_str(&val).map_err(|err| { 84 + log::error!("Error parsing redis data: {}", err); 85 + RedisFetchErrors::ParseError 86 + }))?, 87 + }, 88 + Err(_) => Err(RedisFetchErrors::FromDbError), 89 + } 90 + } 91 + 92 + /// Gets a value for a key 93 + pub async fn fetch_redis<T: FromRedisValue>( 94 + &mut self, 95 + redis_key: &str, 96 + ) -> Result<T, RedisFetchErrors> { 97 + let val = self 98 + .redis_pool 99 + .get(redis_key) 100 + .await 101 + .map_err(|_| RedisFetchErrors::FromDbError)?; 102 + 103 + Ok(val) 104 + } 105 + 106 + /// Gets or sets a value for a given key 107 + pub async fn get_or_set<T, F, Fut>( 108 + &mut self, 109 + redis_key: &str, 110 + seconds: u64, 111 + fallback_fn: F, 112 + ) -> Result<T, RedisFetchErrors> 113 + where 114 + T: for<'de> Deserialize<'de> + Serialize, 115 + F: FnOnce() -> Fut, 116 + Fut: std::future::Future<Output = Result<T, RedisFetchErrors>>, 117 + { 118 + // Try to get from cache first 119 + match self.fetch_redis_json_object::<T>(redis_key).await { 120 + Ok(Some(val)) => Ok(val), 121 + Ok(None) => { 122 + // If not in cache or error, execute the fallback function 123 + let result = fallback_fn().await?; 124 + 125 + // Write the result to cache 126 + self.write_to_cache_with_seconds(redis_key, &result, seconds) 127 + .await 128 + .map_err(|err| { 129 + log::error!("Error fetching from redis: {}", err); 130 + RedisFetchErrors::FromDbError 131 + })?; 132 + 133 + Ok(result) 134 + } 135 + Err(err) => Err(err), 136 + } 137 + } 138 + } 139 + 140 + pub type ConnectionPool = Pool<RedisConnectionManager>; 141 + 142 + pub struct CacheConnection<'a>(pub Cache<'a>); 143 + 144 + impl<'a, S> FromRequestParts<S> for CacheConnection<'a> 145 + where 146 + ConnectionPool: FromRef<S>, 147 + S: Send + Sync, 148 + { 149 + type Rejection = (StatusCode, String); 150 + 151 + async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { 152 + let pool = ConnectionPool::from_ref(state); 153 + 154 + let conn = pool.get_owned().await.map_err(internal_error)?; 155 + let cache = Cache { redis_pool: conn }; 156 + Ok(Self(cache)) 157 + } 158 + }
+1
shared/src/db.rs
··· 1 +
+18
shared/src/lexicons/codes/advent/challenge/day.rs
··· 1 + // @generated - This file is generated by esquema-codegen (forked from atrium-codegen). DO NOT EDIT. 2 + //!Definitions for the `codes.advent.challenge.day` namespace. 3 + use atrium_api::types::TryFromUnknown; 4 + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 5 + #[serde(rename_all = "camelCase")] 6 + pub struct RecordData { 7 + #[serde(skip_serializing_if = "core::option::Option::is_none")] 8 + pub created_at: core::option::Option<atrium_api::types::string::Datetime>, 9 + pub part_one: String, 10 + #[serde(skip_serializing_if = "core::option::Option::is_none")] 11 + pub part_two: core::option::Option<String>, 12 + } 13 + pub type Record = atrium_api::types::Object<RecordData>; 14 + impl From<atrium_api::types::Unknown> for RecordData { 15 + fn from(value: atrium_api::types::Unknown) -> Self { 16 + Self::try_from_unknown(value).unwrap() 17 + } 18 + }
+9
shared/src/lexicons/codes/advent/challenge.rs
··· 1 + // @generated - This file is generated by esquema-codegen (forked from atrium-codegen). DO NOT EDIT. 2 + //!Definitions for the `codes.advent.challenge` namespace. 3 + pub mod day; 4 + #[derive(Debug)] 5 + pub struct Day; 6 + impl atrium_api::types::Collection for Day { 7 + const NSID: &'static str = "codes.advent.challenge.day"; 8 + type Record = day::Record; 9 + }
+3
shared/src/lexicons/codes/advent.rs
··· 1 + // @generated - This file is generated by esquema-codegen (forked from atrium-codegen). DO NOT EDIT. 2 + //!Definitions for the `codes.advent` namespace. 3 + pub mod challenge;
+3
shared/src/lexicons/codes.rs
··· 1 + // @generated - This file is generated by esquema-codegen (forked from atrium-codegen). DO NOT EDIT. 2 + //!Definitions for the `codes` namespace. 3 + pub mod advent;
+3
shared/src/lexicons/mod.rs
··· 1 + // @generated - This file is generated by esquema-codegen (forked from atrium-codegen). DO NOT EDIT. 2 + pub mod record; 3 + pub mod codes;
+27
shared/src/lexicons/record.rs
··· 1 + // @generated - This file is generated by esquema-codegen (forked from atrium-codegen). DO NOT EDIT. 2 + //!A collection of known record types. 3 + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 4 + #[serde(tag = "$type")] 5 + pub enum KnownRecord { 6 + #[serde(rename = "codes.advent.challenge.day")] 7 + LexiconsCodesAdventChallengeDay( 8 + Box<crate::lexicons::codes::advent::challenge::day::Record>, 9 + ), 10 + } 11 + impl From<crate::lexicons::codes::advent::challenge::day::Record> for KnownRecord { 12 + fn from(record: crate::lexicons::codes::advent::challenge::day::Record) -> Self { 13 + KnownRecord::LexiconsCodesAdventChallengeDay(Box::new(record)) 14 + } 15 + } 16 + impl From<crate::lexicons::codes::advent::challenge::day::RecordData> for KnownRecord { 17 + fn from( 18 + record_data: crate::lexicons::codes::advent::challenge::day::RecordData, 19 + ) -> Self { 20 + KnownRecord::LexiconsCodesAdventChallengeDay(Box::new(record_data.into())) 21 + } 22 + } 23 + impl Into<atrium_api::types::Unknown> for KnownRecord { 24 + fn into(self) -> atrium_api::types::Unknown { 25 + atrium_api::types::TryIntoUnknown::try_into_unknown(&self).unwrap() 26 + } 27 + }
+43
shared/src/lib.rs
··· 1 + extern crate core; 2 + 3 + use crate::atrium::dns_resolver::HickoryDnsTxtResolver; 4 + use crate::atrium::stores::{AtriumSessionStore, AtriumStateStore}; 5 + use atrium_api::agent::Agent; 6 + use atrium_identity::did::CommonDidResolver; 7 + use atrium_identity::handle::AtprotoHandleResolver; 8 + use atrium_oauth::{DefaultHttpClient, OAuthClient}; 9 + use std::sync::Arc; 10 + 11 + pub mod advent; 12 + pub mod assets; 13 + pub mod atrium; 14 + pub mod cache; 15 + pub mod db; 16 + pub mod models; 17 + pub mod web_helpers; 18 + 19 + #[rustfmt::skip] 20 + pub mod lexicons; 21 + 22 + /// OAuthClientType to make it easier to access the OAuthClient in web requests 23 + pub type OAuthClientType = Arc< 24 + OAuthClient< 25 + AtriumStateStore, 26 + AtriumSessionStore, 27 + CommonDidResolver<DefaultHttpClient>, 28 + AtprotoHandleResolver<HickoryDnsTxtResolver, DefaultHttpClient>, 29 + >, 30 + >; 31 + 32 + /// HandleResolver type to make it easier to access the resolver in web requests 33 + pub type HandleResolver = Arc<CommonDidResolver<DefaultHttpClient>>; 34 + 35 + /// The agent(what makes atproto calls) 36 + pub type OAuthAgentType = Agent< 37 + atrium_oauth::OAuthSession< 38 + DefaultHttpClient, 39 + CommonDidResolver<DefaultHttpClient>, 40 + AtprotoHandleResolver<HickoryDnsTxtResolver, DefaultHttpClient>, 41 + AtriumSessionStore, 42 + >, 43 + >;
+21
shared/src/models/db_models.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + use sqlx::FromRow; 3 + use sqlx::types::chrono::{DateTime, Utc}; 4 + 5 + #[derive(FromRow, Serialize, Deserialize, Debug, Default)] 6 + pub struct TestModel { 7 + pub id: i64, 8 + pub test: String, 9 + } 10 + 11 + #[derive(FromRow, Serialize, Deserialize, Debug, Clone)] 12 + pub struct ChallengeProgress { 13 + pub id: i64, 14 + pub user_did: String, 15 + pub day: i32, 16 + pub time_started: DateTime<Utc>, 17 + pub time_challenge_one_completed: Option<DateTime<Utc>>, 18 + pub time_challenge_two_completed: Option<DateTime<Utc>>, 19 + pub verification_code_one: Option<String>, 20 + pub verification_code_two: Option<String>, 21 + }
+1
shared/src/models/mod.rs
··· 1 + pub mod db_models;
+10
shared/src/web_helpers.rs
··· 1 + use atrium_api::xrpc::http::StatusCode; 2 + 3 + /// Utility function for mapping any error into a `500 Internal Server Error` 4 + /// response. 5 + pub fn internal_error<E>(err: E) -> (StatusCode, String) 6 + where 7 + E: std::error::Error, 8 + { 9 + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) 10 + }
-31
src/main.rs
··· 1 - use axum::extract::Path; 2 - use axum::{Router, middleware, routing::get}; 3 - use std::net::{IpAddr, Ipv4Addr, SocketAddr}; 4 - use std::time; 5 - 6 - mod unlock; 7 - 8 - #[tokio::main] 9 - async fn main() { 10 - let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7878); 11 - let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); 12 - println!("listening on {}", addr); 13 - 14 - let now = time::Instant::now(); 15 - let daily = time::Duration::from_secs(24 * 60 * 60); 16 - let state = unlock::Unlock::new(now, daily); 17 - 18 - let app = 19 - Router::new() 20 - .route("/day/{id}", get(handler)) 21 - .route_layer(middleware::from_fn_with_state( 22 - state.clone(), 23 - unlock::unlock, 24 - )); 25 - 26 - axum::serve(listener, app).await.unwrap(); 27 - } 28 - 29 - async fn handler(Path(id): Path<u32>) -> String { 30 - format!("hello day {id}") 31 - }
-40
src/unlock.rs
··· 1 - use axum::extract::{Path, Request, State}; 2 - use axum::http; 3 - use axum::{ 4 - middleware, 5 - response::{self, IntoResponse}, 6 - }; 7 - use std::time; 8 - 9 - #[derive(Clone)] 10 - pub struct Unlock { 11 - start: time::Instant, 12 - interval: time::Duration, 13 - } 14 - 15 - impl Unlock { 16 - pub fn new(start: time::Instant, interval: time::Duration) -> Self { 17 - Self { start, interval } 18 - } 19 - } 20 - 21 - pub async fn unlock( 22 - Path(day): Path<u32>, 23 - State(unlocker): State<Unlock>, 24 - request: Request, 25 - next: middleware::Next, 26 - ) -> response::Response { 27 - let deadline = unlocker.start + unlocker.interval * day; 28 - let now = time::Instant::now(); 29 - if now >= deadline { 30 - return next.run(request).await; 31 - } 32 - 33 - let time_remaining = deadline.saturating_duration_since(now); 34 - let error_response = axum::Json(serde_json::json!({ 35 - "error": "Route Locked", 36 - "time_remaining_seconds": time_remaining.as_secs(), 37 - })); 38 - 39 - (http::StatusCode::FORBIDDEN, error_response).into_response() 40 - }
+714
web/Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "addr2line" 7 + version = "0.24.2" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 + dependencies = [ 11 + "gimli", 12 + ] 13 + 14 + [[package]] 15 + name = "adler2" 16 + version = "2.0.1" 17 + source = "registry+https://github.com/rust-lang/crates.io-index" 18 + checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 19 + 20 + [[package]] 21 + name = "at-advent" 22 + version = "0.1.0" 23 + dependencies = [ 24 + "axum", 25 + "serde_json", 26 + "tokio", 27 + ] 28 + 29 + [[package]] 30 + name = "autocfg" 31 + version = "1.5.0" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 34 + 35 + [[package]] 36 + name = "axum" 37 + version = "0.8.4" 38 + source = "registry+https://github.com/rust-lang/crates.io-index" 39 + checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" 40 + dependencies = [ 41 + "axum-core", 42 + "bytes", 43 + "form_urlencoded", 44 + "futures-util", 45 + "http", 46 + "http-body", 47 + "http-body-util", 48 + "hyper", 49 + "hyper-util", 50 + "itoa", 51 + "matchit", 52 + "memchr", 53 + "mime", 54 + "percent-encoding", 55 + "pin-project-lite", 56 + "rustversion", 57 + "serde", 58 + "serde_json", 59 + "serde_path_to_error", 60 + "serde_urlencoded", 61 + "sync_wrapper", 62 + "tokio", 63 + "tower", 64 + "tower-layer", 65 + "tower-service", 66 + "tracing", 67 + ] 68 + 69 + [[package]] 70 + name = "axum-core" 71 + version = "0.5.2" 72 + source = "registry+https://github.com/rust-lang/crates.io-index" 73 + checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" 74 + dependencies = [ 75 + "bytes", 76 + "futures-core", 77 + "http", 78 + "http-body", 79 + "http-body-util", 80 + "mime", 81 + "pin-project-lite", 82 + "rustversion", 83 + "sync_wrapper", 84 + "tower-layer", 85 + "tower-service", 86 + "tracing", 87 + ] 88 + 89 + [[package]] 90 + name = "backtrace" 91 + version = "0.3.75" 92 + source = "registry+https://github.com/rust-lang/crates.io-index" 93 + checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 94 + dependencies = [ 95 + "addr2line", 96 + "cfg-if", 97 + "libc", 98 + "miniz_oxide", 99 + "object", 100 + "rustc-demangle", 101 + "windows-targets", 102 + ] 103 + 104 + [[package]] 105 + name = "bitflags" 106 + version = "2.9.1" 107 + source = "registry+https://github.com/rust-lang/crates.io-index" 108 + checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 109 + 110 + [[package]] 111 + name = "bytes" 112 + version = "1.10.1" 113 + source = "registry+https://github.com/rust-lang/crates.io-index" 114 + checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 115 + 116 + [[package]] 117 + name = "cfg-if" 118 + version = "1.0.1" 119 + source = "registry+https://github.com/rust-lang/crates.io-index" 120 + checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 121 + 122 + [[package]] 123 + name = "fnv" 124 + version = "1.0.7" 125 + source = "registry+https://github.com/rust-lang/crates.io-index" 126 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 127 + 128 + [[package]] 129 + name = "form_urlencoded" 130 + version = "1.2.1" 131 + source = "registry+https://github.com/rust-lang/crates.io-index" 132 + checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 133 + dependencies = [ 134 + "percent-encoding", 135 + ] 136 + 137 + [[package]] 138 + name = "futures-channel" 139 + version = "0.3.31" 140 + source = "registry+https://github.com/rust-lang/crates.io-index" 141 + checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 142 + dependencies = [ 143 + "futures-core", 144 + ] 145 + 146 + [[package]] 147 + name = "futures-core" 148 + version = "0.3.31" 149 + source = "registry+https://github.com/rust-lang/crates.io-index" 150 + checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 151 + 152 + [[package]] 153 + name = "futures-task" 154 + version = "0.3.31" 155 + source = "registry+https://github.com/rust-lang/crates.io-index" 156 + checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 157 + 158 + [[package]] 159 + name = "futures-util" 160 + version = "0.3.31" 161 + source = "registry+https://github.com/rust-lang/crates.io-index" 162 + checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 163 + dependencies = [ 164 + "futures-core", 165 + "futures-task", 166 + "pin-project-lite", 167 + "pin-utils", 168 + ] 169 + 170 + [[package]] 171 + name = "gimli" 172 + version = "0.31.1" 173 + source = "registry+https://github.com/rust-lang/crates.io-index" 174 + checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 175 + 176 + [[package]] 177 + name = "http" 178 + version = "1.3.1" 179 + source = "registry+https://github.com/rust-lang/crates.io-index" 180 + checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 181 + dependencies = [ 182 + "bytes", 183 + "fnv", 184 + "itoa", 185 + ] 186 + 187 + [[package]] 188 + name = "http-body" 189 + version = "1.0.1" 190 + source = "registry+https://github.com/rust-lang/crates.io-index" 191 + checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 192 + dependencies = [ 193 + "bytes", 194 + "http", 195 + ] 196 + 197 + [[package]] 198 + name = "http-body-util" 199 + version = "0.1.3" 200 + source = "registry+https://github.com/rust-lang/crates.io-index" 201 + checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 202 + dependencies = [ 203 + "bytes", 204 + "futures-core", 205 + "http", 206 + "http-body", 207 + "pin-project-lite", 208 + ] 209 + 210 + [[package]] 211 + name = "httparse" 212 + version = "1.10.1" 213 + source = "registry+https://github.com/rust-lang/crates.io-index" 214 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 215 + 216 + [[package]] 217 + name = "httpdate" 218 + version = "1.0.3" 219 + source = "registry+https://github.com/rust-lang/crates.io-index" 220 + checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 221 + 222 + [[package]] 223 + name = "hyper" 224 + version = "1.6.0" 225 + source = "registry+https://github.com/rust-lang/crates.io-index" 226 + checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" 227 + dependencies = [ 228 + "bytes", 229 + "futures-channel", 230 + "futures-util", 231 + "http", 232 + "http-body", 233 + "httparse", 234 + "httpdate", 235 + "itoa", 236 + "pin-project-lite", 237 + "smallvec", 238 + "tokio", 239 + ] 240 + 241 + [[package]] 242 + name = "hyper-util" 243 + version = "0.1.16" 244 + source = "registry+https://github.com/rust-lang/crates.io-index" 245 + checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" 246 + dependencies = [ 247 + "bytes", 248 + "futures-core", 249 + "http", 250 + "http-body", 251 + "hyper", 252 + "pin-project-lite", 253 + "tokio", 254 + "tower-service", 255 + ] 256 + 257 + [[package]] 258 + name = "io-uring" 259 + version = "0.7.9" 260 + source = "registry+https://github.com/rust-lang/crates.io-index" 261 + checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" 262 + dependencies = [ 263 + "bitflags", 264 + "cfg-if", 265 + "libc", 266 + ] 267 + 268 + [[package]] 269 + name = "itoa" 270 + version = "1.0.15" 271 + source = "registry+https://github.com/rust-lang/crates.io-index" 272 + checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 273 + 274 + [[package]] 275 + name = "libc" 276 + version = "0.2.174" 277 + source = "registry+https://github.com/rust-lang/crates.io-index" 278 + checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" 279 + 280 + [[package]] 281 + name = "lock_api" 282 + version = "0.4.13" 283 + source = "registry+https://github.com/rust-lang/crates.io-index" 284 + checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 285 + dependencies = [ 286 + "autocfg", 287 + "scopeguard", 288 + ] 289 + 290 + [[package]] 291 + name = "log" 292 + version = "0.4.27" 293 + source = "registry+https://github.com/rust-lang/crates.io-index" 294 + checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 295 + 296 + [[package]] 297 + name = "matchit" 298 + version = "0.8.4" 299 + source = "registry+https://github.com/rust-lang/crates.io-index" 300 + checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 301 + 302 + [[package]] 303 + name = "memchr" 304 + version = "2.7.5" 305 + source = "registry+https://github.com/rust-lang/crates.io-index" 306 + checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 307 + 308 + [[package]] 309 + name = "mime" 310 + version = "0.3.17" 311 + source = "registry+https://github.com/rust-lang/crates.io-index" 312 + checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 313 + 314 + [[package]] 315 + name = "miniz_oxide" 316 + version = "0.8.9" 317 + source = "registry+https://github.com/rust-lang/crates.io-index" 318 + checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 319 + dependencies = [ 320 + "adler2", 321 + ] 322 + 323 + [[package]] 324 + name = "mio" 325 + version = "1.0.4" 326 + source = "registry+https://github.com/rust-lang/crates.io-index" 327 + checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 328 + dependencies = [ 329 + "libc", 330 + "wasi", 331 + "windows-sys 0.59.0", 332 + ] 333 + 334 + [[package]] 335 + name = "object" 336 + version = "0.36.7" 337 + source = "registry+https://github.com/rust-lang/crates.io-index" 338 + checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 339 + dependencies = [ 340 + "memchr", 341 + ] 342 + 343 + [[package]] 344 + name = "once_cell" 345 + version = "1.21.3" 346 + source = "registry+https://github.com/rust-lang/crates.io-index" 347 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 348 + 349 + [[package]] 350 + name = "parking_lot" 351 + version = "0.12.4" 352 + source = "registry+https://github.com/rust-lang/crates.io-index" 353 + checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 354 + dependencies = [ 355 + "lock_api", 356 + "parking_lot_core", 357 + ] 358 + 359 + [[package]] 360 + name = "parking_lot_core" 361 + version = "0.9.11" 362 + source = "registry+https://github.com/rust-lang/crates.io-index" 363 + checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 364 + dependencies = [ 365 + "cfg-if", 366 + "libc", 367 + "redox_syscall", 368 + "smallvec", 369 + "windows-targets", 370 + ] 371 + 372 + [[package]] 373 + name = "percent-encoding" 374 + version = "2.3.1" 375 + source = "registry+https://github.com/rust-lang/crates.io-index" 376 + checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 377 + 378 + [[package]] 379 + name = "pin-project-lite" 380 + version = "0.2.16" 381 + source = "registry+https://github.com/rust-lang/crates.io-index" 382 + checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 383 + 384 + [[package]] 385 + name = "pin-utils" 386 + version = "0.1.0" 387 + source = "registry+https://github.com/rust-lang/crates.io-index" 388 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 389 + 390 + [[package]] 391 + name = "proc-macro2" 392 + version = "1.0.95" 393 + source = "registry+https://github.com/rust-lang/crates.io-index" 394 + checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 395 + dependencies = [ 396 + "unicode-ident", 397 + ] 398 + 399 + [[package]] 400 + name = "quote" 401 + version = "1.0.40" 402 + source = "registry+https://github.com/rust-lang/crates.io-index" 403 + checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 404 + dependencies = [ 405 + "proc-macro2", 406 + ] 407 + 408 + [[package]] 409 + name = "redox_syscall" 410 + version = "0.5.13" 411 + source = "registry+https://github.com/rust-lang/crates.io-index" 412 + checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" 413 + dependencies = [ 414 + "bitflags", 415 + ] 416 + 417 + [[package]] 418 + name = "rustc-demangle" 419 + version = "0.1.25" 420 + source = "registry+https://github.com/rust-lang/crates.io-index" 421 + checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" 422 + 423 + [[package]] 424 + name = "rustversion" 425 + version = "1.0.21" 426 + source = "registry+https://github.com/rust-lang/crates.io-index" 427 + checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" 428 + 429 + [[package]] 430 + name = "ryu" 431 + version = "1.0.20" 432 + source = "registry+https://github.com/rust-lang/crates.io-index" 433 + checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 434 + 435 + [[package]] 436 + name = "scopeguard" 437 + version = "1.2.0" 438 + source = "registry+https://github.com/rust-lang/crates.io-index" 439 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 440 + 441 + [[package]] 442 + name = "serde" 443 + version = "1.0.219" 444 + source = "registry+https://github.com/rust-lang/crates.io-index" 445 + checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 446 + dependencies = [ 447 + "serde_derive", 448 + ] 449 + 450 + [[package]] 451 + name = "serde_derive" 452 + version = "1.0.219" 453 + source = "registry+https://github.com/rust-lang/crates.io-index" 454 + checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 455 + dependencies = [ 456 + "proc-macro2", 457 + "quote", 458 + "syn", 459 + ] 460 + 461 + [[package]] 462 + name = "serde_json" 463 + version = "1.0.141" 464 + source = "registry+https://github.com/rust-lang/crates.io-index" 465 + checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" 466 + dependencies = [ 467 + "itoa", 468 + "memchr", 469 + "ryu", 470 + "serde", 471 + ] 472 + 473 + [[package]] 474 + name = "serde_path_to_error" 475 + version = "0.1.17" 476 + source = "registry+https://github.com/rust-lang/crates.io-index" 477 + checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" 478 + dependencies = [ 479 + "itoa", 480 + "serde", 481 + ] 482 + 483 + [[package]] 484 + name = "serde_urlencoded" 485 + version = "0.7.1" 486 + source = "registry+https://github.com/rust-lang/crates.io-index" 487 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 488 + dependencies = [ 489 + "form_urlencoded", 490 + "itoa", 491 + "ryu", 492 + "serde", 493 + ] 494 + 495 + [[package]] 496 + name = "signal-hook-registry" 497 + version = "1.4.5" 498 + source = "registry+https://github.com/rust-lang/crates.io-index" 499 + checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 500 + dependencies = [ 501 + "libc", 502 + ] 503 + 504 + [[package]] 505 + name = "slab" 506 + version = "0.4.10" 507 + source = "registry+https://github.com/rust-lang/crates.io-index" 508 + checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" 509 + 510 + [[package]] 511 + name = "smallvec" 512 + version = "1.15.1" 513 + source = "registry+https://github.com/rust-lang/crates.io-index" 514 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 515 + 516 + [[package]] 517 + name = "socket2" 518 + version = "0.5.10" 519 + source = "registry+https://github.com/rust-lang/crates.io-index" 520 + checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 521 + dependencies = [ 522 + "libc", 523 + "windows-sys 0.52.0", 524 + ] 525 + 526 + [[package]] 527 + name = "syn" 528 + version = "2.0.104" 529 + source = "registry+https://github.com/rust-lang/crates.io-index" 530 + checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" 531 + dependencies = [ 532 + "proc-macro2", 533 + "quote", 534 + "unicode-ident", 535 + ] 536 + 537 + [[package]] 538 + name = "sync_wrapper" 539 + version = "1.0.2" 540 + source = "registry+https://github.com/rust-lang/crates.io-index" 541 + checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 542 + 543 + [[package]] 544 + name = "tokio" 545 + version = "1.46.1" 546 + source = "registry+https://github.com/rust-lang/crates.io-index" 547 + checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" 548 + dependencies = [ 549 + "backtrace", 550 + "bytes", 551 + "io-uring", 552 + "libc", 553 + "mio", 554 + "parking_lot", 555 + "pin-project-lite", 556 + "signal-hook-registry", 557 + "slab", 558 + "socket2", 559 + "tokio-macros", 560 + "windows-sys 0.52.0", 561 + ] 562 + 563 + [[package]] 564 + name = "tokio-macros" 565 + version = "2.5.0" 566 + source = "registry+https://github.com/rust-lang/crates.io-index" 567 + checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 568 + dependencies = [ 569 + "proc-macro2", 570 + "quote", 571 + "syn", 572 + ] 573 + 574 + [[package]] 575 + name = "tower" 576 + version = "0.5.2" 577 + source = "registry+https://github.com/rust-lang/crates.io-index" 578 + checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 579 + dependencies = [ 580 + "futures-core", 581 + "futures-util", 582 + "pin-project-lite", 583 + "sync_wrapper", 584 + "tokio", 585 + "tower-layer", 586 + "tower-service", 587 + "tracing", 588 + ] 589 + 590 + [[package]] 591 + name = "tower-layer" 592 + version = "0.3.3" 593 + source = "registry+https://github.com/rust-lang/crates.io-index" 594 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 595 + 596 + [[package]] 597 + name = "tower-service" 598 + version = "0.3.3" 599 + source = "registry+https://github.com/rust-lang/crates.io-index" 600 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 601 + 602 + [[package]] 603 + name = "tracing" 604 + version = "0.1.41" 605 + source = "registry+https://github.com/rust-lang/crates.io-index" 606 + checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 607 + dependencies = [ 608 + "log", 609 + "pin-project-lite", 610 + "tracing-core", 611 + ] 612 + 613 + [[package]] 614 + name = "tracing-core" 615 + version = "0.1.34" 616 + source = "registry+https://github.com/rust-lang/crates.io-index" 617 + checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 618 + dependencies = [ 619 + "once_cell", 620 + ] 621 + 622 + [[package]] 623 + name = "unicode-ident" 624 + version = "1.0.18" 625 + source = "registry+https://github.com/rust-lang/crates.io-index" 626 + checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 627 + 628 + [[package]] 629 + name = "wasi" 630 + version = "0.11.1+wasi-snapshot-preview1" 631 + source = "registry+https://github.com/rust-lang/crates.io-index" 632 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 633 + 634 + [[package]] 635 + name = "windows-sys" 636 + version = "0.52.0" 637 + source = "registry+https://github.com/rust-lang/crates.io-index" 638 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 639 + dependencies = [ 640 + "windows-targets", 641 + ] 642 + 643 + [[package]] 644 + name = "windows-sys" 645 + version = "0.59.0" 646 + source = "registry+https://github.com/rust-lang/crates.io-index" 647 + checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 648 + dependencies = [ 649 + "windows-targets", 650 + ] 651 + 652 + [[package]] 653 + name = "windows-targets" 654 + version = "0.52.6" 655 + source = "registry+https://github.com/rust-lang/crates.io-index" 656 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 657 + dependencies = [ 658 + "windows_aarch64_gnullvm", 659 + "windows_aarch64_msvc", 660 + "windows_i686_gnu", 661 + "windows_i686_gnullvm", 662 + "windows_i686_msvc", 663 + "windows_x86_64_gnu", 664 + "windows_x86_64_gnullvm", 665 + "windows_x86_64_msvc", 666 + ] 667 + 668 + [[package]] 669 + name = "windows_aarch64_gnullvm" 670 + version = "0.52.6" 671 + source = "registry+https://github.com/rust-lang/crates.io-index" 672 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 673 + 674 + [[package]] 675 + name = "windows_aarch64_msvc" 676 + version = "0.52.6" 677 + source = "registry+https://github.com/rust-lang/crates.io-index" 678 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 679 + 680 + [[package]] 681 + name = "windows_i686_gnu" 682 + version = "0.52.6" 683 + source = "registry+https://github.com/rust-lang/crates.io-index" 684 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 685 + 686 + [[package]] 687 + name = "windows_i686_gnullvm" 688 + version = "0.52.6" 689 + source = "registry+https://github.com/rust-lang/crates.io-index" 690 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 691 + 692 + [[package]] 693 + name = "windows_i686_msvc" 694 + version = "0.52.6" 695 + source = "registry+https://github.com/rust-lang/crates.io-index" 696 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 697 + 698 + [[package]] 699 + name = "windows_x86_64_gnu" 700 + version = "0.52.6" 701 + source = "registry+https://github.com/rust-lang/crates.io-index" 702 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 703 + 704 + [[package]] 705 + name = "windows_x86_64_gnullvm" 706 + version = "0.52.6" 707 + source = "registry+https://github.com/rust-lang/crates.io-index" 708 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 709 + 710 + [[package]] 711 + name = "windows_x86_64_msvc" 712 + version = "0.52.6" 713 + source = "registry+https://github.com/rust-lang/crates.io-index" 714 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+32
web/Cargo.toml
··· 1 + [package] 2 + name = "web" 3 + version = "0.1.0" 4 + edition = "2024" 5 + 6 + [dependencies] 7 + atrium-common.workspace = true 8 + atrium-api.workspace = true 9 + atrium-identity.workspace = true 10 + atrium-oauth.workspace = true 11 + axum.workspace = true 12 + chrono.workspace = true 13 + bb8.workspace = true 14 + bb8-redis.workspace = true 15 + dotenv.workspace = true 16 + log.workspace = true 17 + redis.workspace = true 18 + serde_json.workspace = true 19 + shared.workspace = true 20 + sqlx.workspace = true 21 + tokio.workspace = true 22 + tower-http = { version = "0.6.6", features = ["trace"] } 23 + tower-sessions = "0.14.0" 24 + tracing.workspace = true 25 + tracing-subscriber.workspace = true 26 + serde = { version = "1.0.219", features = ["derive"] } 27 + askama = "0.14" 28 + async-trait = "0.1.88" 29 + hypertext = "0.12.1" 30 + 31 + [build-dependencies] 32 + pool = "0.1.4"
+66
web/src/extractors.rs
··· 1 + use crate::{AppState, OAuthClientType}; 2 + use axum::extract::{FromRef, FromRequestParts}; 3 + use axum::http::StatusCode; 4 + use axum::http::request::Parts; 5 + use shared::cache::ConnectionPool; 6 + use shared::web_helpers::internal_error; 7 + use sqlx::PgPool; 8 + 9 + #[allow(dead_code)] 10 + struct DatabaseConnection(sqlx::pool::PoolConnection<sqlx::Postgres>); 11 + 12 + impl<S> FromRequestParts<S> for DatabaseConnection 13 + where 14 + PgPool: FromRef<S>, 15 + S: Send + Sync, 16 + { 17 + type Rejection = (StatusCode, String); 18 + 19 + async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { 20 + let pool = PgPool::from_ref(state); 21 + 22 + let conn = pool.acquire().await.map_err(internal_error)?; 23 + 24 + Ok(Self(conn)) 25 + } 26 + } 27 + 28 + //Redis extractor 29 + 30 + // pub type AtProtoClient = OAuthClientType; 31 + 32 + #[allow(dead_code)] 33 + pub struct AtProtoClient(OAuthClientType); 34 + 35 + impl<S> FromRequestParts<S> for AtProtoClient 36 + where 37 + OAuthClientType: FromRef<S>, 38 + S: Send + Sync, 39 + { 40 + type Rejection = (StatusCode, String); 41 + 42 + async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { 43 + let client = OAuthClientType::from_ref(state); 44 + Ok(Self(client)) 45 + } 46 + } 47 + 48 + //From refs 49 + 50 + impl FromRef<AppState> for PgPool { 51 + fn from_ref(app_state: &AppState) -> PgPool { 52 + app_state.postgres_pool.clone() 53 + } 54 + } 55 + 56 + impl FromRef<AppState> for ConnectionPool { 57 + fn from_ref(app_state: &AppState) -> ConnectionPool { 58 + app_state.redis_pool.clone() 59 + } 60 + } 61 + 62 + impl FromRef<AppState> for OAuthClientType { 63 + fn from_ref(app_state: &AppState) -> OAuthClientType { 64 + app_state.oauth_client.clone() 65 + } 66 + }
+112
web/src/handlers/auth.rs
··· 1 + use crate::session::{AxumSessionStore, FlashMessage, get_flash_message, set_flash_message}; 2 + use crate::templates::{HtmlTemplate, login::LoginTemplate}; 3 + use crate::{error_response, oauth_scopes}; 4 + use atrium_api::agent::Agent; 5 + use atrium_oauth::{AuthorizeOptions, CallbackParams}; 6 + use axum::{ 7 + extract::{Path, Query, State}, 8 + http::StatusCode, 9 + response::{IntoResponse, Redirect, Response}, 10 + }; 11 + use shared::OAuthClientType; 12 + 13 + pub async fn login_page_handler( 14 + mut session: AxumSessionStore, 15 + ) -> Result<impl IntoResponse, Response> { 16 + let possible_error = match get_flash_message(&mut session, "error").await? { 17 + Some(FlashMessage::Error(msg)) => Some(msg), 18 + _ => None, 19 + }; 20 + 21 + Ok(HtmlTemplate(LoginTemplate { 22 + title: "at://advent - Login", 23 + error: possible_error, 24 + })) 25 + } 26 + 27 + pub async fn login_handle( 28 + Path(handle): Path<String>, 29 + State(oauth_client): State<OAuthClientType>, 30 + mut session: AxumSessionStore, 31 + ) -> Result<impl IntoResponse, Response> { 32 + match atrium_api::types::string::Handle::new(handle) { 33 + Ok(handle) => { 34 + match oauth_client 35 + .authorize( 36 + &handle, 37 + AuthorizeOptions { 38 + scopes: oauth_scopes(), 39 + ..Default::default() 40 + }, 41 + ) 42 + .await 43 + { 44 + Ok(url) => Ok(Redirect::to(url.as_str())), 45 + Err(err) => { 46 + log::error!("Error generating OAuth URL: {err}"); 47 + set_flash_message( 48 + &mut session, 49 + "error", 50 + FlashMessage::Error("Error creating login URL".to_string()), 51 + ) 52 + .await?; 53 + Err(error_response( 54 + StatusCode::INTERNAL_SERVER_ERROR, 55 + "Error creating login URL", 56 + )) 57 + } 58 + } 59 + } 60 + Err(err) => { 61 + log::error!("Error parsing the handle: {err}"); 62 + set_flash_message( 63 + &mut session, 64 + "error", 65 + FlashMessage::Error("Error parsing the handle".to_string()), 66 + ) 67 + .await?; 68 + 69 + Ok(Redirect::to("/login")) 70 + } 71 + } 72 + } 73 + 74 + pub async fn handle_root_handler() -> impl IntoResponse { 75 + Redirect::to("/login") 76 + } 77 + 78 + ///End point that takes back the OAuth call back and creates a session 79 + pub async fn oauth_callback_handler( 80 + params: Query<CallbackParams>, 81 + State(oauth_client): State<OAuthClientType>, 82 + mut session: AxumSessionStore, 83 + ) -> Response { 84 + let call_back_params = CallbackParams { 85 + code: params.code.clone(), 86 + state: params.state.clone(), 87 + iss: params.iss.clone(), 88 + }; 89 + match oauth_client.callback(call_back_params).await { 90 + Ok((bsky_session, _)) => { 91 + let agent = Agent::new(bsky_session); 92 + match agent.did().await { 93 + Some(did) => { 94 + if let Err(err) = session.set_did(did.clone().to_string()).await { 95 + log::error!("Failed to write session: {err}"); 96 + return error_response( 97 + StatusCode::INTERNAL_SERVER_ERROR, 98 + "Failed to create session", 99 + ); 100 + } 101 + 102 + Redirect::permanent("/day/1").into_response() 103 + } 104 + None => error_response(StatusCode::INTERNAL_SERVER_ERROR, "No DID found"), 105 + } 106 + } 107 + Err(err) => { 108 + log::error!("OAuth callback error: {err}"); 109 + error_response(StatusCode::INTERNAL_SERVER_ERROR, "OAuth callback failed") 110 + } 111 + } 112 + }
+378
web/src/handlers/day.rs
··· 1 + use crate::error_response; 2 + use crate::session::{AxumSessionStore, FlashMessage, get_flash_message, set_flash_message}; 3 + use crate::templates::{HtmlTemplate, day::DayTemplate}; 4 + use atrium_api::agent::Agent; 5 + use atrium_api::types::string::Did; 6 + use axum::{ 7 + extract::{Form, Path, State}, 8 + http::StatusCode, 9 + response::{IntoResponse, Redirect, Response}, 10 + }; 11 + use shared::advent::ChallengeCheckResponse; 12 + use shared::{ 13 + OAuthAgentType, OAuthClientType, 14 + advent::challenges::day_one::DayOne, 15 + advent::challenges::day_two::DayTwo, 16 + advent::day::Day, 17 + advent::{AdventChallenge, AdventError}, 18 + advent::{AdventPart, CompletionStatus}, 19 + }; 20 + use sqlx::PgPool; 21 + 22 + fn pick_day( 23 + day: Day, 24 + pool: PgPool, 25 + oauth_client: Option<OAuthAgentType>, 26 + ) -> Result<Box<dyn AdventChallenge + Send + Sync>, AdventError> { 27 + match day { 28 + Day::One => Ok(Box::new(DayOne { pool, oauth_client })), 29 + Day::Two => Ok(Box::new(DayTwo { pool, oauth_client })), 30 + _ => Err(AdventError::InvalidDay(0)), // Day::Three => {} 31 + // Day::Four => {} 32 + // Day::Five => {} 33 + // Day::Six => {} 34 + // Day::Seven => {} 35 + // Day::Eight => {} 36 + // Day::Nine => {} 37 + // Day::Ten => {} 38 + // Day::Eleven => {} 39 + // Day::Twelve => {} 40 + // Day::Thirteen => {} 41 + // Day::Fourteen => {} 42 + // Day::Fifteen => {} 43 + // Day::Sixteen => {} 44 + // Day::Seventeen => {} 45 + // Day::Eighteen => {} 46 + // Day::Nineteen => {} 47 + // Day::Twenty => {} 48 + // Day::TwentyOne => {} 49 + // Day::TwentyTwo => {} 50 + // Day::TwentyThree => {} 51 + // Day::TwentyFour => {} 52 + // Day::TwentyFive => {} 53 + } 54 + } 55 + 56 + fn log_and_respond<E: std::fmt::Display>( 57 + status: StatusCode, 58 + context: &'static str, 59 + ) -> impl FnOnce(E) -> Response { 60 + move |err| { 61 + log::error!("{context}: {err}"); 62 + error_response(status, context) 63 + } 64 + } 65 + 66 + pub async fn view_day_handler( 67 + Path(id): Path<u8>, 68 + State(pool): State<PgPool>, 69 + session: AxumSessionStore, 70 + ) -> Result<impl IntoResponse, Response> { 71 + let day = Day::from(id); 72 + 73 + let did = session.get_did(); 74 + let did_clone = did.clone(); 75 + let challenge = pick_day(day, pool, None).map_err(|err| { 76 + log::error!("Error picking day: {err}"); 77 + error_response(StatusCode::INTERNAL_SERVER_ERROR, "Error picking day") 78 + })?; 79 + 80 + let title = format!("at://advent - Day {}", day as u8); 81 + let part_one_text = match did_clone { 82 + None => challenge 83 + .markdown_text_part_one(None) 84 + .map(|s| s.to_string()) 85 + .unwrap_or_else(|_| "Error loading part one".to_string()), 86 + Some(ref users_did) => match challenge.get_days_challenge(&users_did).await { 87 + Ok(current_challenge) => match current_challenge { 88 + None => { 89 + let new_code = challenge 90 + .start_challenge(users_did.to_string(), AdventPart::One) 91 + .await 92 + .unwrap(); 93 + challenge 94 + .markdown_text_part_one(Some(new_code)) 95 + .map(|s| s.to_string()) 96 + .unwrap_or_else(|_| "Error loading part one".to_string()) 97 + } 98 + Some(current_challenge) => match current_challenge.verification_code_one { 99 + None => { 100 + let new_code = challenge 101 + .start_challenge(users_did.to_string(), AdventPart::One) 102 + .await 103 + .unwrap(); 104 + challenge 105 + .markdown_text_part_one(Some(new_code)) 106 + .map(|s| s.to_string()) 107 + .unwrap_or_else(|_| "Error loading part one".to_string()) 108 + } 109 + Some(code) => challenge 110 + .markdown_text_part_one(Some(code)) 111 + .map(|s| s.to_string()) 112 + .unwrap_or_else(|_| "Error loading part one".to_string()), 113 + }, 114 + }, 115 + 116 + Err(err) => { 117 + log::error!("Error loading today's challenge for the user: {users_did} \n {err}"); 118 + "There was an error loading the challenge...sorry about that".to_string() 119 + } 120 + }, 121 + }; 122 + 123 + let status = challenge.get_completed_status(did).await.map_err(|err| { 124 + log::error!("Error getting completed status: {err}"); 125 + error_response( 126 + StatusCode::INTERNAL_SERVER_ERROR, 127 + "Error getting completed status", 128 + ) 129 + })?; 130 + 131 + let mut session = session; 132 + let part_one_flash = get_flash_message(&mut session, "part_one_result").await?; 133 + let part_two_flash = get_flash_message(&mut session, "part_two_result").await?; 134 + 135 + let template = match status { 136 + CompletionStatus::None => DayTemplate { 137 + title, 138 + day: id, 139 + challenge_one_text: part_one_text, 140 + challenge_one_completed: false, 141 + challenge_two_text: None, 142 + challenge_two_completed: false, 143 + part_one_submit_message: part_one_flash, 144 + part_two_submit_message: part_two_flash, 145 + }, 146 + CompletionStatus::PartOne => { 147 + let part_two_text = get_part_two_text(did_clone, &challenge).await; 148 + let completed = part_two_text.is_none(); 149 + DayTemplate { 150 + title, 151 + day: id, 152 + challenge_one_text: part_one_text, 153 + challenge_one_completed: true, 154 + challenge_two_text: part_two_text, 155 + challenge_two_completed: completed, 156 + part_one_submit_message: part_one_flash, 157 + part_two_submit_message: part_two_flash, 158 + } 159 + } 160 + CompletionStatus::Both => { 161 + let part_two_text = get_part_two_text(did_clone, &challenge).await; 162 + DayTemplate { 163 + title, 164 + day: id, 165 + challenge_one_text: part_one_text, 166 + challenge_one_completed: true, 167 + challenge_two_text: part_two_text, 168 + challenge_two_completed: true, 169 + part_one_submit_message: part_one_flash, 170 + part_two_submit_message: part_two_flash, 171 + } 172 + } 173 + }; 174 + 175 + Ok(HtmlTemplate(template)) 176 + } 177 + 178 + ///TODO prob look and see if this can be shared between part one since it is similar logic... 179 + /// Also this is in a function since PartOne and Both load the partwo text 180 + async fn get_part_two_text( 181 + did_clone: Option<String>, 182 + challenge: &Box<dyn AdventChallenge + Send + Sync>, 183 + ) -> Option<String> { 184 + let part_two_text: Option<String> = match did_clone { 185 + None => challenge 186 + .markdown_text_part_two(None) 187 + .map(|opt| opt.map(|s| s.to_string())) 188 + .unwrap_or(None), 189 + Some(users_did) => match challenge.get_days_challenge(&users_did).await { 190 + Ok(current_challenge) => match current_challenge { 191 + None => { 192 + if challenge.has_part_two() { 193 + let new_code = challenge 194 + .start_challenge(users_did.to_string(), AdventPart::Two) 195 + .await 196 + .unwrap(); 197 + challenge 198 + .markdown_text_part_two(Some(new_code)) 199 + .map(|opt| opt.map(|s| s.to_string())) 200 + .unwrap_or(None) 201 + } else { 202 + None 203 + } 204 + } 205 + Some(current_challenge) => { 206 + // If there is no code yet for part two, start it; otherwise use the existing code 207 + if challenge.has_part_two() { 208 + match current_challenge.verification_code_two { 209 + None => { 210 + let new_code = challenge 211 + .start_challenge(users_did.to_string(), AdventPart::Two) 212 + .await 213 + .unwrap(); 214 + challenge 215 + .markdown_text_part_two(Some(new_code)) 216 + .map(|opt| opt.map(|s| s.to_string())) 217 + .unwrap_or(None) 218 + } 219 + Some(code) => challenge 220 + .markdown_text_part_two(Some(code)) 221 + .map(|opt| opt.map(|s| s.to_string())) 222 + .unwrap_or(None), 223 + } 224 + } else { 225 + let day = current_challenge.day; 226 + log::warn!( 227 + "There is no part two for day: {day}. Developer may of forgotten to set the has_part_two flag to true." 228 + ); 229 + None 230 + } 231 + } 232 + }, 233 + Err(err) => { 234 + log::error!("Error loading today's challenge for the user: {users_did} \n {err}"); 235 + None 236 + } 237 + }, 238 + }; 239 + part_two_text 240 + } 241 + 242 + /// This can be used to verify the day's challenge. Empty if it's up to the backend to grab the verification code 243 + /// from somewhere like a lexicon record 244 + #[derive(Debug, serde::Deserialize, Clone)] 245 + pub struct PostDayForm { 246 + #[serde(default)] 247 + pub verification_code_one: Option<String>, 248 + #[serde(default)] 249 + pub verification_code_two: Option<String>, 250 + } 251 + 252 + ///This is the endpoint to verify the day's challenge 253 + pub async fn post_day_handler( 254 + Path(day): Path<u8>, 255 + State(pool): State<PgPool>, 256 + State(oauth_client): State<OAuthClientType>, 257 + mut session: AxumSessionStore, 258 + Form(form): Form<PostDayForm>, 259 + ) -> Result<impl IntoResponse, Response> { 260 + match &session.get_did() { 261 + None => Err(error_response( 262 + StatusCode::FORBIDDEN, 263 + "You need to be logged in to submit an answer", 264 + )), 265 + Some(did) => { 266 + let did_as_string = did.clone(); 267 + let did = Did::new(did.to_string()) 268 + .map_err(log_and_respond(StatusCode::BAD_REQUEST, "Invalid DID"))?; 269 + 270 + let client = oauth_client.restore(&did).await.map_err(log_and_respond( 271 + StatusCode::INTERNAL_SERVER_ERROR, 272 + "There was an error restoring the oauth client", 273 + ))?; 274 + 275 + let agent = Agent::new(client); 276 + let day = Day::from(day); 277 + 278 + let challenge = pick_day(day, pool, Some(agent)).map_err(log_and_respond( 279 + StatusCode::INTERNAL_SERVER_ERROR, 280 + "Error picking the day", 281 + ))?; 282 + 283 + let status = challenge 284 + .get_completed_status(Some(did_as_string.clone())) 285 + .await 286 + .map_err(log_and_respond( 287 + StatusCode::INTERNAL_SERVER_ERROR, 288 + "Error getting the completed status", 289 + ))?; 290 + 291 + match status { 292 + CompletionStatus::None => { 293 + let result = challenge 294 + .check_part_one(did_as_string.clone(), form.verification_code_one) 295 + .await 296 + .map_err(log_and_respond( 297 + StatusCode::INTERNAL_SERVER_ERROR, 298 + "Error checking part one", 299 + ))?; 300 + match result { 301 + ChallengeCheckResponse::Correct => { 302 + challenge.complete_part_one(did_as_string).await.map_err( 303 + log_and_respond( 304 + StatusCode::INTERNAL_SERVER_ERROR, 305 + "Error completing part one", 306 + ), 307 + )?; 308 + set_flash_message( 309 + &mut session, 310 + "part_one_result", 311 + FlashMessage::Success( 312 + "Good job, you've completed Part 1".to_string(), 313 + ), 314 + ) 315 + .await?; 316 + Ok(Redirect::to(format!("/day/{}", day as u8).as_str())) 317 + } 318 + ChallengeCheckResponse::Incorrect(message) => { 319 + set_flash_message( 320 + &mut session, 321 + "part_one_result", 322 + FlashMessage::Error(message), 323 + ) 324 + .await?; 325 + Ok(Redirect::to(format!("/day/{}", day as u8).as_str())) 326 + } 327 + } 328 + } 329 + CompletionStatus::PartOne => { 330 + if !challenge.has_part_two() { 331 + log::info!( 332 + "Someone tried to check for part two on day:{day}, when there was not one" 333 + ); 334 + return Ok(Redirect::to(format!("/day/{}", day as u8).as_str())); 335 + } 336 + 337 + let result = challenge 338 + .check_part_two(did_as_string.clone(), form.verification_code_two) 339 + .await 340 + .map_err(log_and_respond( 341 + StatusCode::INTERNAL_SERVER_ERROR, 342 + "Error checking part two", 343 + ))?; 344 + 345 + match result { 346 + ChallengeCheckResponse::Correct => { 347 + challenge.complete_part_two(did_as_string).await.map_err( 348 + log_and_respond( 349 + StatusCode::INTERNAL_SERVER_ERROR, 350 + "Error completing part two", 351 + ), 352 + )?; 353 + set_flash_message( 354 + &mut session, 355 + "part_two_result", 356 + FlashMessage::Success( 357 + "Good job, you've completed Part 2".to_string(), 358 + ), 359 + ) 360 + .await?; 361 + Ok(Redirect::to(format!("/day/{}", day as u8).as_str())) 362 + } 363 + ChallengeCheckResponse::Incorrect(message) => { 364 + set_flash_message( 365 + &mut session, 366 + "part_two_result", 367 + FlashMessage::Error(message), 368 + ) 369 + .await?; 370 + Ok(Redirect::to(format!("/day/{}", day as u8).as_str())) 371 + } 372 + } 373 + } 374 + CompletionStatus::Both => Ok(Redirect::to(format!("/day/{}", day as u8).as_str())), 375 + } 376 + } 377 + } 378 + }
+2
web/src/handlers/mod.rs
··· 1 + pub mod auth; 2 + pub mod day;
+236
web/src/main.rs
··· 1 + use crate::{ 2 + templates::HtmlTemplate, templates::error::ErrorTemplate, templates::home::HomeTemplate, 3 + }; 4 + use atrium_identity::{ 5 + did::{CommonDidResolver, CommonDidResolverConfig, DEFAULT_PLC_DIRECTORY_URL}, 6 + handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig}, 7 + }; 8 + use atrium_oauth::{ 9 + AtprotoLocalhostClientMetadata, DefaultHttpClient, KnownScope, OAuthClient, OAuthClientConfig, 10 + OAuthResolverConfig, Scope, 11 + }; 12 + use axum::{ 13 + Router, 14 + http::StatusCode, 15 + middleware, 16 + response::IntoResponse, 17 + response::Response, 18 + routing::{get, post}, 19 + }; 20 + use bb8_redis::RedisConnectionManager; 21 + use chrono::Datelike; 22 + use dotenv::dotenv; 23 + use redis::AsyncCommands; 24 + use shared::{ 25 + HandleResolver, OAuthClientType, atrium::dns_resolver::HickoryDnsTxtResolver, 26 + atrium::stores::AtriumSessionStore, atrium::stores::AtriumStateStore, 27 + }; 28 + use sqlx::{PgPool, postgres::PgPoolOptions}; 29 + use std::{ 30 + env, 31 + net::{IpAddr, Ipv4Addr, SocketAddr}, 32 + sync::Arc, 33 + time, 34 + }; 35 + use time::Duration; 36 + use tower_http::trace::TraceLayer; 37 + use tower_sessions::{SessionManagerLayer, cookie::SameSite}; 38 + use tracing_subscriber::EnvFilter; 39 + 40 + mod handlers; 41 + 42 + extern crate dotenv; 43 + 44 + mod extractors; 45 + mod redis_session_store; 46 + mod session; 47 + mod templates; 48 + mod unlock; 49 + 50 + #[derive(Clone)] 51 + struct AppState { 52 + postgres_pool: PgPool, 53 + redis_pool: bb8::Pool<RedisConnectionManager>, 54 + oauth_client: OAuthClientType, 55 + //Used to get did to handle leaving because I figured we'd need it 56 + _handle_resolver: HandleResolver, 57 + } 58 + 59 + fn oauth_scopes() -> Vec<Scope> { 60 + vec![ 61 + Scope::Known(KnownScope::Atproto), 62 + //Gives full CRUD to the codes.advent.* collection 63 + Scope::Unknown("repo:codes.advent.*".to_string()), 64 + ] 65 + } 66 + 67 + fn error_response(status: StatusCode, message: &str) -> Response { 68 + IntoResponse::into_response(( 69 + status, 70 + HtmlTemplate(ErrorTemplate { 71 + title: "at://advent - Error", 72 + message, 73 + }), 74 + )) 75 + } 76 + 77 + #[tokio::main] 78 + async fn main() -> Result<(), Box<dyn std::error::Error>> { 79 + dotenv().ok(); 80 + 81 + //Sets up logging/tracing 82 + tracing_subscriber::fmt() 83 + .with_env_filter( 84 + EnvFilter::try_from_default_env() 85 + .or_else(|_| EnvFilter::try_new("info,axum_tracing_example=error,tower_http=warn")) 86 + .unwrap(), 87 + ) 88 + .init(); 89 + 90 + let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7878); 91 + let host = addr.ip(); 92 + let port = addr.port(); 93 + let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); 94 + 95 + //sqlx pool 96 + let database_url = 97 + env::var("DATABASE_URL").expect("DATABASE_URL must be set in the environment or .env"); 98 + 99 + // set up a postgres connection pool 100 + let postgres_pool = PgPoolOptions::new() 101 + .max_connections(5) 102 + .acquire_timeout(Duration::from_secs(3)) 103 + .connect(&database_url) 104 + .await 105 + .expect("can't connect to database"); 106 + 107 + // redis pool setup 108 + let redis_url = 109 + env::var("REDIS_URL").expect("REDIS_URL must be set in the environment or .env"); 110 + let manager = RedisConnectionManager::new(redis_url.clone()).unwrap(); 111 + let redis_pool = bb8::Pool::builder().build(manager).await.unwrap(); 112 + //cam be deleted, just an example for the test endpoint 113 + { 114 + // ping the database before starting 115 + let mut conn = redis_pool.get().await.unwrap(); 116 + conn.set::<&str, &str, ()>("foo", "bar").await.unwrap(); 117 + let result: String = conn.get("foo").await.unwrap(); 118 + assert_eq!(result, "bar"); 119 + } 120 + 121 + //Atrium/atproto setup 122 + 123 + //Create a new handle resolver for the home page 124 + let http_client = Arc::new(DefaultHttpClient::default()); 125 + 126 + let handle_resolver = CommonDidResolver::new(CommonDidResolverConfig { 127 + plc_directory_url: DEFAULT_PLC_DIRECTORY_URL.to_string(), 128 + http_client: http_client.clone(), 129 + }); 130 + let handle_resolver = Arc::new(handle_resolver); 131 + 132 + // Create a new OAuth client 133 + let http_client = Arc::new(DefaultHttpClient::default()); 134 + let config = OAuthClientConfig { 135 + client_metadata: AtprotoLocalhostClientMetadata { 136 + redirect_uris: Some(vec![String::from(format!( 137 + //This must match the endpoint you use the callback function 138 + "http://{host}:{port}/oauth/callback" 139 + ))]), 140 + scopes: Some(oauth_scopes()), 141 + }, 142 + keys: None, 143 + resolver: OAuthResolverConfig { 144 + did_resolver: CommonDidResolver::new(CommonDidResolverConfig { 145 + plc_directory_url: DEFAULT_PLC_DIRECTORY_URL.to_string(), 146 + http_client: http_client.clone(), 147 + }), 148 + handle_resolver: AtprotoHandleResolver::new(AtprotoHandleResolverConfig { 149 + dns_txt_resolver: HickoryDnsTxtResolver::default(), 150 + http_client: http_client.clone(), 151 + }), 152 + authorization_server_metadata: Default::default(), 153 + protected_resource_metadata: Default::default(), 154 + }, 155 + state_store: AtriumStateStore::new(redis_pool.clone()), 156 + session_store: AtriumSessionStore::new(redis_pool.clone()), 157 + }; 158 + let client = Arc::new(OAuthClient::new(config).expect("failed to create OAuth client")); 159 + 160 + let session_store = redis_session_store::RedisSessionStore::new(redis_pool.clone()); 161 + let session_layer = SessionManagerLayer::new(session_store) 162 + //Set to lax so session id cookie can be set on redirect 163 + .with_same_site(SameSite::Lax) 164 + .with_secure(false); 165 + 166 + let app_state = AppState { 167 + postgres_pool, 168 + redis_pool, 169 + oauth_client: client, 170 + _handle_resolver: handle_resolver, 171 + }; 172 + //HACK Yeah I don't like it either - bt 173 + let prod: bool = env::var("PROD") 174 + .map(|val| val == "true") 175 + .unwrap_or_else(|_| true); 176 + log::info!("listening on http://{}", addr); 177 + let app = Router::new() 178 + .route("/", get(home_handler)) 179 + .route( 180 + "/day/{id}", 181 + match prod { 182 + true => get(handlers::day::view_day_handler) 183 + .route_layer(middleware::from_fn(unlock::unlock)), 184 + false => get(handlers::day::view_day_handler), 185 + }, 186 + ) 187 + .route( 188 + "/day/{id}", 189 + match prod { 190 + true => post(handlers::day::post_day_handler) 191 + .route_layer(middleware::from_fn(unlock::unlock)), 192 + false => post(handlers::day::post_day_handler), 193 + }, 194 + ) 195 + .route("/login", get(handlers::auth::login_page_handler)) 196 + .route("/handle", get(handlers::auth::handle_root_handler)) 197 + .route("/login/{handle}", get(handlers::auth::login_handle)) 198 + .route( 199 + "/oauth/callback", 200 + get(handlers::auth::oauth_callback_handler), 201 + ) 202 + .layer(session_layer) 203 + .with_state(app_state) 204 + .layer(TraceLayer::new_for_http()); 205 + axum::serve(listener, app).await?; 206 + Ok(()) 207 + } 208 + 209 + /// Landing page showing currently unlocked days and a login button 210 + async fn home_handler() -> impl IntoResponse { 211 + //TODO make a helper function for this since it is similar to the middleware 212 + let now = chrono::Utc::now(); 213 + let mut unlocked: Vec<u8> = Vec::new(); 214 + 215 + //HACK Yeah I don't like it either - bt 216 + let prod: bool = env::var("PROD") 217 + .map(|val| val == "true") 218 + .unwrap_or_else(|_| true); 219 + if prod { 220 + if now.month() == 12 { 221 + let today = now.day().min(25); 222 + for d in 1..=today { 223 + unlocked.push(d as u8); 224 + } 225 + } 226 + } else { 227 + for d in 1..=25 { 228 + unlocked.push(d as u8); 229 + } 230 + } 231 + 232 + HtmlTemplate(HomeTemplate { 233 + title: "at://advent", 234 + unlocked_days: unlocked, 235 + }) 236 + }
+111
web/src/redis_session_store.rs
··· 1 + use async_trait::async_trait; 2 + use bb8::Pool; 3 + use bb8_redis::{RedisConnectionManager, redis::cmd}; 4 + use shared::cache::{Cache, TOWER_SESSION_KEY, create_prefixed_key}; 5 + use std::fmt::Display; 6 + use std::fmt::{Debug, Formatter}; 7 + use tower_sessions::SessionStore; 8 + use tower_sessions::session::{Id, Record}; 9 + use tower_sessions::session_store::{Error, Result as StoreResult}; 10 + 11 + #[derive(Clone)] 12 + pub struct RedisSessionStore { 13 + cache_pool: Pool<RedisConnectionManager>, 14 + } 15 + 16 + impl RedisSessionStore { 17 + pub fn new(cache_pool: Pool<RedisConnectionManager>) -> Self { 18 + Self { cache_pool } 19 + } 20 + } 21 + 22 + impl Debug for RedisSessionStore { 23 + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 24 + f.debug_struct("RedisSessionStore").finish() 25 + } 26 + } 27 + 28 + // Small helper to convert any error into tower-sessions Backend error consistently 29 + fn backend_map<E: Display>(context: &'static str) -> impl FnOnce(E) -> Error { 30 + move |err| { 31 + log::error!("{}: {}", context, err); 32 + Error::Backend(err.to_string()) 33 + } 34 + } 35 + 36 + #[async_trait] 37 + impl SessionStore for RedisSessionStore { 38 + async fn create(&self, session_record: &mut Record) -> StoreResult<()> { 39 + //TODO i don't think there is an issue with overwriting the session here since it's redis and should be no collision 40 + //The default create throws a warning about this so, added this to get rid of it and adding a note in case it does cause a problem 41 + self.save(session_record).await 42 + } 43 + 44 + async fn save(&self, session_record: &Record) -> StoreResult<()> { 45 + let id_as_str: String = session_record.id.0.to_string(); 46 + let key = create_prefixed_key(TOWER_SESSION_KEY, id_as_str.as_str()); 47 + 48 + // Get a redis connection 49 + let conn = self 50 + .cache_pool 51 + .get() 52 + .await 53 + .map_err(backend_map("There was an error connecting to the cache"))?; 54 + 55 + // Set value with TTL based on expiry_date 56 + let expiry = session_record.expiry_date; 57 + let now = std::time::SystemTime::now() 58 + .duration_since(std::time::UNIX_EPOCH) 59 + .unwrap_or_default() 60 + .as_secs() as i64; 61 + let ttl_secs = expiry.unix_timestamp().saturating_sub(now).max(0) as usize; 62 + 63 + //Helper for some cache functions 64 + let mut cache = Cache { redis_pool: conn }; 65 + cache 66 + .write_to_cache_with_seconds(&key, &session_record, ttl_secs as u64) 67 + .await 68 + .map_err(backend_map("There was an error saving the session"))?; 69 + 70 + Ok(()) 71 + } 72 + 73 + async fn load(&self, session_id: &Id) -> StoreResult<Option<Record>> { 74 + let id_as_str: String = session_id.0.to_string(); 75 + let key = create_prefixed_key(TOWER_SESSION_KEY, id_as_str.as_str()); 76 + 77 + let conn = self 78 + .cache_pool 79 + .get() 80 + .await 81 + .map_err(backend_map("There was an error connecting to the cache"))?; 82 + let mut cache = Cache { redis_pool: conn }; 83 + 84 + let val = match cache.fetch_redis_json_object::<Option<Record>>(&key).await { 85 + Ok(Some(record)) => Ok(record), 86 + Ok(None) => Ok(None), 87 + Err(err) => Err(err), 88 + } 89 + .map_err(backend_map("There was an error loading the session"))?; 90 + Ok(val) 91 + } 92 + 93 + async fn delete(&self, session_id: &Id) -> StoreResult<()> { 94 + let id_as_str: String = session_id.0.to_string(); 95 + let key = create_prefixed_key(TOWER_SESSION_KEY, id_as_str.as_str()); 96 + 97 + let mut conn = self 98 + .cache_pool 99 + .get() 100 + .await 101 + .map_err(backend_map("There was an error connecting to the cache"))?; 102 + 103 + let _: usize = cmd("DEL") 104 + .arg(&key) 105 + .query_async::<usize>(&mut *conn) 106 + .await 107 + .map_err(backend_map("There was an error deleting the session"))?; 108 + 109 + Ok(()) 110 + } 111 + }
+148
web/src/session.rs
··· 1 + /// A bunch of syntax sugar too make strongly typed sessions for Axum's sessions store 2 + use crate::error_response; 3 + use axum::extract::FromRequestParts; 4 + use axum::http::StatusCode; 5 + use axum::http::request::Parts; 6 + use axum::response::Response; 7 + use serde::{Deserialize, Serialize}; 8 + use std::collections::HashMap; 9 + use std::fmt; 10 + use tower_sessions::Session; 11 + 12 + #[derive(Debug, Deserialize, Serialize, Clone)] 13 + pub enum FlashMessage { 14 + Success(String), 15 + Error(String), 16 + } 17 + 18 + /// THis is the actual session store for axum sessions 19 + #[derive(Debug, Deserialize, Serialize)] 20 + struct SessionData { 21 + did: Option<String>, 22 + 23 + flash_message: HashMap<String, FlashMessage>, 24 + } 25 + 26 + impl Default for SessionData { 27 + fn default() -> Self { 28 + Self { 29 + did: None, 30 + flash_message: HashMap::new(), 31 + } 32 + } 33 + } 34 + 35 + pub struct AxumSessionStore { 36 + session: Session, 37 + data: SessionData, 38 + } 39 + 40 + /// How you actually interact with the session store 41 + impl AxumSessionStore { 42 + const SESSION_DATA_KEY: &'static str = "session.data"; 43 + 44 + pub fn _logged_in(&self) -> bool { 45 + self.data.did.is_some() 46 + } 47 + 48 + pub async fn set_did(&mut self, did: String) -> Result<(), tower_sessions::session::Error> { 49 + self.data.did = Some(did); 50 + Self::update_session(&self.session, &self.data).await 51 + } 52 + 53 + pub fn get_did(&self) -> Option<String> { 54 + self.data.did.clone() 55 + } 56 + 57 + ///Gets the message as well as removes it from the session 58 + pub async fn get_flash_message( 59 + &mut self, 60 + key: &str, 61 + ) -> Result<Option<FlashMessage>, tower_sessions::session::Error> { 62 + let message = self.data.flash_message.get(key).cloned(); 63 + if message.is_some() { 64 + self.data.flash_message.remove(key); 65 + Self::update_session(&self.session, &self.data).await? 66 + } 67 + Ok(message) 68 + } 69 + 70 + pub async fn set_flash_message( 71 + &mut self, 72 + key: &str, 73 + message: FlashMessage, 74 + ) -> Result<(), tower_sessions::session::Error> { 75 + self.data.flash_message.insert(key.to_string(), message); 76 + Self::update_session(&self.session, &self.data).await 77 + } 78 + 79 + /// Make sure to call this or your session won't actually be saved 80 + async fn update_session( 81 + session: &Session, 82 + session_data: &SessionData, 83 + ) -> Result<(), tower_sessions::session::Error> { 84 + session.insert(Self::SESSION_DATA_KEY, session_data).await 85 + } 86 + } 87 + 88 + impl fmt::Display for AxumSessionStore { 89 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 90 + f.debug_struct("SessionStore") 91 + .field("did", &self.data.did) 92 + .finish() 93 + } 94 + } 95 + 96 + impl<S> FromRequestParts<S> for AxumSessionStore 97 + where 98 + S: Send + Sync, 99 + { 100 + type Rejection = (StatusCode, &'static str); 101 + 102 + async fn from_request_parts(req: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { 103 + let session = Session::from_request_parts(req, state).await?; 104 + 105 + let data: SessionData = session 106 + .get(Self::SESSION_DATA_KEY) 107 + .await 108 + .unwrap() 109 + .unwrap_or_default(); 110 + 111 + Ok(Self { session, data }) 112 + } 113 + } 114 + 115 + /// Helper wrapper for handling http responses if theres an error 116 + pub async fn set_flash_message( 117 + session: &mut AxumSessionStore, 118 + key: &str, 119 + flash_message: FlashMessage, 120 + ) -> Result<(), Response> { 121 + session 122 + .set_flash_message(key, flash_message) 123 + .await 124 + .map_err(|err| { 125 + log::error!("Error setting flash message: {err}"); 126 + error_response( 127 + StatusCode::INTERNAL_SERVER_ERROR, 128 + "Error setting flash message", 129 + ) 130 + }) 131 + } 132 + 133 + /// Helper wrapper for handling http responses if theres an error 134 + pub async fn get_flash_message( 135 + session: &mut AxumSessionStore, 136 + key: &str, 137 + ) -> Result<Option<FlashMessage>, Response> { 138 + match session.get_flash_message(key).await { 139 + Ok(message) => Ok(message), 140 + Err(err) => { 141 + log::error!("Error getting flash message: {err}"); 142 + Err(error_response( 143 + StatusCode::INTERNAL_SERVER_ERROR, 144 + "Error getting flash message", 145 + )) 146 + } 147 + } 148 + }
+17
web/src/templates/day.rs
··· 1 + use crate::session::FlashMessage; 2 + use askama::Template; 3 + 4 + #[derive(Template)] 5 + #[template(path = "day.askama.html")] 6 + pub struct DayTemplate { 7 + pub title: String, 8 + pub day: u8, 9 + pub challenge_one_text: String, 10 + pub challenge_one_completed: bool, 11 + pub challenge_two_text: Option<String>, 12 + pub challenge_two_completed: bool, 13 + 14 + //If these are set than it was a redirect from checking the challenge. 15 + pub part_one_submit_message: Option<FlashMessage>, 16 + pub part_two_submit_message: Option<FlashMessage>, 17 + }
+8
web/src/templates/error.rs
··· 1 + use askama::Template; 2 + 3 + #[derive(Template)] 4 + #[template(path = "error.askama.html")] 5 + pub struct ErrorTemplate<'a> { 6 + pub title: &'a str, 7 + pub message: &'a str, 8 + }
+8
web/src/templates/home.rs
··· 1 + use askama::Template; 2 + 3 + #[derive(Template)] 4 + #[template(path = "index.askama.html")] 5 + pub struct HomeTemplate { 6 + pub title: &'static str, 7 + pub unlocked_days: Vec<u8>, 8 + }
+8
web/src/templates/login.rs
··· 1 + use askama::Template; 2 + 3 + #[derive(Template)] 4 + #[template(path = "login.askama.html")] 5 + pub struct LoginTemplate<'a> { 6 + pub title: &'a str, 7 + pub error: Option<String>, 8 + }
+33
web/src/templates/mod.rs
··· 1 + use askama::Template; 2 + use axum::http::StatusCode; 3 + use axum::response::{Html, IntoResponse, Response}; 4 + 5 + pub mod day; 6 + pub mod error; 7 + pub mod home; 8 + pub mod login; 9 + 10 + pub struct HtmlTemplate<T>(pub T); 11 + 12 + /// Allows us to convert Askama HTML templates into valid HTML 13 + /// for axum to serve in the response. 14 + impl<T> IntoResponse for HtmlTemplate<T> 15 + where 16 + T: Template, 17 + { 18 + fn into_response(self) -> Response { 19 + // Attempt to render the template with askama 20 + match self.0.render() { 21 + // If we're able to successfully parse and aggregate the template, serve it 22 + Ok(html) => Html(html).into_response(), 23 + // If we're not, return an error or some bit of fallback HTML 24 + Err(err) => { 25 + log::error!("Failed to render template: {}", err); 26 + IntoResponse::into_response(( 27 + StatusCode::INTERNAL_SERVER_ERROR, 28 + "Failed to render the HTML Template", 29 + )) 30 + } 31 + } 32 + } 33 + }
+68
web/src/unlock.rs
··· 1 + use axum::extract::{Path, Request}; 2 + use axum::http; 3 + use axum::{ 4 + middleware, 5 + response::{self, IntoResponse}, 6 + }; 7 + use chrono::Datelike; 8 + 9 + pub async fn unlock( 10 + Path(mut day): Path<u8>, 11 + request: Request, 12 + next: middleware::Next, 13 + ) -> response::Response { 14 + if day == 0 { 15 + day = 1; 16 + } 17 + 18 + if day == 69 { 19 + return (http::StatusCode::FORBIDDEN, "Really?").into_response(); 20 + } 21 + 22 + if day == 42 { 23 + return ( 24 + http::StatusCode::FORBIDDEN, 25 + "Oh, you have all the answers, huh?", 26 + ) 27 + .into_response(); 28 + } 29 + 30 + if day > 25 || day < 1 { 31 + return ( 32 + http::StatusCode::FORBIDDEN, 33 + "This isn't even a day in the advent calendar????", 34 + ) 35 + .into_response(); 36 + } 37 + 38 + let now = chrono::Utc::now(); 39 + let current_day = now.day(); 40 + let month = now.month(); 41 + 42 + if month != 12 { 43 + return ( 44 + http::StatusCode::FORBIDDEN, 45 + "It's not December yet! NO PEAKING", 46 + ) 47 + .into_response(); 48 + } 49 + 50 + //Show any day previous to the current day and current day 51 + if day as u32 <= current_day { 52 + return next.run(request).await; 53 + } 54 + 55 + ( 56 + http::StatusCode::FORBIDDEN, 57 + "Now just hold on a minute. It ain't time yet.", 58 + ) 59 + .into_response() 60 + 61 + // Just commenting out for now if we do want a json endpoint and i forgot easiest way to return it 62 + // let error_response = axum::Json(serde_json::json!({ 63 + // "error": "Route Locked", 64 + // "time_remaining_seconds": time_remaining.as_secs(), 65 + // })); 66 + 67 + // (http::StatusCode::FORBIDDEN, error_response).into_response() 68 + }
+55
web/templates/day.askama.html
··· 1 + {% extends "layout.askama.html" %} 2 + 3 + {% block content %} 4 + <h2 class="text-xl">Day {{ day }}</h2> 5 + <p>Part 1:</p> 6 + <article class="prose">{{ challenge_one_text | safe }}</article> 7 + <br/> 8 + {% if let Some(msg) = part_one_submit_message %} 9 + {% match msg %} 10 + {% when FlashMessage::Success with (success) %} 11 + <span class="text-success">{{success}}</span> 12 + {% when FlashMessage::Error with (error) %} 13 + <div class="alert alert-error mb-2">{{error}}</div> 14 + {% endmatch %} 15 + {% endif %} 16 + 17 + {% if !challenge_one_completed %} 18 + <form method="post" action="/day/{{ day }}"> 19 + <!-- TODO will be optional prob load from a markdown variable? --> 20 + <!-- <input type="text" name="verification_code_one" placeholder="Enter Part 1 code" class="input input-bordered mr-2"/>--> 21 + <button class="btn" type="submit">Check answer</button> 22 + </form> 23 + {% else %} 24 + <span class="text-success">Great work, you've completed Part 1</span> 25 + {% endif %} 26 + 27 + 28 + {% if let Some(challenge_two_text) = challenge_two_text %} 29 + <hr class="my-4"/> 30 + <p>Part 2:</p> 31 + <article class="prose">{{ challenge_two_text | safe }}</article> 32 + {% if let Some(msg) = part_two_submit_message %} 33 + {% match msg %} 34 + {% when FlashMessage::Success with (success) %} 35 + <span class="text-success">{{success}}</span> 36 + {% when FlashMessage::Error with (error) %} 37 + <div class="alert alert-error mb-2">{{error}}</div> 38 + {% endmatch %} 39 + {% endif %} 40 + {% if !challenge_two_completed %} 41 + <form method="post" action="/day/{{ day }}"> 42 + <!-- TODO will be optional prob load from a markdown variable? --> 43 + <!-- <input type="text" name="verification_code_two" placeholder="Enter Part 2 code" class="input input-bordered mr-2"/>--> 44 + <button class="btn" type="submit">Check answer</button> 45 + </form> 46 + {% endif %} 47 + 48 + {% endif %} 49 + 50 + {% if challenge_one_completed && challenge_two_completed %} 51 + <br> 52 + <span class="text-success">Great work, you've completed all the challenges for today! Come back tomorrow for more at 00:00 UTC</span> 53 + {% endif %} 54 + 55 + {% endblock %}
+7
web/templates/error.askama.html
··· 1 + {% extends "layout.askama.html" %} 2 + 3 + {% block content %} 4 + <h2 class="text-xl">An error occurred</h2> 5 + <p class="mt-2">{{ message }}</p> 6 + <p class="mt-4"><a class="link" href="?">Return</a></p> 7 + {% endblock %}
+19
web/templates/index.askama.html
··· 1 + {% extends "layout.askama.html" %} 2 + 3 + {% block content %} 4 + <div class="flex items-center justify-between mb-6"> 5 + <h2 class="text-xl font-semibold">Welcome</h2> 6 + <a class="btn" href="/login">Login</a> 7 + </div> 8 + 9 + <p class="mb-3">Unlocked days:</p> 10 + {% if unlocked_days.len() == 0 %} 11 + <div class="alert">No days are unlocked yet. Please check back in December!</div> 12 + {% else %} 13 + <div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-2"> 14 + {% for d in unlocked_days %} 15 + <a class="btn" href="/day/{{ d }}">Day {{ d }}</a> 16 + {% endfor %} 17 + </div> 18 + {% endif %} 19 + {% endblock %}
+25
web/templates/layout.askama.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="utf-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1"> 6 + <title>{{ title }}</title> 7 + <link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" /> 8 + <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script> 9 + 10 + <style> 11 + body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; margin: 0; color: #111; } 12 + header { background: #0d47a1; color: #fff; padding: 1rem; } 13 + main { padding: 1rem; max-width: 900px; margin: 0 auto; } 14 + 15 + </style> 16 + </head> 17 + <body> 18 + <header> 19 + <h1>at://advent</h1> 20 + </header> 21 + <main> 22 + {% block content %}{% endblock %} 23 + </main> 24 + </body> 25 + </html>
+27
web/templates/login.askama.html
··· 1 + {% extends "layout.askama.html" %} 2 + 3 + {% block content %} 4 + <h2 class="text-xl mb-4">Login</h2> 5 + <p class="mb-4">Enter your Bluesky handle to continue.</p> 6 + {% if let Some(err) = error %} 7 + <div class="alert alert-error mb-4">{{ err }}</div> 8 + {% endif %} 9 + <form id="handle-form" class="flex gap-2" onsubmit="return goToHandle(event)"> 10 + <input id="handle-input" type="text" name="handle" placeholder="you.bsky.social" class="input input-bordered" 11 + required/> 12 + <button class="btn" type="submit">Continue</button> 13 + </form> 14 + <script> 15 + 16 + function goToHandle(e) { 17 + e.preventDefault(); 18 + const input = document.getElementById('handle-input'); 19 + const handle = (input.value || '').trim(); 20 + if (!handle) return false; 21 + const encoded = encodeURIComponent(handle); 22 + // Redirect to /handle/{handle} 23 + window.location.href = `/login/${encoded}`; 24 + return false; 25 + } 26 + </script> 27 + {% endblock %}