about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1357
-rw-r--r--Cargo.toml38
-rw-r--r--indieauth/Cargo.toml4
-rw-r--r--src/database/postgres/mod.rs4
-rw-r--r--src/indieauth/mod.rs11
-rw-r--r--src/indieauth/webauthn.rs5
-rw-r--r--src/lib.rs4
-rw-r--r--src/main.rs23
-rw-r--r--src/media/mod.rs8
-rw-r--r--src/micropub/mod.rs34
-rw-r--r--src/webmentions/check.rs23
-rw-r--r--src/webmentions/check/rcdom.rs515
-rw-r--r--templates-neo/Cargo.toml9
-rw-r--r--templates-neo/src/mf2.rs69
-rw-r--r--templates/Cargo.toml11
-rw-r--r--templates/src/lib.rs81
-rw-r--r--util/Cargo.toml6
17 files changed, 1442 insertions, 760 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 744e9bd..a2b49cd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,6 +3,16 @@
 version = 3
 
 [[package]]
+name = "Inflector"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
+[[package]]
 name = "addr2line"
 version = "0.22.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -30,7 +40,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
 dependencies = [
  "cfg-if",
- "getrandom 0.2.15",
  "once_cell",
  "version_check",
  "zerocopy 0.7.35",
@@ -137,6 +146,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
 
 [[package]]
+name = "arc-swap"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+
+[[package]]
 name = "argon2"
 version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -170,8 +185,8 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 1.0.109",
  "synstructure",
 ]
@@ -182,8 +197,8 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 1.0.109",
 ]
 
@@ -198,14 +213,15 @@ dependencies = [
 ]
 
 [[package]]
-name = "async-channel"
-version = "1.9.0"
+name = "ast_node"
+version = "0.9.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
+checksum = "f9184f2b369b3e8625712493c89b785881f27eedc6cde480a81883cef78868b2"
 dependencies = [
- "concurrent-queue",
- "event-listener",
- "futures-core",
+ "proc-macro2",
+ "quote",
+ "swc_macros_common",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -228,8 +244,8 @@ version = "0.1.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -243,12 +259,20 @@ dependencies = [
 ]
 
 [[package]]
-name = "autocfg"
-version = "0.1.8"
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "auto_impl"
+version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
+checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
 dependencies = [
- "autocfg 1.3.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -259,20 +283,20 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
 
 [[package]]
 name = "axum"
-version = "0.6.20"
+version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
+checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
 dependencies = [
  "async-trait",
  "axum-core",
  "axum-macros",
- "bitflags 1.3.2",
  "bytes",
  "futures-util",
- "headers",
  "http",
  "http-body",
+ "http-body-util",
  "hyper",
+ "hyper-util",
  "itoa 1.0.11",
  "matchit",
  "memchr",
@@ -285,61 +309,68 @@ dependencies = [
  "serde_json",
  "serde_path_to_error",
  "serde_urlencoded",
- "sync_wrapper",
+ "sync_wrapper 1.0.1",
  "tokio",
  "tower",
  "tower-layer",
  "tower-service",
+ "tracing",
 ]
 
 [[package]]
 name = "axum-core"
-version = "0.3.4"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
+checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
 dependencies = [
  "async-trait",
  "bytes",
  "futures-util",
  "http",
  "http-body",
+ "http-body-util",
  "mime",
+ "pin-project-lite",
  "rustversion",
+ "sync_wrapper 0.1.2",
  "tower-layer",
  "tower-service",
+ "tracing",
 ]
 
 [[package]]
 name = "axum-extra"
-version = "0.7.7"
+version = "0.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a93e433be9382c737320af3924f7d5fc6f89c155cf2bf88949d8f5126fab283f"
+checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733"
 dependencies = [
  "axum",
  "axum-core",
  "bytes",
  "cookie",
  "futures-util",
+ "headers",
  "http",
  "http-body",
+ "http-body-util",
  "mime",
  "pin-project-lite",
  "serde",
- "tokio",
  "tower",
  "tower-layer",
  "tower-service",
+ "tracing",
 ]
 
 [[package]]
 name = "axum-macros"
-version = "0.3.8"
+version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62"
+checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa"
 dependencies = [
  "heck 0.4.1",
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -371,6 +402,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
 
 [[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
 name = "base64ct"
 version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -388,6 +425,26 @@ dependencies = [
 ]
 
 [[package]]
+name = "base64urlsafedata"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a56894edf5cd1efa7068d7454adeb7ce0b3da4ffa5ab08cfc06165bbc62f0c7"
+dependencies = [
+ "base64 0.21.7",
+ "paste",
+ "serde",
+]
+
+[[package]]
+name = "better_scoped_tls"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "794edcc9b3fb07bb4aecaa11f093fd45663b4feadb782d68303a2268bc2701de"
+dependencies = [
+ "scoped-tls",
+]
+
+[[package]]
 name = "bitflags"
 version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -515,8 +572,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
 dependencies = [
  "heck 0.5.0",
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -527,15 +584,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
 
 [[package]]
-name = "cloudabi"
-version = "0.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
-dependencies = [
- "bitflags 1.3.2",
-]
-
-[[package]]
 name = "colorchoice"
 version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -562,14 +610,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7aa76ef19968577838a34d02848136bb9b6bdbfd7675fb968fe9c931bc434b33"
 dependencies = [
  "base64 0.13.1",
- "base64urlsafedata",
+ "base64urlsafedata 0.1.3",
  "hex",
  "openssl",
  "serde",
  "serde_json",
  "tracing",
  "url",
- "uuid 1.10.0",
+ "uuid",
 ]
 
 [[package]]
@@ -595,11 +643,11 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
 
 [[package]]
 name = "cookie"
-version = "0.17.0"
+version = "0.18.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24"
+checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
 dependencies = [
- "base64 0.21.7",
+ "base64 0.22.1",
  "hmac",
  "percent-encoding",
  "rand 0.8.5",
@@ -703,8 +751,8 @@ dependencies = [
  "itoa 0.4.8",
  "matches",
  "phf 0.8.0",
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "smallvec",
  "syn 1.0.109",
 ]
@@ -715,7 +763,7 @@ version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
 dependencies = [
- "quote 1.0.36",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -733,14 +781,13 @@ checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
 
 [[package]]
 name = "deadpool"
-version = "0.9.5"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e"
+checksum = "fb84100978c1c7b37f09ed3ce3e5f843af02c2a2c431bae5b19230dad2c1b490"
 dependencies = [
  "async-trait",
  "deadpool-runtime",
  "num_cpus",
- "retain_mut",
  "tokio",
 ]
 
@@ -782,6 +829,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
 dependencies = [
  "powerfmt",
+ "serde",
 ]
 
 [[package]]
@@ -791,8 +839,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
 dependencies = [
  "convert_case",
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "rustc_version",
  "syn 2.0.72",
 ]
@@ -821,8 +869,8 @@ version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -914,9 +962,14 @@ dependencies = [
 
 [[package]]
 name = "event-listener"
-version = "2.5.3"
+version = "5.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
 
 [[package]]
 name = "faker_rand"
@@ -931,15 +984,6 @@ dependencies = [
 
 [[package]]
 name = "fastrand"
-version = "1.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
-dependencies = [
- "instant",
-]
-
-[[package]]
-name = "fastrand"
 version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
@@ -996,10 +1040,15 @@ dependencies = [
 ]
 
 [[package]]
-name = "fuchsia-cprng"
-version = "0.1.1"
+name = "from_variant"
+version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+checksum = "32016f1242eb82af5474752d00fd8ebcd9004bd69b462b1c91de833972d08ed4"
+dependencies = [
+ "proc-macro2",
+ "swc_macros_common",
+ "syn 2.0.72",
+]
 
 [[package]]
 name = "futf"
@@ -1071,28 +1120,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
 
 [[package]]
-name = "futures-lite"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
-dependencies = [
- "fastrand 1.9.0",
- "futures-core",
- "futures-io",
- "memchr",
- "parking",
- "pin-project-lite",
- "waker-fn",
-]
-
-[[package]]
 name = "futures-macro"
 version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -1109,12 +1143,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
 
 [[package]]
-name = "futures-timer"
-version = "3.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
-
-[[package]]
 name = "futures-util"
 version = "0.3.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1181,15 +1209,15 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
 
 [[package]]
 name = "h2"
-version = "0.3.26"
+version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
+checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab"
 dependencies = [
+ "atomic-waker",
  "bytes",
  "fnv",
  "futures-core",
  "futures-sink",
- "futures-util",
  "http",
  "indexmap",
  "slab",
@@ -1216,18 +1244,18 @@ dependencies = [
 
 [[package]]
 name = "hashlink"
-version = "0.8.4"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
+checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
 dependencies = [
  "hashbrown",
 ]
 
 [[package]]
 name = "headers"
-version = "0.3.9"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270"
+checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9"
 dependencies = [
  "base64 0.21.7",
  "bytes",
@@ -1235,14 +1263,14 @@ dependencies = [
  "http",
  "httpdate",
  "mime",
- "sha1 0.10.6",
+ "sha1",
 ]
 
 [[package]]
 name = "headers-core"
-version = "0.2.0"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
+checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
 dependencies = [
  "http",
 ]
@@ -1252,9 +1280,6 @@ name = "heck"
 version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
-dependencies = [
- "unicode-segmentation",
-]
 
 [[package]]
 name = "heck"
@@ -1302,6 +1327,20 @@ dependencies = [
 ]
 
 [[package]]
+name = "hstr"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96274be293b8877e61974a607105d09c84caebe9620b47774aa8a6b942042dd4"
+dependencies = [
+ "hashbrown",
+ "new_debug_unreachable",
+ "once_cell",
+ "phf 0.11.2",
+ "rustc-hash",
+ "triomphe",
+]
+
+[[package]]
 name = "html"
 version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1318,37 +1357,37 @@ checksum = "13eca55667a5657dd1b86db77c5fe2d1810e3f9413e9555a2c4c461733dd2573"
 
 [[package]]
 name = "html5ever"
-version = "0.22.5"
+version = "0.25.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c213fa6a618dc1da552f54f85cba74b05d8e883c92ec4e89067736938084c26e"
+checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148"
 dependencies = [
  "log",
  "mac",
- "markup5ever 0.7.5",
- "proc-macro2 0.4.30",
- "quote 0.6.13",
- "syn 0.15.44",
+ "markup5ever 0.10.1",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
 ]
 
 [[package]]
 name = "html5ever"
-version = "0.25.2"
+version = "0.27.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148"
+checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4"
 dependencies = [
  "log",
  "mac",
- "markup5ever 0.10.1",
- "proc-macro2 1.0.86",
- "quote 1.0.36",
- "syn 1.0.109",
+ "markup5ever 0.12.1",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.72",
 ]
 
 [[package]]
 name = "http"
-version = "0.2.12"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
+checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
 dependencies = [
  "bytes",
  "fnv",
@@ -1357,40 +1396,25 @@ dependencies = [
 
 [[package]]
 name = "http-body"
-version = "0.4.6"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
 dependencies = [
  "bytes",
  "http",
- "pin-project-lite",
 ]
 
 [[package]]
-name = "http-range-header"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
-
-[[package]]
-name = "http-types"
-version = "2.12.0"
+name = "http-body-util"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad"
+checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
 dependencies = [
- "anyhow",
- "async-channel",
- "base64 0.13.1",
- "futures-lite",
+ "bytes",
+ "futures-util",
  "http",
- "infer",
+ "http-body",
  "pin-project-lite",
- "rand 0.7.3",
- "serde",
- "serde_json",
- "serde_qs",
- "serde_urlencoded",
- "url",
 ]
 
 [[package]]
@@ -1407,13 +1431,12 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
 
 [[package]]
 name = "hyper"
-version = "0.14.30"
+version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9"
+checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
 dependencies = [
  "bytes",
  "futures-channel",
- "futures-core",
  "futures-util",
  "h2",
  "http",
@@ -1422,38 +1445,63 @@ dependencies = [
  "httpdate",
  "itoa 1.0.11",
  "pin-project-lite",
- "socket2",
+ "smallvec",
  "tokio",
- "tower-service",
- "tracing",
  "want",
 ]
 
 [[package]]
 name = "hyper-rustls"
-version = "0.24.2"
+version = "0.27.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
+checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
 dependencies = [
  "futures-util",
  "http",
  "hyper",
- "rustls",
+ "hyper-util",
+ "rustls 0.23.12",
+ "rustls-pki-types",
  "tokio",
  "tokio-rustls",
+ "tower-service",
+ "webpki-roots 0.26.3",
 ]
 
 [[package]]
 name = "hyper-tls"
-version = "0.5.0"
+version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
 dependencies = [
  "bytes",
+ "http-body-util",
  "hyper",
+ "hyper-util",
  "native-tls",
  "tokio",
  "tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower",
+ "tower-service",
+ "tracing",
 ]
 
 [[package]]
@@ -1504,8 +1552,8 @@ version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
 ]
 
 [[package]]
@@ -1519,27 +1567,24 @@ dependencies = [
 ]
 
 [[package]]
-name = "infer"
-version = "0.2.3"
+name = "ipnet"
+version = "2.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
+checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
 
 [[package]]
-name = "instant"
-version = "0.1.13"
+name = "is-macro"
+version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
+checksum = "59a85abdc13717906baccb5a1e435556ce0df215f242892f721dff62bf25288f"
 dependencies = [
- "cfg-if",
+ "Inflector",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.72",
 ]
 
 [[package]]
-name = "ipnet"
-version = "2.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
-
-[[package]]
 name = "is_terminal_polyfill"
 version = "1.70.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1575,7 +1620,7 @@ dependencies = [
  "async-trait",
  "axum",
  "axum-extra",
- "base64 0.21.7",
+ "base64 0.22.1",
  "bytes",
  "chrono",
  "clap",
@@ -1586,6 +1631,7 @@ dependencies = [
  "futures",
  "futures-util",
  "hex",
+ "html5ever 0.27.0",
  "hyper",
  "kittybox-frontend-renderer",
  "kittybox-indieauth",
@@ -1615,12 +1661,12 @@ dependencies = [
  "tower",
  "tower-http",
  "tracing",
- "tracing-log 0.1.4",
+ "tracing-log",
  "tracing-subscriber",
  "tracing-test",
  "tracing-tree",
  "url",
- "uuid 1.10.0",
+ "uuid",
  "webauthn-rs",
  "wiremock",
 ]
@@ -1642,6 +1688,7 @@ dependencies = [
  "microformats",
  "rand 0.8.5",
  "serde_json",
+ "time",
  "walkdir",
 ]
 
@@ -1663,6 +1710,7 @@ dependencies = [
  "rand 0.8.5",
  "serde_json",
  "thiserror",
+ "time",
  "url",
  "walkdir",
 ]
@@ -1695,7 +1743,7 @@ dependencies = [
  "serde_json",
  "sqlx",
  "tokio",
- "uuid 1.10.0",
+ "uuid",
 ]
 
 [[package]]
@@ -1757,9 +1805,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
 
 [[package]]
 name = "libsqlite3-sys"
-version = "0.27.0"
+version = "0.28.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716"
+checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f"
 dependencies = [
  "cc",
  "pkg-config",
@@ -1774,12 +1822,12 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
 
 [[package]]
 name = "listenfd"
-version = "0.5.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c02b14f35d9f5f082fd0b1b34aa0ef32e3354c859c721d7f3325b3f79a42ba54"
+checksum = "e0500463acd96259d219abb05dc57e5a076ef04b2db9a2112846929b5f174c96"
 dependencies = [
  "libc",
- "uuid 0.8.2",
+ "uuid",
  "winapi",
 ]
 
@@ -1789,7 +1837,7 @@ version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
 dependencies = [
- "autocfg 1.3.0",
+ "autocfg",
  "scopeguard",
 ]
 
@@ -1816,52 +1864,49 @@ dependencies = [
 
 [[package]]
 name = "markup"
-version = "0.13.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd9196a235d499738d04f6a2466ce2610bf6b84730610efea8bee1b90d028b0d"
+checksum = "74a887ad620fe1022257343ac77fcdd3720e92888e1b2e66e1b7a4707f453898"
 dependencies = [
- "itoa 1.0.11",
  "markup-proc-macro",
 ]
 
 [[package]]
 name = "markup-proc-macro"
-version = "0.13.1"
+version = "0.15.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a927f0e237dcbdd8c1a8ab03c4e1e8b1999804c448ebf06ff3b5512506c8150"
+checksum = "9ab6ee21fd1855134cacf2f41afdf45f1bc456c7d7f6165d763b4647062dd2be"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
- "syn 1.0.109",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.72",
 ]
 
 [[package]]
 name = "markup5ever"
-version = "0.7.5"
+version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "897636f9850c3eef4905a5540683ed53dc9393860f0846cab2c2ddf9939862ff"
+checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
 dependencies = [
- "phf 0.7.24",
- "phf_codegen 0.7.24",
- "serde",
- "serde_derive",
- "serde_json",
- "string_cache 0.7.5",
- "string_cache_codegen 0.4.4",
+ "log",
+ "phf 0.8.0",
+ "phf_codegen 0.8.0",
+ "string_cache",
+ "string_cache_codegen",
  "tendril",
 ]
 
 [[package]]
 name = "markup5ever"
-version = "0.10.1"
+version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
+checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45"
 dependencies = [
  "log",
- "phf 0.8.0",
- "phf_codegen 0.8.0",
- "string_cache 0.8.7",
- "string_cache_codegen 0.5.2",
+ "phf 0.11.2",
+ "phf_codegen 0.11.2",
+ "string_cache",
+ "string_cache_codegen",
  "tendril",
 ]
 
@@ -1904,18 +1949,39 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
 
 [[package]]
 name = "microformats"
-version = "0.3.0"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18e7fccd15cba21880e824d71b8354e9e67561c9cd2bf3ec09b21dba26392ecb"
+checksum = "ed8bbf237d3068b89f1e445b9789bd0e5731638a018227722348cfc84b967fb8"
 dependencies = [
- "chrono",
- "html5ever 0.22.5",
  "lazy_static",
- "log",
+ "microformats-types",
  "regex",
  "serde",
  "serde_json",
+ "swc_common",
+ "swc_html_ast",
+ "swc_html_codegen",
+ "swc_html_parser",
+ "swc_html_visit",
  "thiserror",
+ "time",
+ "tracing",
+ "tracing-unwrap",
+ "url",
+]
+
+[[package]]
+name = "microformats-types"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d4e0d8cf1d664df99cf57e6bcff23e4ee3e8676da1ede5c15ecb398783fe72a"
+dependencies = [
+ "lazy_static",
+ "regex",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "time",
  "url",
 ]
 
@@ -1954,16 +2020,15 @@ dependencies = [
 
 [[package]]
 name = "multer"
-version = "2.1.0"
+version = "3.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2"
+checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b"
 dependencies = [
  "bytes",
  "encoding_rs",
  "futures-util",
  "http",
  "httparse",
- "log",
  "memchr",
  "mime",
  "spin",
@@ -2026,6 +2091,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "nu-ansi-term"
+version = "0.50.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
 name = "num-bigint"
 version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2073,7 +2147,7 @@ version = "0.1.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
 dependencies = [
- "autocfg 1.3.0",
+ "autocfg",
  "num-integer",
  "num-traits",
 ]
@@ -2084,7 +2158,7 @@ version = "0.2.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
 dependencies = [
- "autocfg 1.3.0",
+ "autocfg",
  "libm",
 ]
 
@@ -2143,8 +2217,8 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -2235,15 +2309,6 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
 
 [[package]]
 name = "phf"
-version = "0.7.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18"
-dependencies = [
- "phf_shared 0.7.24",
-]
-
-[[package]]
-name = "phf"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
@@ -2254,13 +2319,12 @@ dependencies = [
 ]
 
 [[package]]
-name = "phf_codegen"
-version = "0.7.24"
+name = "phf"
+version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e"
+checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
 dependencies = [
- "phf_generator 0.7.24",
- "phf_shared 0.7.24",
+ "phf_shared 0.11.2",
 ]
 
 [[package]]
@@ -2274,13 +2338,13 @@ dependencies = [
 ]
 
 [[package]]
-name = "phf_generator"
-version = "0.7.24"
+name = "phf_codegen"
+version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662"
+checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
 dependencies = [
- "phf_shared 0.7.24",
- "rand 0.6.5",
+ "phf_generator 0.11.2",
+ "phf_shared 0.11.2",
 ]
 
 [[package]]
@@ -2304,6 +2368,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "phf_generator"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
+dependencies = [
+ "phf_shared 0.11.2",
+ "rand 0.8.5",
+]
+
+[[package]]
 name = "phf_macros"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2312,36 +2386,36 @@ dependencies = [
  "phf_generator 0.8.0",
  "phf_shared 0.8.0",
  "proc-macro-hack",
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 1.0.109",
 ]
 
 [[package]]
 name = "phf_shared"
-version = "0.7.24"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"
+checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
 dependencies = [
- "siphasher 0.2.3",
+ "siphasher",
 ]
 
 [[package]]
 name = "phf_shared"
-version = "0.8.0"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
+checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
 dependencies = [
- "siphasher 0.3.11",
+ "siphasher",
 ]
 
 [[package]]
 name = "phf_shared"
-version = "0.10.0"
+version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
+checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
 dependencies = [
- "siphasher 0.3.11",
+ "siphasher",
 ]
 
 [[package]]
@@ -2359,8 +2433,8 @@ version = "1.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -2432,15 +2506,6 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
 
 [[package]]
 name = "proc-macro2"
-version = "0.4.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
-dependencies = [
- "unicode-xid 0.1.0",
-]
-
-[[package]]
-name = "proc-macro2"
 version = "1.0.86"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
@@ -2495,40 +2560,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
 
 [[package]]
-name = "quote"
-version = "0.6.13"
+name = "quinn"
+version = "0.11.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
+checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad"
 dependencies = [
- "proc-macro2 0.4.30",
+ "bytes",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls 0.23.12",
+ "thiserror",
+ "tokio",
+ "tracing",
 ]
 
 [[package]]
-name = "quote"
-version = "1.0.36"
+name = "quinn-proto"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe"
 dependencies = [
- "proc-macro2 1.0.86",
+ "bytes",
+ "rand 0.8.5",
+ "ring",
+ "rustc-hash",
+ "rustls 0.23.12",
+ "slab",
+ "thiserror",
+ "tinyvec",
+ "tracing",
 ]
 
 [[package]]
-name = "rand"
-version = "0.6.5"
+name = "quinn-udp"
+version = "0.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
+checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285"
 dependencies = [
- "autocfg 0.1.8",
  "libc",
- "rand_chacha 0.1.1",
- "rand_core 0.4.2",
- "rand_hc 0.1.0",
- "rand_isaac",
- "rand_jitter",
- "rand_os",
- "rand_pcg 0.1.2",
- "rand_xorshift",
- "winapi",
+ "once_cell",
+ "socket2",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
 ]
 
 [[package]]
@@ -2541,8 +2624,8 @@ dependencies = [
  "libc",
  "rand_chacha 0.2.2",
  "rand_core 0.5.1",
- "rand_hc 0.2.0",
- "rand_pcg 0.2.1",
+ "rand_hc",
+ "rand_pcg",
 ]
 
 [[package]]
@@ -2558,16 +2641,6 @@ dependencies = [
 
 [[package]]
 name = "rand_chacha"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
-dependencies = [
- "autocfg 0.1.8",
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rand_chacha"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
@@ -2588,21 +2661,6 @@ dependencies = [
 
 [[package]]
 name = "rand_core"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
-dependencies = [
- "rand_core 0.4.2",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
-
-[[package]]
-name = "rand_core"
 version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
@@ -2621,15 +2679,6 @@ dependencies = [
 
 [[package]]
 name = "rand_hc"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rand_hc"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
@@ -2638,50 +2687,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "rand_isaac"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rand_jitter"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
-dependencies = [
- "libc",
- "rand_core 0.4.2",
- "winapi",
-]
-
-[[package]]
-name = "rand_os"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
-dependencies = [
- "cloudabi",
- "fuchsia-cprng",
- "libc",
- "rand_core 0.4.2",
- "rdrand",
- "winapi",
-]
-
-[[package]]
-name = "rand_pcg"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
-dependencies = [
- "autocfg 0.1.8",
- "rand_core 0.4.2",
-]
-
-[[package]]
 name = "rand_pcg"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2691,38 +2696,23 @@ dependencies = [
 ]
 
 [[package]]
-name = "rand_xorshift"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rdrand"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
 name = "redis"
-version = "0.21.7"
+version = "0.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "152f3863635cbb76b73bc247845781098302c6c9ad2060e1a9a7de56840346b6"
+checksum = "8cc5b667390cb038bc65fc4b18c06e2550469f7e06a02d886f1a018a11f63563"
 dependencies = [
+ "arc-swap",
  "async-trait",
  "bytes",
  "combine",
  "futures-util",
  "itoa 1.0.11",
+ "num-bigint",
  "percent-encoding",
  "pin-project-lite",
  "ryu",
- "sha1 0.6.1",
+ "sha1_smol",
+ "socket2",
  "tokio",
  "tokio-util",
  "url",
@@ -2798,22 +2788,22 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
 
 [[package]]
 name = "reqwest"
-version = "0.11.27"
+version = "0.12.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
+checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37"
 dependencies = [
  "async-compression",
- "base64 0.21.7",
+ "base64 0.22.1",
  "bytes",
- "encoding_rs",
  "futures-core",
  "futures-util",
- "h2",
  "http",
  "http-body",
+ "http-body-util",
  "hyper",
  "hyper-rustls",
  "hyper-tls",
+ "hyper-util",
  "ipnet",
  "js-sys",
  "log",
@@ -2822,13 +2812,14 @@ dependencies = [
  "once_cell",
  "percent-encoding",
  "pin-project-lite",
- "rustls",
- "rustls-pemfile",
+ "quinn",
+ "rustls 0.23.12",
+ "rustls-pemfile 2.1.2",
+ "rustls-pki-types",
  "serde",
  "serde_json",
  "serde_urlencoded",
- "sync_wrapper",
- "system-configuration",
+ "sync_wrapper 1.0.1",
  "tokio",
  "tokio-native-tls",
  "tokio-rustls",
@@ -2839,17 +2830,11 @@ dependencies = [
  "wasm-bindgen-futures",
  "wasm-streams",
  "web-sys",
- "webpki-roots",
+ "webpki-roots 0.26.3",
  "winreg",
 ]
 
 [[package]]
-name = "retain_mut"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0"
-
-[[package]]
 name = "ring"
 version = "0.17.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2897,6 +2882,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
 
 [[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
 name = "rustc_version"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2933,13 +2924,26 @@ version = "0.21.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
 dependencies = [
- "log",
  "ring",
- "rustls-webpki",
+ "rustls-webpki 0.101.7",
  "sct",
 ]
 
 [[package]]
+name = "rustls"
+version = "0.23.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki 0.102.6",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
 name = "rustls-pemfile"
 version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2949,6 +2953,22 @@ dependencies = [
 ]
 
 [[package]]
+name = "rustls-pemfile"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
+dependencies = [
+ "base64 0.22.1",
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
+
+[[package]]
 name = "rustls-webpki"
 version = "0.101.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2959,6 +2979,17 @@ dependencies = [
 ]
 
 [[package]]
+name = "rustls-webpki"
+version = "0.102.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
 name = "rustversion"
 version = "1.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2989,6 +3020,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "scoped-tls"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
+
+[[package]]
 name = "scopeguard"
 version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3078,8 +3115,8 @@ version = "1.0.204"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -3106,17 +3143,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "serde_qs"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6"
-dependencies = [
- "percent-encoding",
- "serde",
- "thiserror",
-]
-
-[[package]]
 name = "serde_urlencoded"
 version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3149,15 +3175,6 @@ dependencies = [
 
 [[package]]
 name = "sha1"
-version = "0.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
-dependencies = [
- "sha1_smol",
-]
-
-[[package]]
-name = "sha1"
 version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
@@ -3214,12 +3231,6 @@ dependencies = [
 
 [[package]]
 name = "siphasher"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
-
-[[package]]
-name = "siphasher"
 version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
@@ -3230,7 +3241,7 @@ version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
 dependencies = [
- "autocfg 1.3.0",
+ "autocfg",
 ]
 
 [[package]]
@@ -3238,6 +3249,9 @@ name = "smallvec"
 version = "1.13.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+dependencies = [
+ "serde",
+]
 
 [[package]]
 name = "socket2"
@@ -3291,9 +3305,9 @@ dependencies = [
 
 [[package]]
 name = "sqlx"
-version = "0.7.4"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa"
+checksum = "27144619c6e5802f1380337a209d2ac1c431002dd74c6e60aebff3c506dc4f0c"
 dependencies = [
  "sqlx-core",
  "sqlx-macros",
@@ -3304,11 +3318,10 @@ dependencies = [
 
 [[package]]
 name = "sqlx-core"
-version = "0.7.4"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6"
+checksum = "a999083c1af5b5d6c071d34a708a19ba3e02106ad82ef7bbd69f5e48266b613b"
 dependencies = [
- "ahash",
  "atoi",
  "byteorder",
  "bytes",
@@ -3322,6 +3335,7 @@ dependencies = [
  "futures-intrusive",
  "futures-io",
  "futures-util",
+ "hashbrown",
  "hashlink",
  "hex",
  "indexmap",
@@ -3331,8 +3345,8 @@ dependencies = [
  "once_cell",
  "paste",
  "percent-encoding",
- "rustls",
- "rustls-pemfile",
+ "rustls 0.21.12",
+ "rustls-pemfile 1.0.4",
  "serde",
  "serde_json",
  "sha2",
@@ -3343,36 +3357,36 @@ dependencies = [
  "tokio-stream",
  "tracing",
  "url",
- "uuid 1.10.0",
- "webpki-roots",
+ "uuid",
+ "webpki-roots 0.25.4",
 ]
 
 [[package]]
 name = "sqlx-macros"
-version = "0.7.4"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127"
+checksum = "a23217eb7d86c584b8cbe0337b9eacf12ab76fe7673c513141ec42565698bb88"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "sqlx-core",
  "sqlx-macros-core",
- "syn 1.0.109",
+ "syn 2.0.72",
 ]
 
 [[package]]
 name = "sqlx-macros-core"
-version = "0.7.4"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8"
+checksum = "1a099220ae541c5db479c6424bdf1b200987934033c2584f79a0e1693601e776"
 dependencies = [
  "dotenvy",
  "either",
- "heck 0.4.1",
+ "heck 0.5.0",
  "hex",
  "once_cell",
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "serde",
  "serde_json",
  "sha2",
@@ -3380,7 +3394,7 @@ dependencies = [
  "sqlx-mysql",
  "sqlx-postgres",
  "sqlx-sqlite",
- "syn 1.0.109",
+ "syn 2.0.72",
  "tempfile",
  "tokio",
  "url",
@@ -3388,12 +3402,12 @@ dependencies = [
 
 [[package]]
 name = "sqlx-mysql"
-version = "0.7.4"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418"
+checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6"
 dependencies = [
  "atoi",
- "base64 0.21.7",
+ "base64 0.22.1",
  "bitflags 2.6.0",
  "byteorder",
  "bytes",
@@ -3419,25 +3433,25 @@ dependencies = [
  "rand 0.8.5",
  "rsa",
  "serde",
- "sha1 0.10.6",
+ "sha1",
  "sha2",
  "smallvec",
  "sqlx-core",
  "stringprep",
  "thiserror",
  "tracing",
- "uuid 1.10.0",
+ "uuid",
  "whoami",
 ]
 
 [[package]]
 name = "sqlx-postgres"
-version = "0.7.4"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
+checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21"
 dependencies = [
  "atoi",
- "base64 0.21.7",
+ "base64 0.22.1",
  "bitflags 2.6.0",
  "byteorder",
  "chrono",
@@ -3466,15 +3480,15 @@ dependencies = [
  "stringprep",
  "thiserror",
  "tracing",
- "uuid 1.10.0",
+ "uuid",
  "whoami",
 ]
 
 [[package]]
 name = "sqlx-sqlite"
-version = "0.7.4"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa"
+checksum = "9b2cdd83c008a622d94499c0006d8ee5f821f36c89b7d625c900e5dc30b5c5ee"
 dependencies = [
  "atoi",
  "chrono",
@@ -3488,11 +3502,11 @@ dependencies = [
  "log",
  "percent-encoding",
  "serde",
+ "serde_urlencoded",
  "sqlx-core",
  "tracing",
  "url",
- "urlencoding",
- "uuid 1.10.0",
+ "uuid",
 ]
 
 [[package]]
@@ -3503,21 +3517,6 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
 [[package]]
 name = "string_cache"
-version = "0.7.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89c058a82f9fd69b1becf8c274f412281038877c553182f1d02eb027045a2d67"
-dependencies = [
- "lazy_static",
- "new_debug_unreachable",
- "phf_shared 0.7.24",
- "precomputed-hash",
- "serde",
- "string_cache_codegen 0.4.4",
- "string_cache_shared",
-]
-
-[[package]]
-name = "string_cache"
 version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
@@ -3532,34 +3531,27 @@ dependencies = [
 
 [[package]]
 name = "string_cache_codegen"
-version = "0.4.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0f45ed1b65bf9a4bf2f7b7dc59212d1926e9eaf00fa998988e420fd124467c6"
-dependencies = [
- "phf_generator 0.7.24",
- "phf_shared 0.7.24",
- "proc-macro2 1.0.86",
- "quote 1.0.36",
- "string_cache_shared",
-]
-
-[[package]]
-name = "string_cache_codegen"
 version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
 dependencies = [
  "phf_generator 0.10.0",
  "phf_shared 0.10.0",
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
 ]
 
 [[package]]
-name = "string_cache_shared"
-version = "0.3.0"
+name = "string_enum"
+version = "0.4.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
+checksum = "05e383308aebc257e7d7920224fa055c632478d92744eca77f99be8fa1545b90"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "swc_macros_common",
+ "syn 2.0.72",
+]
 
 [[package]]
 name = "stringprep"
@@ -3585,14 +3577,164 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
 
 [[package]]
-name = "syn"
-version = "0.15.44"
+name = "swc_atoms"
+version = "0.6.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
+checksum = "bb6567e4e67485b3e7662b486f1565bdae54bd5b9d6b16b2ba1a9babb1e42125"
 dependencies = [
- "proc-macro2 0.4.30",
- "quote 0.6.13",
- "unicode-xid 0.1.0",
+ "hstr",
+ "once_cell",
+ "rustc-hash",
+ "serde",
+]
+
+[[package]]
+name = "swc_common"
+version = "0.33.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2f9706038906e66f3919028f9f7a37f3ed552f1b85578e93f4468742e2da438"
+dependencies = [
+ "ast_node",
+ "better_scoped_tls",
+ "cfg-if",
+ "either",
+ "from_variant",
+ "new_debug_unreachable",
+ "num-bigint",
+ "once_cell",
+ "rustc-hash",
+ "serde",
+ "siphasher",
+ "swc_atoms",
+ "swc_eq_ignore_macros",
+ "swc_visit",
+ "tracing",
+ "unicode-width",
+ "url",
+]
+
+[[package]]
+name = "swc_eq_ignore_macros"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63db0adcff29d220c3d151c5b25c0eabe7e32dd936212b84cdaa1392e3130497"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.72",
+]
+
+[[package]]
+name = "swc_html_ast"
+version = "0.33.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59957df8048be691db04e6e358b29c6ff1274cd60ee2b4c2141e1d90b598d24e"
+dependencies = [
+ "is-macro",
+ "serde",
+ "string_enum",
+ "swc_atoms",
+ "swc_common",
+]
+
+[[package]]
+name = "swc_html_codegen"
+version = "0.42.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4c28899c6d01596124686dae5d139412488f4066d013a6c5691e497f5e9a98f"
+dependencies = [
+ "auto_impl",
+ "bitflags 2.6.0",
+ "rustc-hash",
+ "swc_atoms",
+ "swc_common",
+ "swc_html_ast",
+ "swc_html_codegen_macros",
+ "swc_html_utils",
+]
+
+[[package]]
+name = "swc_html_codegen_macros"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e593a6cbb3a49230fbab3171d4493f7d0fb1e20a34d9a9f9e972550690408ba8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "swc_macros_common",
+ "syn 2.0.72",
+]
+
+[[package]]
+name = "swc_html_parser"
+version = "0.39.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06cda5fcfd7c979a473be215cb7263e13bb7707bec46d0b6fbf364d203eea2d"
+dependencies = [
+ "swc_atoms",
+ "swc_common",
+ "swc_html_ast",
+ "swc_html_utils",
+]
+
+[[package]]
+name = "swc_html_utils"
+version = "0.18.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "406d1fcf69915a6726065060a9e85e7f36d66239708901d9bd0ad4d4b4e935a8"
+dependencies = [
+ "once_cell",
+ "serde",
+ "serde_json",
+ "swc_atoms",
+ "swc_common",
+]
+
+[[package]]
+name = "swc_html_visit"
+version = "0.33.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cffd11c6331e830a6f954597edd612d9393b6a4edb6e88004e5d7e84ee73b570"
+dependencies = [
+ "serde",
+ "swc_atoms",
+ "swc_common",
+ "swc_html_ast",
+ "swc_visit",
+]
+
+[[package]]
+name = "swc_macros_common"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f486687bfb7b5c560868f69ed2d458b880cebc9babebcb67e49f31b55c5bf847"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.72",
+]
+
+[[package]]
+name = "swc_visit"
+version = "0.5.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "043d11fe683dcb934583ead49405c0896a5af5face522e4682c16971ef7871b9"
+dependencies = [
+ "either",
+ "swc_visit_macros",
+]
+
+[[package]]
+name = "swc_visit_macros"
+version = "0.5.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92807d840959f39c60ce8a774a3f83e8193c658068e6d270dbe0a05e40e90b41"
+dependencies = [
+ "Inflector",
+ "proc-macro2",
+ "quote",
+ "swc_macros_common",
+ "syn 2.0.72",
 ]
 
 [[package]]
@@ -3601,8 +3743,8 @@ version = "1.0.109"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "unicode-ident",
 ]
 
@@ -3612,8 +3754,8 @@ version = "2.0.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "unicode-ident",
 ]
 
@@ -3624,36 +3766,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
 
 [[package]]
+name = "sync_wrapper"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
+
+[[package]]
 name = "synstructure"
 version = "0.12.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 1.0.109",
- "unicode-xid 0.2.4",
-]
-
-[[package]]
-name = "system-configuration"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
-dependencies = [
- "bitflags 1.3.2",
- "core-foundation",
- "system-configuration-sys",
-]
-
-[[package]]
-name = "system-configuration-sys"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
-dependencies = [
- "core-foundation-sys",
- "libc",
+ "unicode-xid",
 ]
 
 [[package]]
@@ -3663,7 +3790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
 dependencies = [
  "cfg-if",
- "fastrand 2.1.0",
+ "fastrand",
  "rustix",
  "windows-sys 0.52.0",
 ]
@@ -3700,8 +3827,8 @@ version = "1.0.63"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -3786,8 +3913,8 @@ version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -3803,11 +3930,12 @@ dependencies = [
 
 [[package]]
 name = "tokio-rustls"
-version = "0.24.1"
+version = "0.26.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
+checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
 dependencies = [
- "rustls",
+ "rustls 0.23.12",
+ "rustls-pki-types",
  "tokio",
 ]
 
@@ -3853,17 +3981,16 @@ dependencies = [
 
 [[package]]
 name = "tower-http"
-version = "0.3.5"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
+checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
 dependencies = [
- "bitflags 1.3.2",
+ "bitflags 2.6.0",
  "bytes",
- "futures-core",
  "futures-util",
  "http",
  "http-body",
- "http-range-header",
+ "http-body-util",
  "pin-project-lite",
  "tower-layer",
  "tower-service",
@@ -3900,8 +4027,8 @@ version = "0.1.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -3917,17 +4044,6 @@ dependencies = [
 
 [[package]]
 name = "tracing-log"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
-dependencies = [
- "log",
- "once_cell",
- "tracing-core",
-]
-
-[[package]]
-name = "tracing-log"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
@@ -3954,7 +4070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
 dependencies = [
  "matchers",
- "nu-ansi-term",
+ "nu-ansi-term 0.46.0",
  "once_cell",
  "regex",
  "serde",
@@ -3964,7 +4080,7 @@ dependencies = [
  "thread_local",
  "tracing",
  "tracing-core",
- "tracing-log 0.2.0",
+ "tracing-log",
  "tracing-serde",
 ]
 
@@ -3985,23 +4101,42 @@ version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568"
 dependencies = [
- "quote 1.0.36",
+ "quote",
  "syn 2.0.72",
 ]
 
 [[package]]
 name = "tracing-tree"
-version = "0.2.5"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ec6adcab41b1391b08a308cc6302b79f8095d1673f6947c2dc65ffb028b0b2d"
+checksum = "f459ca79f1b0d5f71c54ddfde6debfc59c8b6eeb46808ae492077f739dc7b49c"
 dependencies = [
- "nu-ansi-term",
+ "nu-ansi-term 0.50.1",
  "tracing-core",
- "tracing-log 0.1.4",
+ "tracing-log",
  "tracing-subscriber",
 ]
 
 [[package]]
+name = "tracing-unwrap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4e33415be97f5ae70322d6fefc696bbc08887d8835400d6c77f059469b30354"
+dependencies = [
+ "tracing",
+]
+
+[[package]]
+name = "triomphe"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6631e42e10b40c0690bf92f404ebcfe6e1fdb480391d15f17cc8e96eeed5369"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+]
+
+[[package]]
 name = "try-lock"
 version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4053,10 +4188,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
 
 [[package]]
-name = "unicode-xid"
-version = "0.1.0"
+name = "unicode-width"
+version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
+checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
 
 [[package]]
 name = "unicode-xid"
@@ -4089,12 +4224,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "urlencoding"
-version = "2.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
-
-[[package]]
 name = "utf-8"
 version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4108,12 +4237,6 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 
 [[package]]
 name = "uuid"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
-
-[[package]]
-name = "uuid"
 version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
@@ -4141,12 +4264,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
 
 [[package]]
-name = "waker-fn"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
-
-[[package]]
 name = "walkdir"
 version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4202,8 +4319,8 @@ dependencies = [
  "bumpalo",
  "log",
  "once_cell",
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
  "wasm-bindgen-shared",
 ]
@@ -4226,7 +4343,7 @@ version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
 dependencies = [
- "quote 1.0.36",
+ "quote",
  "wasm-bindgen-macro-support",
 ]
 
@@ -4236,8 +4353,8 @@ version = "0.2.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
@@ -4273,50 +4390,67 @@ dependencies = [
 ]
 
 [[package]]
+name = "webauthn-attestation-ca"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b0f2ebaf5650ca15b515a761f31ed6477fa2312491cf632a71102ac22b82784"
+dependencies = [
+ "base64urlsafedata 0.5.0",
+ "openssl",
+ "serde",
+ "tracing",
+ "uuid",
+]
+
+[[package]]
 name = "webauthn-rs"
-version = "0.4.8"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2db00711c712414e93b019c4596315085792215bc2ac2d5872f9e8913b0a6316"
+checksum = "fb9d7cdc9ec26e3e06f7e8ee1433e6fa3627c6c075ab3effbc3a2280c2f526c0"
 dependencies = [
- "base64urlsafedata",
+ "base64urlsafedata 0.5.0",
  "serde",
  "tracing",
  "url",
- "uuid 1.10.0",
+ "uuid",
  "webauthn-rs-core",
 ]
 
 [[package]]
 name = "webauthn-rs-core"
-version = "0.4.9"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "294c78c83f12153a51e1cf1e6970b5da1397645dada39033a9c3173a8fc4fc2b"
+checksum = "cf1ee1dc7f4138b8fd05a74a6eae93ddaf504c5a60861f1eb95d9de3172900b3"
 dependencies = [
- "base64 0.13.1",
- "base64urlsafedata",
+ "base64 0.21.7",
+ "base64urlsafedata 0.5.0",
  "compact_jwt",
  "der-parser",
+ "hex",
  "nom",
  "openssl",
  "rand 0.8.5",
+ "rand_chacha 0.3.1",
  "serde",
  "serde_cbor_2",
  "serde_json",
  "thiserror",
  "tracing",
  "url",
- "uuid 1.10.0",
+ "uuid",
+ "webauthn-attestation-ca",
  "webauthn-rs-proto",
  "x509-parser",
 ]
 
 [[package]]
 name = "webauthn-rs-proto"
-version = "0.4.9"
+version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d24e638361a63ba5c0a0be6a60229490fcdf33740ed63df5bb6bdb627b52a138"
+checksum = "1f1c6dc254607f48eec3bdb35b86b377202436859ca1e4c9290afafd7349dcc3"
 dependencies = [
- "base64urlsafedata",
+ "base64 0.21.7",
+ "base64urlsafedata 0.5.0",
  "serde",
  "serde_json",
  "url",
@@ -4329,6 +4463,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
 
 [[package]]
+name = "webpki-roots"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
 name = "whoami"
 version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4519,9 +4662,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
 name = "winreg"
-version = "0.50.0"
+version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
+checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
 dependencies = [
  "cfg-if",
  "windows-sys 0.48.0",
@@ -4529,24 +4672,26 @@ dependencies = [
 
 [[package]]
 name = "wiremock"
-version = "0.5.22"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13a3a53eaf34f390dd30d7b1b078287dd05df2aa2e21a589ccb80f5c7253c2e9"
+checksum = "6a59f8ae78a4737fb724f20106fb35ccb7cfe61ff335665d3042b3aa98e34717"
 dependencies = [
  "assert-json-diff",
  "async-trait",
  "base64 0.21.7",
  "deadpool",
  "futures",
- "futures-timer",
- "http-types",
+ "http",
+ "http-body-util",
  "hyper",
+ "hyper-util",
  "log",
  "once_cell",
  "regex",
  "serde",
  "serde_json",
  "tokio",
+ "url",
 ]
 
 [[package]]
@@ -4592,8 +4737,8 @@ version = "0.6.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "125139de3f6b9d625c39e2efdd73d41bdac468ccd556556440e322be0e1bbd91"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
@@ -4603,8 +4748,8 @@ version = "0.7.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
- "proc-macro2 1.0.86",
- "quote 1.0.36",
+ "proc-macro2",
+ "quote",
  "syn 2.0.72",
 ]
 
diff --git a/Cargo.toml b/Cargo.toml
index 974bb67..9062cfe 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@ default = ["rustls", "postgres"]
 #util = ["anyhow"]
 #migration = ["util"]
 webauthn = ["openssl", "dep:webauthn"]
-openssl = ["reqwest/native-tls-crate", "reqwest/native-tls-alpn", "sqlx/tls-native-tls"]
+openssl = ["reqwest/native-tls", "reqwest/native-tls-alpn", "sqlx/tls-native-tls"]
 rustls = ["reqwest/rustls-tls-webpki-roots", "sqlx/tls-rustls"]
 cli = ["clap"]
 postgres = ["sqlx", "kittybox-util/sqlx"]
@@ -71,7 +71,7 @@ features = ["axum"]
 
 [dev-dependencies]
 tempfile = "^3.4.0"          # Temporary file managment
-wiremock = "^0.5.14"
+wiremock = "^0.6.1"
 faker_rand = "^0.1.1"        # Seedable, rand-compatible generators of fake data
 rand = "^0.8.5"              # Utilities for random number generation
 tracing-test = "^0.2.2"
@@ -87,8 +87,7 @@ futures = "^0.3.14"          # An implementation of futures and streams
 futures-util = "^0.3.14"     # Common utilities and extension traits for the futures-rs library
 hex = "^0.4.3"
 lazy_static = "^1.4.0"       # A macro for declaring lazily evaluated statics in Rust
-listenfd = "^0.5.0"          # A simple library to work with listenfds passed from the outside (systemd/catflap socket activation)
-#log = "^0.4.14"              # A lightweight logging facade for Rust
+listenfd = "^1.0.1"          # A simple library to work with listenfds passed from the outside (systemd/catflap socket activation)
 markdown = "^1.0.0-alpha.7"  # Native Rust library for parsing Markdown and (outputting HTML)
 newbase60 = "^0.1.3"         # A library that implements Tantek Çelik's New Base 60
 rand = "^0.8.4"              # Random number generators.
@@ -99,13 +98,14 @@ relative-path = "^1.5.0"     # Portable relative paths for Rust
 sha2 = "^0.10.7"              # SHA-2 series of algorithms for Rust
 uuid = "^1.3.3"
 tracing = { version = "0.1.34", features = [] }
-tracing-tree = "0.2.1"
-tracing-log = "0.1.3"
+tracing-tree = "0.4.0"
+tracing-log = "0.2.0"
 tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
-tower-http = { version = "0.3.3", features = ["trace", "cors", "catch-panic", "sensitive-headers"] }
+tower-http = { version = "0.5.0", features = ["trace", "cors", "catch-panic", "sensitive-headers"] }
 tower = { version = "0.4.12", features = ["tracing"] }
-webauthn = { version = "0.4.5", package = "webauthn-rs", features = ["danger-allow-state-serialisation"], optional = true }
-base64 = "0.21.2"
+webauthn = { version = "0.5.0", package = "webauthn-rs", features = ["danger-allow-state-serialisation"], optional = true }
+base64 = "0.22.1"
+html5ever = "0.27.0"
 [dependencies.tokio]
 version = "^1.29.1"
 features = ["full", "tracing"] # TODO determine if my app doesn't need some features
@@ -121,16 +121,16 @@ features = ["io-util"]
 version = "^1.0.42"
 optional = true
 [dependencies.axum]
-version = "^0.6.18"
-features = ["multipart", "json", "headers", "form", "macros"]
+version = "^0.7.5"
+features = ["multipart", "json", "form", "macros"]
 [dependencies.axum-extra]
-version = "^0.7.4"
-features = ["cookie", "cookie-signed"]
+version = "^0.9.3"
+features = ["cookie", "cookie-signed", "typed-header"]
 [dependencies.chrono]        # Date and time library for Rust
 version = "^0.4.19"
 features = ["serde"]
 [dependencies.redis]
-version = "^0.21.3"
+version = "^0.26.0"
 optional = true
 features = ["aio", "tokio-comp"]
 [dependencies.prometheus]    # Prometheus instrumentation library for Rust applications
@@ -143,14 +143,14 @@ features = ["derive"]
 version = "^2.2.1"
 features = ["serde"]
 [dependencies.hyper]
-version = "^0.14.17"
-features = ["stream", "runtime"]
+version = "^1.4.1"
+features = []
 [dependencies.reqwest]
-version = "^0.11.10"
+version = "^0.12.5"
 default-features = false
 features = ["gzip", "brotli", "json", "stream"]
 [dependencies.microformats]
-version = "^0.3.0"
+version = "^0.9.1"
 #git = "https://gitlab.com/maxburon/microformats-parser"
 
 [dependencies.clap]
@@ -160,7 +160,7 @@ optional = true
 [dependencies.thiserror]
 version = "1.0.35"
 [dependencies.sqlx]
-version = "^0.7"
+version = "^0.8"
 features = ["uuid", "chrono", "json", "postgres", "runtime-tokio"]
 optional = true
 [dependencies.sqlparser]
diff --git a/indieauth/Cargo.toml b/indieauth/Cargo.toml
index 8d2dc90..2c30aed 100644
--- a/indieauth/Cargo.toml
+++ b/indieauth/Cargo.toml
@@ -21,11 +21,11 @@ features = ["serde"]
 version = "^1.0.170"
 features = ["derive"]
 [dependencies.axum-core]
-version = "^0.3.4"
+version = "^0.4.3"
 optional = true
 [dependencies.serde_json]
 version = "^1.0.64"
 optional = true
 [dependencies.http]
-version = "^0.2.7"
+version = "^1.0"
 optional = true
\ No newline at end of file
diff --git a/src/database/postgres/mod.rs b/src/database/postgres/mod.rs
index 0ebaffb..7f788a8 100644
--- a/src/database/postgres/mod.rs
+++ b/src/database/postgres/mod.rs
@@ -1,5 +1,4 @@
 use std::borrow::Cow;
-use std::str::FromStr;
 
 use kittybox_util::{MicropubChannel, MentionType};
 use sqlx::{ConnectOptions, Executor, PgPool};
@@ -30,6 +29,7 @@ impl From<sqlx::migrate::MigrateError> for StorageError {
     }
 }
 
+/// Micropub storage that uses a PostgreSQL database.
 #[derive(Debug, Clone)]
 pub struct PostgresStorage {
     db: PgPool
@@ -38,7 +38,7 @@ pub struct PostgresStorage {
 impl PostgresStorage {
     /// Construct a [`PostgresStorage`] from a [`sqlx::PgPool`],
     /// running appropriate migrations.
-    pub async fn from_pool(db: sqlx::PgPool) -> Result<Self> {
+    pub(crate) async fn from_pool(db: sqlx::PgPool) -> Result<Self> {
         db.execute(sqlx::query("CREATE SCHEMA IF NOT EXISTS kittybox")).await?;
         MIGRATOR.run(&db).await?;
         Ok(Self { db })
diff --git a/src/indieauth/mod.rs b/src/indieauth/mod.rs
index 2550df0..de4c367 100644
--- a/src/indieauth/mod.rs
+++ b/src/indieauth/mod.rs
@@ -1,13 +1,13 @@
 use std::marker::PhantomData;
-
 use microformats::types::Class;
 use tracing::error;
 use serde::Deserialize;
 use axum::{
-    extract::{Form, FromRef, Host, Json, Query, State}, headers::{authorization::Bearer, Authorization}, http::StatusCode, response::{Html, IntoResponse, Response}, Extension, TypedHeader
+    extract::{Form, FromRef, Host, Json, Query, State}, http::StatusCode, response::{Html, IntoResponse, Response}, Extension
 };
 #[cfg_attr(not(feature = "webauthn"), allow(unused_imports))]
 use axum_extra::extract::cookie::{CookieJar, Cookie};
+use axum_extra::{TypedHeader, headers::{authorization::Bearer, Authorization}};
 use crate::database::Storage;
 use kittybox_indieauth::{
     Metadata, IntrospectionEndpointAuthMethod, RevocationEndpointAuthMethod,
@@ -17,7 +17,6 @@ use kittybox_indieauth::{
     TokenIntrospectionRequest, TokenIntrospectionResponse, TokenRevocationRequest, TokenData
 };
 use std::str::FromStr;
-use std::ops::Deref;
 
 pub mod backend;
 #[cfg(feature = "webauthn")]
@@ -174,7 +173,7 @@ async fn authorization_endpoint_get<A: AuthBackend, D: Storage + 'static>(
 
                         mf2.items
                             .iter()
-                            .find(|&i| (**i).borrow().r#type.iter()
+                            .find(|&i| i.r#type.iter()
                                 .any(|i| {
                                     *i == Class::from_str("h-app").unwrap()
                                         || *i == Class::from_str("h-x-app").unwrap()
@@ -182,7 +181,7 @@ async fn authorization_endpoint_get<A: AuthBackend, D: Storage + 'static>(
                             )
                             .cloned()
                             .map(|i| {
-                                serde_json::to_value(i.borrow().deref()).unwrap()
+                                serde_json::to_value(&i).unwrap()
                             })
                     },
                     Err(err) => {
@@ -315,7 +314,7 @@ async fn authorization_endpoint_confirm<A: AuthBackend>(
     (StatusCode::NO_CONTENT,
      [("Location", location.as_str())],
      #[cfg(feature = "webauthn")]
-     cookies.remove(Cookie::named(webauthn::CHALLENGE_ID_COOKIE))
+     cookies.remove(Cookie::from(webauthn::CHALLENGE_ID_COOKIE))
     )
         .into_response()
 }
diff --git a/src/indieauth/webauthn.rs b/src/indieauth/webauthn.rs
index ea3ad3d..b7d8c71 100644
--- a/src/indieauth/webauthn.rs
+++ b/src/indieauth/webauthn.rs
@@ -1,9 +1,10 @@
 use axum::{
     extract::{Json, Host},
     response::{IntoResponse, Response},
-    http::StatusCode, Extension, TypedHeader, headers::{authorization::Bearer, Authorization}
+    http::StatusCode, Extension
 };
 use axum_extra::extract::cookie::{CookieJar, Cookie};
+use axum_extra::{TypedHeader, headers::{authorization::Bearer, Authorization}};
 
 use super::backend::AuthBackend;
 use crate::database::Storage;
@@ -66,7 +67,7 @@ pub async fn webauthn_pre_register<A: AuthBackend, D: Storage + 'static>(
     match auth.persist_registration_challenge(&uid_url, state).await {
         Ok(challenge_id) => (
             cookies.add(
-                Cookie::build(CHALLENGE_ID_COOKIE, challenge_id)
+                Cookie::build((CHALLENGE_ID_COOKIE, challenge_id))
                     .secure(true)
                     .finish()
             ),
diff --git a/src/lib.rs b/src/lib.rs
index 2d15423..495591d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,10 +7,10 @@ use axum::extract::FromRef;
 use axum_extra::extract::cookie::Key;
 use database::{FileStorage, PostgresStorage, Storage};
 use indieauth::backend::{AuthBackend, FileBackend as FileAuthBackend};
-use kittybox_util::queue::{JobItem, JobQueue};
+use kittybox_util::queue::JobQueue;
 use media::storage::{MediaStore, file::FileStore as FileMediaStore};
 use tokio::{sync::Mutex, task::JoinSet};
-use webmentions::queue::{PostgresJobItem, PostgresJobQueue};
+use webmentions::queue::PostgresJobQueue;
 
 /// Database abstraction layer for Kittybox, allowing the CMS to work with any kind of database.
 pub mod database;
diff --git a/src/main.rs b/src/main.rs
index 4af8a81..f683c38 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,7 @@
 use base64::Engine;
 use kittybox::{database::Storage, indieauth::backend::AuthBackend, media::storage::MediaStore, webmentions::Webmention, compose_kittybox};
 use tokio::{sync::Mutex, task::JoinSet};
-use std::{env, time::Duration, sync::Arc};
+use std::{env, future::IntoFuture, sync::Arc};
 use tracing::error;
 
 
@@ -208,7 +208,7 @@ async fn main() {
         }
     };
 
-    let mut servers: Vec<hyper::server::Server<hyper::server::conn::AddrIncoming, _>> = vec![];
+    let mut servers: Vec<axum::serve::Serve<_, _>> = vec![];
 
     let build_hyper = |tcp: std::net::TcpListener| {
         tracing::info!("Listening on {}", tcp.local_addr().unwrap());
@@ -216,10 +216,14 @@ async fn main() {
         // properly -- this is the async magic!
         tcp.set_nonblocking(true).unwrap();
 
-        hyper::server::Server::from_tcp(tcp).unwrap()
-            // Otherwise Chrome keeps connections open for too long
-            .tcp_keepalive(Some(Duration::from_secs(30 * 60)))
-            .serve(router.clone().into_make_service())
+        //hyper::server::Server::from_tcp(tcp).unwrap()
+        //    // Otherwise Chrome keeps connections open for too long
+        //    .tcp_keepalive(Some(Duration::from_secs(30 * 60)))
+        //    .serve(router.clone().into_make_service())
+        axum::serve(
+            tokio::net::TcpListener::from_std(tcp).unwrap(),
+            router.clone()
+        )
     };
 
     let mut listenfd = listenfd::ListenFd::from_env();
@@ -286,19 +290,20 @@ async fn main() {
         .map(
             #[cfg(not(tokio_unstable))] |server| tokio::task::spawn(
                 server.with_graceful_shutdown(cancellation_token.clone().cancelled_owned())
+                    .into_future()
             ),
             #[cfg(tokio_unstable)] |server| {
                 tokio::task::Builder::new()
-                    .name(format!("Kittybox HTTP acceptor: {}", server.local_addr()).as_str())
+                    .name(format!("Kittybox HTTP acceptor: {:?}", server).as_str())
                     .spawn(
                         server.with_graceful_shutdown(
                             cancellation_token.clone().cancelled_owned()
-                        )
+                        ).into_future()
                     )
                     .unwrap()
             }
         )
-        .collect::<futures_util::stream::FuturesUnordered<tokio::task::JoinHandle<Result<(), hyper::Error>>>>()
+        .collect::<futures_util::stream::FuturesUnordered<tokio::task::JoinHandle<Result<(), std::io::Error>>>>()
     );
 
     #[cfg(not(unix))]
diff --git a/src/media/mod.rs b/src/media/mod.rs
index 47f456a..7884ef8 100644
--- a/src/media/mod.rs
+++ b/src/media/mod.rs
@@ -1,6 +1,8 @@
 use axum::{
-    extract::{multipart::Multipart, FromRef, Host, Path, State}, headers::{HeaderMapExt, HeaderValue, IfNoneMatch}, response::{IntoResponse, Response}, TypedHeader
+    extract::{multipart::Multipart, FromRef, Host, Path, State}, response::{IntoResponse, Response}
 };
+use axum_extra::headers::{HeaderMapExt, HeaderValue, IfNoneMatch};
+use axum_extra::TypedHeader;
 use kittybox_util::error::{MicropubError, ErrorType};
 use kittybox_indieauth::Scope;
 use crate::indieauth::{backend::AuthBackend, User};
@@ -74,7 +76,7 @@ pub(crate) async fn serve<S: MediaStore>(
             tracing::debug!("Metadata: {:?}", metadata);
 
             let etag = if let Some(etag) = metadata.etag {
-                let etag = format!("\"{}\"", etag).parse::<axum::headers::ETag>().unwrap();
+                let etag = format!("\"{}\"", etag).parse::<axum_extra::headers::ETag>().unwrap();
 
                 if let Some(TypedHeader(if_none_match)) = if_none_match {
                     tracing::debug!("If-None-Match: {:?}", if_none_match);
@@ -110,7 +112,7 @@ pub(crate) async fn serve<S: MediaStore>(
                     headers.typed_insert(etag);
                 }
             }
-            r.body(axum::body::StreamBody::new(stream))
+            r.body(axum::body::Body::from_stream(stream))
                 .unwrap()
                 .into_response()
         },
diff --git a/src/micropub/mod.rs b/src/micropub/mod.rs
index fc5dd10..63b81c5 100644
--- a/src/micropub/mod.rs
+++ b/src/micropub/mod.rs
@@ -5,12 +5,12 @@ use std::sync::Arc;
 use crate::database::{MicropubChannel, Storage, StorageError};
 use crate::indieauth::backend::AuthBackend;
 use crate::indieauth::User;
-use crate::media::storage::MediaStore;
 use crate::micropub::util::form_to_mf2_json;
-use axum::extract::{BodyStream, FromRef, Host, Query, State};
-use axum::headers::ContentType;
+use axum::extract::{FromRef, Host, Query, State};
+use axum::body::Body as BodyStream;
+use axum_extra::headers::ContentType;
 use axum::response::{IntoResponse, Response};
-use axum::TypedHeader;
+use axum_extra::TypedHeader;
 use axum::http::StatusCode;
 use serde::{Deserialize, Serialize};
 use serde_json::json;
@@ -136,10 +136,10 @@ async fn background_processing<D: 'static + Storage>(
                 // TODO parse link headers
                 let links = response
                     .headers()
-                    .get_all(hyper::http::header::LINK)
+                    .get_all(reqwest::header::LINK)
                     .iter()
                     .cloned()
-                    .collect::<Vec<hyper::http::HeaderValue>>();
+                    .collect::<Vec<reqwest::header::HeaderValue>>();
                 let html = response.text().await;
                 if html.is_err() {
                     return None;
@@ -330,9 +330,9 @@ pub(crate) async fn _post<D: 'static + Storage>(
         IntoResponse::into_response((StatusCode::ACCEPTED, [("Location", uid.as_str())]));
 
     #[cfg(not(tokio_unstable))]
-    jobset.lock().await.spawn(background_processing(db, mf2, http));
+    let _ = jobset.lock().await.spawn(background_processing(db, mf2, http));
     #[cfg(tokio_unstable)]
-    jobset.lock().await.build_task()
+    let _ = jobset.lock().await.build_task()
         .name(format!("Kittybox background processing for post {}", uid.as_str()).as_str())
         .spawn(background_processing(db, mf2, http));
 
@@ -459,7 +459,7 @@ enum PostBody {
 
 #[tracing::instrument]
 async fn dispatch_body(
-    mut body: BodyStream,
+    body: BodyStream,
     content_type: ContentType,
 ) -> Result<PostBody, MicropubError> {
     let body: Vec<u8> = {
@@ -467,6 +467,7 @@ async fn dispatch_body(
         use tokio_stream::StreamExt;
         let mut buf = Vec::default();
 
+        let mut body = body.into_data_stream();
         while let Some(chunk) = body.next().await {
             buf.extend_from_slice(&chunk.unwrap())
         }
@@ -673,7 +674,7 @@ where
 {
     axum::routing::get(query::<S, A>)
         .post(post::<S, A>)
-        .layer::<_, _, std::convert::Infallible>(tower_http::cors::CorsLayer::new()
+        .layer::<_, _>(tower_http::cors::CorsLayer::new()
                .allow_methods([
                    axum::http::Method::GET,
                    axum::http::Method::POST,
@@ -704,7 +705,8 @@ mod tests {
     use std::sync::Arc;
 
     use crate::{database::Storage, micropub::MicropubError};
-    use hyper::body::HttpBody;
+    use bytes::Bytes;
+    use futures::StreamExt;
     use serde_json::json;
     use tokio::sync::Mutex;
 
@@ -861,7 +863,15 @@ mod tests {
         .await;
 
         assert_eq!(res.status(), 401);
-        let body = res.body_mut().data().await.unwrap().unwrap();
+        let body = res
+            .into_body()
+            .into_data_stream()
+            .collect::<Vec<Result<Bytes, axum::Error>>>()
+            .await
+            .into_iter()
+            .map(Result::unwrap)
+            .by_ref()
+            .fold(Vec::new(), |mut a, i| { a.extend(i); a});
         let json: MicropubError = serde_json::from_slice(&body as &[u8]).unwrap();
         assert_eq!(json.error, super::ErrorType::NotAuthorized);
     }
diff --git a/src/webmentions/check.rs b/src/webmentions/check.rs
index 6dc6a25..178c008 100644
--- a/src/webmentions/check.rs
+++ b/src/webmentions/check.rs
@@ -1,7 +1,11 @@
-use std::{cell::RefCell, rc::Rc};
-use microformats::{types::PropertyValue, html5ever::{self, tendril::TendrilSink}};
+use std::rc::Rc;
+use microformats::types::PropertyValue;
+use html5ever::{self, tendril::TendrilSink};
 use kittybox_util::MentionType;
 
+// TODO: replace.
+mod rcdom;
+
 #[derive(thiserror::Error, Debug)]
 pub enum Error {
     #[error("microformats error: {0}")]
@@ -19,19 +23,16 @@ pub fn check_mention(document: impl AsRef<str> + std::fmt::Debug, base_url: &url
     let document = microformats::from_html(document.as_ref(), base_url.clone())?;
 
     // Get an iterator of all items
-    let items_iter = document.items.iter()
-        .map(AsRef::as_ref)
-        .map(RefCell::borrow);
+    let items_iter = document.items.iter();
 
     for item in items_iter {
         tracing::debug!("Processing item: {:?}", item);
 
-        let props = item.properties.borrow();
         for (prop, interaction_type) in [
             ("in-reply-to", MentionType::Reply), ("like-of", MentionType::Like),
             ("bookmark-of", MentionType::Bookmark), ("repost-of", MentionType::Repost)
         ] {
-            if let Some(propvals) = props.get(prop) {
+            if let Some(propvals) = item.properties.get(prop) {
                 tracing::debug!("Has a u-{} property", prop);
                 for val in propvals {
                     if let PropertyValue::Url(url) = val {
@@ -45,13 +46,13 @@ pub fn check_mention(document: impl AsRef<str> + std::fmt::Debug, base_url: &url
         }
         // Process `content`
         tracing::debug!("Processing e-content...");
-        if let Some(PropertyValue::Fragment(content)) = props.get("content")
+        if let Some(PropertyValue::Fragment(content)) = item.properties.get("content")
             .map(Vec::as_slice)
             .unwrap_or_default()
             .first()
         {
             tracing::debug!("Parsing HTML data...");
-            let root = html5ever::parse_document(html5ever::rcdom::RcDom::default(), Default::default())
+            let root = html5ever::parse_document(rcdom::RcDom::default(), Default::default())
                 .from_utf8()
                 .one(content.html.to_owned().as_bytes())
                 .document;
@@ -64,7 +65,7 @@ pub fn check_mention(document: impl AsRef<str> + std::fmt::Debug, base_url: &url
             // iteration of the loop.
             //
             // Empty list means all nodes were processed.
-            let mut unprocessed_nodes: Vec<Rc<html5ever::rcdom::Node>> = root.children.borrow().iter().cloned().collect();
+            let mut unprocessed_nodes: Vec<Rc<rcdom::Node>> = root.children.borrow().iter().cloned().collect();
             while !unprocessed_nodes.is_empty() {
                 // "Take" the list out of its memory slot, replace it with an empty list
                 let nodes = std::mem::take(&mut unprocessed_nodes);
@@ -73,7 +74,7 @@ pub fn check_mention(document: impl AsRef<str> + std::fmt::Debug, base_url: &url
                     // Add children nodes to the list for the next iteration
                     unprocessed_nodes.extend(node.children.borrow().iter().cloned());
 
-                    if let html5ever::rcdom::NodeData::Element { ref name, ref attrs, .. } = node.data {
+                    if let rcdom::NodeData::Element { ref name, ref attrs, .. } = node.data {
                         // If it's not `<a>`, skip it
                         if name.local != *"a" { continue; }
                         let mut is_mention: bool = false;
diff --git a/src/webmentions/check/rcdom.rs b/src/webmentions/check/rcdom.rs
new file mode 100644
index 0000000..549610f
--- /dev/null
+++ b/src/webmentions/check/rcdom.rs
@@ -0,0 +1,515 @@
+// Copyright 2014-2017 The html5ever Project Developers.
+// Copyright Michael Howell and others.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![allow(missing_docs)]
+
+//! A simple reference-counted DOM.
+//!
+//! This is sufficient as a static parse tree, but don't build a
+//! web browser using it. :)
+//!
+//! A DOM is a [tree structure] with ordered children that can be represented in an XML-like
+//! format. For example, the following graph
+//!
+//! ```text
+//! div
+//!  +- "text node"
+//!  +- span
+//! ```
+//! in HTML would be serialized as
+//!
+//! ```html
+//! <div>text node<span></span></div>
+//! ```
+//!
+//! See the [document object model article on wikipedia][dom wiki] for more information.
+//!
+//! This implementation stores the information associated with each node once, and then hands out
+//! refs to children. The nodes themselves are reference-counted to avoid copying - you can create
+//! a new ref and then a node will outlive the document. Nodes own their children, but only have
+//! weak references to their parents.
+//!
+//! [tree structure]: https://en.wikipedia.org/wiki/Tree_(data_structure)
+//! [dom wiki]: https://en.wikipedia.org/wiki/Document_Object_Model
+
+use std::borrow::Cow;
+use std::cell::{Cell, RefCell};
+use std::collections::{HashSet, VecDeque};
+use std::default::Default;
+use std::fmt;
+use std::io;
+use std::mem;
+use std::rc::{Rc, Weak};
+
+use html5ever::tendril::StrTendril;
+
+use html5ever::interface::tree_builder;
+use html5ever::interface::tree_builder::{ElementFlags, NodeOrText, QuirksMode, TreeSink};
+use html5ever::serialize::TraversalScope;
+use html5ever::serialize::TraversalScope::{ChildrenOnly, IncludeNode};
+use html5ever::serialize::{Serialize, Serializer};
+use html5ever::Attribute;
+use html5ever::ExpandedName;
+use html5ever::QualName;
+
+/// The different kinds of nodes in the DOM.
+#[derive(Debug)]
+pub enum NodeData {
+    /// The `Document` itself - the root node of a HTML document.
+    Document,
+
+    /// A `DOCTYPE` with name, public id, and system id. See
+    /// [document type declaration on wikipedia][dtd wiki].
+    ///
+    /// [dtd wiki]: https://en.wikipedia.org/wiki/Document_type_declaration
+    Doctype {
+        name: StrTendril,
+        public_id: StrTendril,
+        system_id: StrTendril,
+    },
+
+    /// A text node.
+    Text { contents: RefCell<StrTendril> },
+
+    /// A comment.
+    Comment { contents: StrTendril },
+
+    /// An element with attributes.
+    Element {
+        name: QualName,
+        attrs: RefCell<Vec<Attribute>>,
+
+        /// For HTML \<template\> elements, the [template contents].
+        ///
+        /// [template contents]: https://html.spec.whatwg.org/multipage/#template-contents
+        template_contents: RefCell<Option<Handle>>,
+
+        /// Whether the node is a [HTML integration point].
+        ///
+        /// [HTML integration point]: https://html.spec.whatwg.org/multipage/#html-integration-point
+        mathml_annotation_xml_integration_point: bool,
+    },
+
+    /// A Processing instruction.
+    ProcessingInstruction {
+        target: StrTendril,
+        contents: StrTendril,
+    },
+}
+
+/// A DOM node.
+pub struct Node {
+    /// Parent node.
+    pub parent: Cell<Option<WeakHandle>>,
+    /// Child nodes of this node.
+    pub children: RefCell<Vec<Handle>>,
+    /// Represents this node's data.
+    pub data: NodeData,
+}
+
+impl Node {
+    /// Create a new node from its contents
+    pub fn new(data: NodeData) -> Rc<Self> {
+        Rc::new(Node {
+            data,
+            parent: Cell::new(None),
+            children: RefCell::new(Vec::new()),
+        })
+    }
+}
+
+impl Drop for Node {
+    fn drop(&mut self) {
+        let mut nodes = mem::take(&mut *self.children.borrow_mut());
+        while let Some(node) = nodes.pop() {
+            let children = mem::take(&mut *node.children.borrow_mut());
+            nodes.extend(children.into_iter());
+            if let NodeData::Element {
+                ref template_contents,
+                ..
+            } = node.data
+            {
+                if let Some(template_contents) = template_contents.borrow_mut().take() {
+                    nodes.push(template_contents);
+                }
+            }
+        }
+    }
+}
+
+impl fmt::Debug for Node {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+        fmt.debug_struct("Node")
+            .field("data", &self.data)
+            .field("children", &self.children)
+            .finish()
+    }
+}
+
+/// Reference to a DOM node.
+pub type Handle = Rc<Node>;
+
+/// Weak reference to a DOM node, used for parent pointers.
+pub type WeakHandle = Weak<Node>;
+
+/// Append a parentless node to another nodes' children
+fn append(new_parent: &Handle, child: Handle) {
+    let previous_parent = child.parent.replace(Some(Rc::downgrade(new_parent)));
+    // Invariant: child cannot have existing parent
+    assert!(previous_parent.is_none());
+    new_parent.children.borrow_mut().push(child);
+}
+
+/// If the node has a parent, get it and this node's position in its children
+fn get_parent_and_index(target: &Handle) -> Option<(Handle, usize)> {
+    if let Some(weak) = target.parent.take() {
+        let parent = weak.upgrade().expect("dangling weak pointer");
+        target.parent.set(Some(weak));
+        let i = match parent
+            .children
+            .borrow()
+            .iter()
+            .enumerate()
+            .find(|&(_, child)| Rc::ptr_eq(child, target))
+        {
+            Some((i, _)) => i,
+            None => panic!("have parent but couldn't find in parent's children!"),
+        };
+        Some((parent, i))
+    } else {
+        None
+    }
+}
+
+fn append_to_existing_text(prev: &Handle, text: &str) -> bool {
+    match prev.data {
+        NodeData::Text { ref contents } => {
+            contents.borrow_mut().push_slice(text);
+            true
+        }
+        _ => false,
+    }
+}
+
+fn remove_from_parent(target: &Handle) {
+    if let Some((parent, i)) = get_parent_and_index(target) {
+        parent.children.borrow_mut().remove(i);
+        target.parent.set(None);
+    }
+}
+
+/// The DOM itself; the result of parsing.
+pub struct RcDom {
+    /// The `Document` itself.
+    pub document: Handle,
+
+    /// Errors that occurred during parsing.
+    pub errors: Vec<Cow<'static, str>>,
+
+    /// The document's quirks mode.
+    pub quirks_mode: QuirksMode,
+}
+
+impl TreeSink for RcDom {
+    type Output = Self;
+    fn finish(self) -> Self {
+        self
+    }
+
+    type Handle = Handle;
+
+    fn parse_error(&mut self, msg: Cow<'static, str>) {
+        self.errors.push(msg);
+    }
+
+    fn get_document(&mut self) -> Handle {
+        self.document.clone()
+    }
+
+    fn get_template_contents(&mut self, target: &Handle) -> Handle {
+        if let NodeData::Element {
+            ref template_contents,
+            ..
+        } = target.data
+        {
+            template_contents
+                .borrow()
+                .as_ref()
+                .expect("not a template element!")
+                .clone()
+        } else {
+            panic!("not a template element!")
+        }
+    }
+
+    fn set_quirks_mode(&mut self, mode: QuirksMode) {
+        self.quirks_mode = mode;
+    }
+
+    fn same_node(&self, x: &Handle, y: &Handle) -> bool {
+        Rc::ptr_eq(x, y)
+    }
+
+    fn elem_name<'a>(&self, target: &'a Handle) -> ExpandedName<'a> {
+        return match target.data {
+            NodeData::Element { ref name, .. } => name.expanded(),
+            _ => panic!("not an element!"),
+        };
+    }
+
+    fn create_element(
+        &mut self,
+        name: QualName,
+        attrs: Vec<Attribute>,
+        flags: ElementFlags,
+    ) -> Handle {
+        Node::new(NodeData::Element {
+            name,
+            attrs: RefCell::new(attrs),
+            template_contents: RefCell::new(if flags.template {
+                Some(Node::new(NodeData::Document))
+            } else {
+                None
+            }),
+            mathml_annotation_xml_integration_point: flags.mathml_annotation_xml_integration_point,
+        })
+    }
+
+    fn create_comment(&mut self, text: StrTendril) -> Handle {
+        Node::new(NodeData::Comment { contents: text })
+    }
+
+    fn create_pi(&mut self, target: StrTendril, data: StrTendril) -> Handle {
+        Node::new(NodeData::ProcessingInstruction {
+            target,
+            contents: data,
+        })
+    }
+
+    fn append(&mut self, parent: &Handle, child: NodeOrText<Handle>) {
+        // Append to an existing Text node if we have one.
+        if let NodeOrText::AppendText(ref text) = child {
+            if let Some(h) = parent.children.borrow().last() {
+                if append_to_existing_text(h, text) {
+                    return;
+                }
+            }
+        }
+
+        append(
+            parent,
+            match child {
+                NodeOrText::AppendText(text) => Node::new(NodeData::Text {
+                    contents: RefCell::new(text),
+                }),
+                NodeOrText::AppendNode(node) => node,
+            },
+        );
+    }
+
+    fn append_before_sibling(&mut self, sibling: &Handle, child: NodeOrText<Handle>) {
+        let (parent, i) = get_parent_and_index(sibling)
+            .expect("append_before_sibling called on node without parent");
+
+        let child = match (child, i) {
+            // No previous node.
+            (NodeOrText::AppendText(text), 0) => Node::new(NodeData::Text {
+                contents: RefCell::new(text),
+            }),
+
+            // Look for a text node before the insertion point.
+            (NodeOrText::AppendText(text), i) => {
+                let children = parent.children.borrow();
+                let prev = &children[i - 1];
+                if append_to_existing_text(prev, &text) {
+                    return;
+                }
+                Node::new(NodeData::Text {
+                    contents: RefCell::new(text),
+                })
+            }
+
+            // The tree builder promises we won't have a text node after
+            // the insertion point.
+
+            // Any other kind of node.
+            (NodeOrText::AppendNode(node), _) => node,
+        };
+
+        remove_from_parent(&child);
+
+        child.parent.set(Some(Rc::downgrade(&parent)));
+        parent.children.borrow_mut().insert(i, child);
+    }
+
+    fn append_based_on_parent_node(
+        &mut self,
+        element: &Self::Handle,
+        prev_element: &Self::Handle,
+        child: NodeOrText<Self::Handle>,
+    ) {
+        let parent = element.parent.take();
+        let has_parent = parent.is_some();
+        element.parent.set(parent);
+
+        if has_parent {
+            self.append_before_sibling(element, child);
+        } else {
+            self.append(prev_element, child);
+        }
+    }
+
+    fn append_doctype_to_document(
+        &mut self,
+        name: StrTendril,
+        public_id: StrTendril,
+        system_id: StrTendril,
+    ) {
+        append(
+            &self.document,
+            Node::new(NodeData::Doctype {
+                name,
+                public_id,
+                system_id,
+            }),
+        );
+    }
+
+    fn add_attrs_if_missing(&mut self, target: &Handle, attrs: Vec<Attribute>) {
+        let mut existing = if let NodeData::Element { ref attrs, .. } = target.data {
+            attrs.borrow_mut()
+        } else {
+            panic!("not an element")
+        };
+
+        let existing_names = existing
+            .iter()
+            .map(|e| e.name.clone())
+            .collect::<HashSet<_>>();
+        existing.extend(
+            attrs
+                .into_iter()
+                .filter(|attr| !existing_names.contains(&attr.name)),
+        );
+    }
+
+    fn remove_from_parent(&mut self, target: &Handle) {
+        remove_from_parent(target);
+    }
+
+    fn reparent_children(&mut self, node: &Handle, new_parent: &Handle) {
+        let mut children = node.children.borrow_mut();
+        let mut new_children = new_parent.children.borrow_mut();
+        for child in children.iter() {
+            let previous_parent = child.parent.replace(Some(Rc::downgrade(new_parent)));
+            assert!(Rc::ptr_eq(
+                node,
+                &previous_parent.unwrap().upgrade().expect("dangling weak")
+            ))
+        }
+        new_children.extend(mem::take(&mut *children));
+    }
+
+    fn is_mathml_annotation_xml_integration_point(&self, target: &Handle) -> bool {
+        if let NodeData::Element {
+            mathml_annotation_xml_integration_point,
+            ..
+        } = target.data
+        {
+            mathml_annotation_xml_integration_point
+        } else {
+            panic!("not an element!")
+        }
+    }
+}
+
+impl Default for RcDom {
+    fn default() -> RcDom {
+        RcDom {
+            document: Node::new(NodeData::Document),
+            errors: vec![],
+            quirks_mode: tree_builder::NoQuirks,
+        }
+    }
+}
+
+enum SerializeOp {
+    Open(Handle),
+    Close(QualName),
+}
+
+pub struct SerializableHandle(Handle);
+
+impl From<Handle> for SerializableHandle {
+    fn from(h: Handle) -> SerializableHandle {
+        SerializableHandle(h)
+    }
+}
+
+impl Serialize for SerializableHandle {
+    fn serialize<S>(&self, serializer: &mut S, traversal_scope: TraversalScope) -> io::Result<()>
+    where
+        S: Serializer,
+    {
+        let mut ops = VecDeque::new();
+        match traversal_scope {
+            IncludeNode => ops.push_back(SerializeOp::Open(self.0.clone())),
+            ChildrenOnly(_) => ops.extend(
+                self.0
+                    .children
+                    .borrow()
+                    .iter()
+                    .map(|h| SerializeOp::Open(h.clone())),
+            ),
+        }
+
+        while let Some(op) = ops.pop_front() {
+            match op {
+                SerializeOp::Open(handle) => match handle.data {
+                    NodeData::Element {
+                        ref name,
+                        ref attrs,
+                        ..
+                    } => {
+                        serializer.start_elem(
+                            name.clone(),
+                            attrs.borrow().iter().map(|at| (&at.name, &at.value[..])),
+                        )?;
+
+                        ops.reserve(1 + handle.children.borrow().len());
+                        ops.push_front(SerializeOp::Close(name.clone()));
+
+                        for child in handle.children.borrow().iter().rev() {
+                            ops.push_front(SerializeOp::Open(child.clone()));
+                        }
+                    }
+
+                    NodeData::Doctype { ref name, .. } => serializer.write_doctype(name)?,
+
+                    NodeData::Text { ref contents } => serializer.write_text(&contents.borrow())?,
+
+                    NodeData::Comment { ref contents } => serializer.write_comment(contents)?,
+
+                    NodeData::ProcessingInstruction {
+                        ref target,
+                        ref contents,
+                    } => serializer.write_processing_instruction(target, contents)?,
+
+                    NodeData::Document => panic!("Can't serialize Document node itself"),
+                },
+
+                SerializeOp::Close(name) => {
+                    serializer.end_elem(name)?;
+                }
+            }
+        }
+
+        Ok(())
+    }
+}
diff --git a/templates-neo/Cargo.toml b/templates-neo/Cargo.toml
index 98e70a7..ed88873 100644
--- a/templates-neo/Cargo.toml
+++ b/templates-neo/Cargo.toml
@@ -15,16 +15,19 @@ rand = "^0.8.5"
 
 [dependencies]
 ellipse = "^0.2.0"
-http = "^0.2.7"
+http = "^1.0"
 html = "^0.6.0"
 serde_json = "^1.0.64"
 include_dir = "^0.7.2"
-axum = "^0.6.18"
+axum = "^0.7.5"
 thiserror = "1.0.43"
 [dependencies.url]
 # URL library for Rust, based on the WHATWG URL Standard
 version = "^2.2.1"
 features = ["serde"]
+[dependencies.time]
+version = "^0.3.34"
+features = ["formatting"]
 [dependencies.chrono]
 version = "^0.4.19"
 features = ["serde"]
@@ -35,4 +38,4 @@ path = "../util"
 version = "0.2.0"
 path = "../indieauth"
 [dependencies.microformats]
-version="^0.3.0"
\ No newline at end of file
+version="^0.9.1"
\ No newline at end of file
diff --git a/templates-neo/src/mf2.rs b/templates-neo/src/mf2.rs
index 8190720..3cf453f 100644
--- a/templates-neo/src/mf2.rs
+++ b/templates-neo/src/mf2.rs
@@ -64,7 +64,7 @@ impl TryFrom<Item> for Card {
             });
         }
 
-        let mut props = card.properties.take();
+        let mut props = card.properties;
         let uid = {
             let uids = props.remove("uid").ok_or(Error::NoUid)?;
             if let Some(PropertyValue::Url(uid)) = uids.into_iter().take(1).next() {
@@ -106,12 +106,12 @@ impl TryFrom<Item> for Card {
                 .unwrap_or_default()
                 .into_iter()
                 .next()
-                .and_then(|v| match v {
-                    PropertyValue::Plain(plain) => Some(Ok(plain)),
-                    other => Some(Err(Error::WrongValueType {
+                .map(|v| match v {
+                    PropertyValue::Plain(plain) => Ok(plain),
+                    other => Err(Error::WrongValueType {
                         expected: "string",
                         got: other,
-                    })),
+                    }),
                 })
                 .transpose()?,
             photo: props
@@ -122,10 +122,13 @@ impl TryFrom<Item> for Card {
                 .ok_or(Error::MissingProperty("photo"))
                 .and_then(|v| match v {
                     PropertyValue::Url(url) => Ok(Image::Plain(url)),
-                    PropertyValue::Image(image) => Ok(Image::Accessible {
-                        src: image.src,
-                        alt: image.alt,
-                    }),
+                    PropertyValue::Image(image) => match image.alt {
+                        Some(alt) => Ok(Image::Accessible {
+                            src: image.value,
+                            alt,
+                        }),
+                        None => Ok(Image::Plain(image.value))
+                    },
                     other => Err(Error::WrongValueType {
                         expected: "string",
                         got: other,
@@ -198,7 +201,7 @@ impl Card {
                 for url in urls {
                     let url = String::from(url);
                     ul.list_item(move |li| {
-                        li.push({ Anchor::builder().href(url.clone()).text(url).build() })
+                        li.push(Anchor::builder().href(url.clone()).text(url).build())
                     });
                 }
 
@@ -215,7 +218,7 @@ impl TryFrom<PropertyValue> for Card {
 
     fn try_from(v: PropertyValue) -> Result<Self, Self::Error> {
         match v {
-            PropertyValue::Item(item) => item.take().try_into(),
+            PropertyValue::Item(item) => item.try_into(),
             other => Err(Error::WrongValueType {
                 expected: "h-card",
                 got: other,
@@ -229,7 +232,7 @@ pub struct Cite {
     url: Vec<url::Url>,
     in_reply_to: Option<Vec<Citation>>,
     author: Card,
-    published: Option<chrono::DateTime<chrono::FixedOffset>>,
+    published: Option<time::OffsetDateTime>,
     content: Content,
 }
 
@@ -258,7 +261,7 @@ impl TryFrom<PropertyValue> for Citation {
     fn try_from(v: PropertyValue) -> Result<Self, Self::Error> {
         match v {
             PropertyValue::Url(url) => Ok(Self::Brief(url)),
-            PropertyValue::Item(item) => Ok(Self::Full(item.take().try_into()?)),
+            PropertyValue::Item(item) => Ok(Self::Full(item.try_into()?)),
             other => Err(Error::WrongValueType {
                 expected: "url or h-cite",
                 got: other,
@@ -287,7 +290,7 @@ pub struct Entry {
     author: Card,
     category: Vec<String>,
     syndication: Vec<url::Url>,
-    published: chrono::DateTime<chrono::FixedOffset>,
+    published: time::OffsetDateTime,
     content: Content,
 }
 
@@ -301,7 +304,7 @@ impl TryFrom<Item> for Entry {
             });
         }
 
-        let mut props = entry.properties.take();
+        let mut props = entry.properties;
         let uid = {
             let uids = props.remove("uid").ok_or(Error::NoUid)?;
             if let Some(PropertyValue::Url(uid)) = uids.into_iter().take(1).next() {
@@ -369,23 +372,26 @@ impl TryFrom<Item> for Entry {
                 .into_iter()
                 .next()
                 .map(
-                    |v| -> Result<chrono::DateTime<chrono::FixedOffset>, Error> {
+                    |v| -> Result<time::OffsetDateTime, Error> {
                         match v {
-                            PropertyValue::Temporal(Temporal::Timestamp(dt)) => {
+                            PropertyValue::Temporal(Temporal::Timestamp(ref dt)) => {
                                 // This is incredibly sketchy.
                                 let (date, time, offset) = (
-                                    dt.date.clone().unwrap().data,
-                                    dt.as_time().unwrap().data.clone(),
-                                    dt.as_time().unwrap().offset.unwrap().data,
+                                    dt.date.to_owned().ok_or_else(|| Error::WrongValueType {
+                                        expected: "timestamp (date, time, offset)",
+                                        got: v.clone()
+                                    })?.data,
+                                    dt.time.to_owned().ok_or_else(|| Error::WrongValueType {
+                                        expected: "timestamp (date, time, offset)",
+                                        got: v.clone()
+                                    })?.data,
+                                    dt.offset.to_owned().ok_or_else(|| Error::WrongValueType {
+                                        expected: "timestamp (date, time, offset)",
+                                        got: v.clone()
+                                    })?.data,
                                 );
 
-                                date.and_time(time)
-                                    .and_local_timezone(offset)
-                                    .single()
-                                    .ok_or_else(|| Error::WrongValueType {
-                                        expected: "datetime with timezone",
-                                        got: PropertyValue::Temporal(Temporal::Timestamp(dt)),
-                                    })
+                                Ok(date.with_time(time).assume_offset(offset))
                             }
                             other => Err(Error::WrongValueType {
                                 expected: "timestamp",
@@ -428,13 +434,10 @@ impl Entry {
                                         html::inline_text::Time::builder()
                                             .text(
                                                 self.published
-                                                    .format("%Y-%m-%d %a %H:%M:%S %z")
-                                                    .to_string(),
+                                                    .format(&time::format_description::well_known::Rfc2822)
+                                                    .unwrap()
                                             )
-                                            .date_time(self.published.to_rfc3339_opts(
-                                                chrono::SecondsFormat::Secs,
-                                                false,
-                                            ))
+                                            .date_time(self.published.format(&time::format_description::well_known::Rfc3339).unwrap())
                                             .build(),
                                     )
                                 })
diff --git a/templates/Cargo.toml b/templates/Cargo.toml
index 9be5f30..6209e76 100644
--- a/templates/Cargo.toml
+++ b/templates/Cargo.toml
@@ -12,16 +12,19 @@ walkdir = "^2.3.2"
 [dev-dependencies]
 faker_rand = "^0.1.1"
 rand = "^0.8.5"
+[dev-dependencies.time]
+version = "^0.3.34"
+features = ["parsing", "formatting"]
 [dev-dependencies.microformats]
-version="^0.3.0"
+version="^0.9.1"
 
 [dependencies]
 ellipse = "^0.2.0"
-http = "^0.2.7"
-markup = "^0.13.1"
+http = "^1.0"
+markup = "^0.15.0"
 serde_json = "^1.0.64"
 include_dir = "^0.7.2"
-axum = "^0.6.18"
+axum = "^0.7.5"
 [dependencies.chrono]
 version = "^0.4.19"
 features = ["serde"]
diff --git a/templates/src/lib.rs b/templates/src/lib.rs
index dd263e9..b7b9a24 100644
--- a/templates/src/lib.rs
+++ b/templates/src/lib.rs
@@ -56,9 +56,8 @@ mod tests {
     use faker_rand::en_us::internet::Domain;
     use faker_rand::lorem::Word;
     use microformats::types::{Document, Item, PropertyValue, Url};
+    use rand::distributions::Distribution;
     use serde_json::json;
-    use std::cell::RefCell;
-    use std::rc::Rc;
 
     enum PostType {
         Note,
@@ -99,7 +98,17 @@ mod tests {
             rand::random::<Word>(),
             rand::random::<Word>()
         );
-        let dt = chrono::offset::Local::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
+        let dt = time::OffsetDateTime::now_utc()
+            .to_offset(
+                time::UtcOffset::from_hms(
+                    rand::distributions::Uniform::new(-11, 12)
+                        .sample(&mut rand::thread_rng()),
+                    if rand::random::<bool>() { 0 } else { 30 },
+                    0
+                ).unwrap()
+            )
+            .format(&time::format_description::well_known::Rfc3339)
+            .unwrap();
 
         match kind {
             PostType::Note => {
@@ -189,46 +198,38 @@ mod tests {
         }
     }
 
-    fn check_dt_published(mf2: &serde_json::Value, item: &Rc<RefCell<Item>>) {
+    fn check_dt_published(mf2: &serde_json::Value, item: &Item) {
         use microformats::types::temporal::Value as TemporalValue;
 
-        let _item = item.borrow();
-        let props = _item.properties.borrow();
-        assert!(props.contains_key("published"));
+        assert!(item.properties.contains_key("published"));
 
         if let Some(PropertyValue::Temporal(TemporalValue::Timestamp(item))) =
-            props.get("published").and_then(|v| v.first())
+            item.properties.get("published").and_then(|v| v.first())
         {
-            use chrono::{DateTime, FixedOffset, NaiveDateTime};
-
             // Faithfully reconstruct the original datetime
             // I wonder why not just have an Enum that would
             // get you either date, time or a datetime,
             // potentially with an offset?
             let offset = item.as_offset().unwrap().data;
-            let ndt: NaiveDateTime = item.as_date().unwrap().data
-                .and_time(item.as_time().unwrap().data)
-            // subtract the offset here, since we will add it back
-                - offset;
-            let dt = DateTime::<FixedOffset>::from_utc(ndt, offset);
+            let date = item.as_date().unwrap().data;
+            let time =  item.as_time().unwrap().data;
 
-            let expected: DateTime<FixedOffset> = chrono::DateTime::parse_from_rfc3339(
+            let dt = date.with_time(time).assume_offset(offset);
+            let expected = time::OffsetDateTime::parse(
                 mf2["properties"]["published"][0].as_str().unwrap(),
-            )
-            .unwrap();
-
+                &time::format_description::well_known::Rfc3339
+            ).unwrap();
+            
             assert_eq!(dt, expected);
         } else {
             unreachable!()
         }
     }
 
-    fn check_e_content(mf2: &serde_json::Value, item: &Rc<RefCell<Item>>) {
-        let _item = item.borrow();
-        let props = _item.properties.borrow();
-        assert!(props.contains_key("content"));
+    fn check_e_content(mf2: &serde_json::Value, item: &Item) {
+        assert!(item.properties.contains_key("content"));
 
-        if let Some(PropertyValue::Fragment(content)) = props.get("content").and_then(|v| v.first())
+        if let Some(PropertyValue::Fragment(content)) = item.properties.get("content").and_then(|v| v.first())
         {
             assert_eq!(
                 content.html,
@@ -240,7 +241,6 @@ mod tests {
     }
 
     #[test]
-    #[ignore = "see https://gitlab.com/maxburon/microformats-parser/-/issues/7"]
     fn test_note() {
         let mf2 = gen_random_post(&rand::random::<Domain>().to_string(), PostType::Note);
 
@@ -253,9 +253,8 @@ mod tests {
             .unwrap();
         let parsed: Document = microformats::from_html(&html, url.clone()).unwrap();
 
-        if let Some(PropertyValue::Item(item)) = parsed.get_item_by_url(&url) {
-            let _item = item.borrow();
-            let props = _item.properties.borrow();
+        if let Some(item) = parsed.into_iter().find(|i| i.properties.get("url").unwrap().contains(&PropertyValue::Url(url.clone()))) {
+            let props = &item.properties;
 
             check_e_content(&mf2, &item);
             check_dt_published(&mf2, &item);
@@ -284,21 +283,20 @@ mod tests {
             .unwrap();
         let parsed: Document = microformats::from_html(&html, url.clone()).unwrap();
 
-        if let Some(PropertyValue::Item(item)) = parsed.get_item_by_url(&url) {
-            let _item = item.borrow();
-            let props = _item.properties.borrow();
+        if let Some(item) = parsed.into_iter().find(|i| i.properties.get("url").unwrap().contains(&PropertyValue::Url(url.clone()))) {
 
             check_e_content(&mf2, &item);
             check_dt_published(&mf2, &item);
-            assert!(props.contains_key("uid"));
-            assert!(props.contains_key("url"));
-            assert!(props
+            assert!(item.properties.contains_key("uid"));
+            assert!(item.properties.contains_key("url"));
+            assert!(item
+                .properties
                 .get("url")
                 .unwrap()
                 .iter()
-                .any(|i| i == props.get("uid").and_then(|v| v.first()).unwrap()));
-            assert!(props.contains_key("name"));
-            if let Some(PropertyValue::Plain(name)) = props.get("name").and_then(|v| v.first()) {
+                .any(|i| i == item.properties.get("uid").and_then(|v| v.first()).unwrap()));
+            assert!(item.properties.contains_key("name"));
+            if let Some(PropertyValue::Plain(name)) = item.properties.get("name").and_then(|v| v.first()) {
                 assert_eq!(
                     name,
                     mf2.pointer("/properties/name/0")
@@ -337,13 +335,10 @@ mod tests {
             let html = crate::mf2::Entry { post: &mf2 }.to_string();
             let parsed: Document = microformats::from_html(&html, url.clone()).unwrap();
 
-            if let Some(item) = parsed.items.get(0) {
-                let _item = item.borrow();
-                let props = _item.properties.borrow();
-
+            if let Some(item) = parsed.items.first() {
                 check_dt_published(&mf2, item);
-                assert!(props.contains_key("like-of"));
-                match props.get("like-of").and_then(|v| v.first()) {
+                assert!(item.properties.contains_key("like-of"));
+                match item.properties.get("like-of").and_then(|v| v.first()) {
                     Some(PropertyValue::Url(url)) => {
                         assert_eq!(
                             url,
diff --git a/util/Cargo.toml b/util/Cargo.toml
index 0425849..3d8327c 100644
--- a/util/Cargo.toml
+++ b/util/Cargo.toml
@@ -11,8 +11,8 @@ fs = ["rand", "tokio", "tokio/fs"]
 [dependencies]
 serde = { version = "^1.0.170", features = ["derive"] }
 serde_json = "^1.0.64"
-axum-core = "^0.3.4"
-http = "^0.2.7"
+axum-core = "^0.4.3"
+http = "^1.0"
 async-trait = "^0.1.50"
 futures-util = "^0.3.14"
 uuid = "^1.3.3"
@@ -24,6 +24,6 @@ version = "^1.16.1"
 features = ["tracing"]
 optional = true
 [dependencies.sqlx]
-version = "0.7"
+version = "0.8"
 features = ["json"]
 optional = true
\ No newline at end of file