about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--kittybox-rs/Cargo.lock609
-rw-r--r--kittybox-rs/Cargo.toml7
-rw-r--r--kittybox-rs/src/database/file/mod.rs8
-rw-r--r--kittybox-rs/src/database/mod.rs2
-rw-r--r--kittybox-rs/src/frontend/mod.rs6
-rw-r--r--kittybox-rs/src/frontend/onboarding.rs4
-rw-r--r--kittybox-rs/src/indieauth/mod.rs1
-rw-r--r--kittybox-rs/src/main.rs1
-rw-r--r--kittybox-rs/templates/Cargo.toml9
-rw-r--r--kittybox-rs/templates/src/lib.rs18
-rw-r--r--kittybox-rs/templates/src/mf2.rs409
-rw-r--r--kittybox-rs/templates/src/templates.rs414
12 files changed, 513 insertions, 975 deletions
diff --git a/kittybox-rs/Cargo.lock b/kittybox-rs/Cargo.lock
index e325b54..ce6417d 100644
--- a/kittybox-rs/Cargo.lock
+++ b/kittybox-rs/Cargo.lock
@@ -9,75 +9,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
 [[package]]
-name = "aead"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
-name = "aes"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
-dependencies = [
- "aes-soft",
- "aesni",
- "cipher",
-]
-
-[[package]]
-name = "aes-gcm"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da"
-dependencies = [
- "aead",
- "aes",
- "cipher",
- "ctr",
- "ghash",
- "subtle",
-]
-
-[[package]]
-name = "aes-soft"
-version = "0.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
-dependencies = [
- "cipher",
- "opaque-debug",
-]
-
-[[package]]
-name = "aesni"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
-dependencies = [
- "cipher",
- "opaque-debug",
-]
-
-[[package]]
-name = "aho-corasick"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
-dependencies = [
- "memchr 0.1.11",
-]
-
-[[package]]
 name = "aho-corasick"
 version = "0.7.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
 dependencies = [
- "memchr 2.5.0",
+ "memchr",
 ]
 
 [[package]]
@@ -101,7 +38,7 @@ version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
 dependencies = [
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -149,7 +86,7 @@ dependencies = [
  "brotli",
  "flate2",
  "futures-core",
- "memchr 2.5.0",
+ "memchr",
  "pin-project-lite",
  "tokio",
 ]
@@ -193,14 +130,14 @@ dependencies = [
  "concurrent-queue",
  "futures-lite",
  "libc",
- "log 0.4.17",
+ "log",
  "once_cell",
  "parking",
  "polling",
  "slab",
  "socket2",
  "waker-fn",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -244,7 +181,7 @@ dependencies = [
  "libc",
  "once_cell",
  "signal-hook",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -265,8 +202,8 @@ dependencies = [
  "futures-lite",
  "gloo-timers",
  "kv-log-macro",
- "log 0.4.17",
- "memchr 2.5.0",
+ "log",
+ "memchr",
  "num_cpus",
  "once_cell",
  "pin-project-lite",
@@ -306,7 +243,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
 dependencies = [
  "hermit-abi",
  "libc",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -341,7 +278,7 @@ dependencies = [
  "hyper",
  "itoa 1.0.1",
  "matchit",
- "memchr 2.5.0",
+ "memchr",
  "mime",
  "multer",
  "percent-encoding",
@@ -372,12 +309,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "base-x"
-version = "0.2.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc19a4937b4fbd3fe3379793130e42060d10627a360f2127802b10b87e7baf74"
-
-[[package]]
 name = "base64"
 version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -391,7 +322,7 @@ checksum = "cb53b6b315f924c7f113b162e53b3901c05fc9966baf84d201dfcc7432a4bb38"
 dependencies = [
  "lalrpop",
  "lalrpop-util",
- "regex 1.5.5",
+ "regex",
 ]
 
 [[package]]
@@ -520,17 +451,8 @@ dependencies = [
  "num-integer",
  "num-traits",
  "serde",
- "time 0.1.44",
- "winapi 0.3.9",
-]
-
-[[package]]
-name = "cipher"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
-dependencies = [
- "generic-array",
+ "time",
+ "winapi",
 ]
 
 [[package]]
@@ -550,7 +472,7 @@ checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
 dependencies = [
  "atty",
  "lazy_static",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -561,7 +483,7 @@ checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948"
 dependencies = [
  "bytes",
  "futures-core",
- "memchr 2.5.0",
+ "memchr",
  "pin-project-lite",
  "tokio",
  "tokio-util 0.7.3",
@@ -577,35 +499,12 @@ dependencies = [
 ]
 
 [[package]]
-name = "const_fn"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
-
-[[package]]
 name = "convert_case"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
 
 [[package]]
-name = "cookie"
-version = "0.14.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
-dependencies = [
- "aes-gcm",
- "base64",
- "hkdf",
- "hmac",
- "percent-encoding",
- "rand 0.8.5",
- "sha2",
- "time 0.2.27",
- "version_check",
-]
-
-[[package]]
 name = "cpufeatures"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -615,12 +514,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "cpuid-bool"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
-
-[[package]]
 name = "crc32fast"
 version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -656,16 +549,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "crypto-mac"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a"
-dependencies = [
- "generic-array",
- "subtle",
-]
-
-[[package]]
 name = "cssparser"
 version = "0.27.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -703,15 +586,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "ctr"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f"
-dependencies = [
- "cipher",
-]
-
-[[package]]
 name = "curl"
 version = "0.4.43"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -723,7 +597,7 @@ dependencies = [
  "openssl-sys",
  "schannel",
  "socket2",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -739,7 +613,7 @@ dependencies = [
  "openssl-sys",
  "pkg-config",
  "vcpkg",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -757,7 +631,7 @@ dependencies = [
  "convert_case",
  "proc-macro2 1.0.38",
  "quote 1.0.18",
- "rustc_version 0.4.0",
+ "rustc_version",
  "syn 1.0.93",
 ]
 
@@ -816,16 +690,10 @@ checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
 dependencies = [
  "libc",
  "redox_users",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
-name = "discard"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
-
-[[package]]
 name = "dtoa"
 version = "0.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -848,7 +716,7 @@ checksum = "18a857bc01b5ae04874234f6b5d16b8b8fa86910aa5777479c2669b5df607fce"
 dependencies = [
  "html5ever 0.25.2",
  "kuchiki",
- "regex 1.5.5",
+ "regex",
 ]
 
 [[package]]
@@ -872,7 +740,7 @@ version = "0.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3"
 dependencies = [
- "log 0.4.17",
+ "log",
 ]
 
 [[package]]
@@ -885,16 +753,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "env_logger"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15abd780e45b3ea4f76b4e9a26ff4843258dd8a3eed2775a0e7368c2e7936c2f"
-dependencies = [
- "log 0.3.9",
- "regex 0.1.80",
-]
-
-[[package]]
 name = "event-listener"
 version = "2.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1027,7 +885,7 @@ dependencies = [
  "fastrand",
  "futures-core",
  "futures-io",
- "memchr 2.5.0",
+ "memchr",
  "parking",
  "pin-project-lite",
  "waker-fn",
@@ -1068,7 +926,7 @@ dependencies = [
  "futures-macro",
  "futures-sink",
  "futures-task",
- "memchr 2.5.0",
+ "memchr",
  "pin-project-lite",
  "pin-utils",
  "slab",
@@ -1116,16 +974,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "ghash"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375"
-dependencies = [
- "opaque-debug",
- "polyval",
-]
-
-[[package]]
 name = "gloo-timers"
 version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1203,32 +1051,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
 [[package]]
-name = "hkdf"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
-dependencies = [
- "digest 0.9.0",
- "hmac",
-]
-
-[[package]]
-name = "hmac"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
-dependencies = [
- "crypto-mac",
- "digest 0.9.0",
-]
-
-[[package]]
 name = "html5ever"
 version = "0.22.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c213fa6a618dc1da552f54f85cba74b05d8e883c92ec4e89067736938084c26e"
 dependencies = [
- "log 0.4.17",
+ "log",
  "mac",
  "markup5ever 0.7.5",
  "proc-macro2 0.4.30",
@@ -1242,7 +1070,7 @@ version = "0.25.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148"
 dependencies = [
- "log 0.4.17",
+ "log",
  "mac",
  "markup5ever 0.10.1",
  "proc-macro2 1.0.38",
@@ -1279,29 +1107,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
 
 [[package]]
-name = "http-types"
-version = "2.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad"
-dependencies = [
- "anyhow",
- "async-channel",
- "async-std",
- "base64",
- "cookie",
- "futures-lite",
- "http",
- "infer",
- "pin-project-lite",
- "rand 0.7.3",
- "serde",
- "serde_json",
- "serde_qs",
- "serde_urlencoded",
- "url",
-]
-
-[[package]]
 name = "httparse"
 version = "1.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1331,8 +1136,8 @@ dependencies = [
  "isahc",
  "lazy_static",
  "levenshtein",
- "log 0.4.17",
- "regex 1.5.5",
+ "log",
+ "regex",
  "serde",
  "serde_json",
  "serde_regex",
@@ -1400,12 +1205,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "infer"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
-
-[[package]]
 name = "instant"
 version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1435,7 +1234,7 @@ dependencies = [
  "event-listener",
  "futures-lite",
  "http",
- "log 0.4.17",
+ "log",
  "mime",
  "once_cell",
  "polling",
@@ -1478,16 +1277,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "kernel32-sys"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
-dependencies = [
- "winapi 0.2.8",
- "winapi-build",
-]
-
-[[package]]
 name = "kittybox"
 version = "0.1.0"
 dependencies = [
@@ -1503,7 +1292,6 @@ dependencies = [
  "futures",
  "futures-util",
  "hex",
- "http-types",
  "httpmock",
  "hyper",
  "kittybox-indieauth",
@@ -1511,7 +1299,6 @@ dependencies = [
  "kittybox-util",
  "lazy_static",
  "listenfd",
- "log 0.4.17",
  "markdown",
  "microformats",
  "mockito",
@@ -1527,14 +1314,12 @@ dependencies = [
  "serde_variant",
  "sha2",
  "tempdir",
- "test-logger",
  "tokio",
  "tokio-stream",
  "tokio-util 0.7.3",
  "tower",
  "tower-http",
  "tracing",
- "tracing-log",
  "tracing-subscriber",
  "tracing-test",
  "tracing-tree",
@@ -1564,13 +1349,12 @@ dependencies = [
  "ellipse",
  "faker_rand",
  "http",
+ "kittybox-indieauth",
  "kittybox-util",
- "log 0.4.17",
  "markup",
  "microformats",
  "rand 0.8.5",
  "serde_json",
- "test-logger",
 ]
 
 [[package]]
@@ -1601,7 +1385,7 @@ version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
 dependencies = [
- "log 0.4.17",
+ "log",
 ]
 
 [[package]]
@@ -1619,8 +1403,8 @@ dependencies = [
  "lalrpop-util",
  "petgraph",
  "pico-args",
- "regex 1.5.5",
- "regex-syntax 0.6.25",
+ "regex",
+ "regex-syntax",
  "string_cache 0.8.4",
  "term",
  "tiny-keccak",
@@ -1633,7 +1417,7 @@ version = "0.19.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bcf796c978e9b4d983414f4caedc9273aa33ee214c5b887bd55fde84c85d2dc4"
 dependencies = [
- "regex 1.5.5",
+ "regex",
 ]
 
 [[package]]
@@ -1684,7 +1468,7 @@ checksum = "c02b14f35d9f5f082fd0b1b34aa0ef32e3354c859c721d7f3325b3f79a42ba54"
 dependencies = [
  "libc",
  "uuid",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -1699,15 +1483,6 @@ dependencies = [
 
 [[package]]
 name = "log"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
-dependencies = [
- "log 0.4.17",
-]
-
-[[package]]
-name = "log"
 version = "0.4.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
@@ -1730,7 +1505,7 @@ checksum = "ef3aab6a1d529b112695f72beec5ee80e729cb45af58663ec902c8fac764ecdd"
 dependencies = [
  "lazy_static",
  "pipeline",
- "regex 1.5.5",
+ "regex",
 ]
 
 [[package]]
@@ -1776,7 +1551,7 @@ version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd"
 dependencies = [
- "log 0.4.17",
+ "log",
  "phf 0.8.0",
  "phf_codegen 0.8.0",
  "string_cache 0.8.4",
@@ -1807,15 +1582,6 @@ checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb"
 
 [[package]]
 name = "memchr"
-version = "0.1.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "memchr"
 version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
@@ -1829,8 +1595,8 @@ dependencies = [
  "chrono",
  "html5ever 0.22.5",
  "lazy_static",
- "log 0.4.17",
- "regex 1.5.5",
+ "log",
+ "regex",
  "serde",
  "serde_json",
  "thiserror",
@@ -1859,7 +1625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799"
 dependencies = [
  "libc",
- "log 0.4.17",
+ "log",
  "wasi 0.11.0+wasi-snapshot-preview1",
  "windows-sys",
 ]
@@ -1875,9 +1641,9 @@ dependencies = [
  "difference",
  "httparse",
  "lazy_static",
- "log 0.4.17",
+ "log",
  "rand 0.8.5",
- "regex 1.5.5",
+ "regex",
  "serde_json",
  "serde_urlencoded",
 ]
@@ -1893,8 +1659,8 @@ dependencies = [
  "futures-util",
  "http",
  "httparse",
- "log 0.4.17",
- "memchr 2.5.0",
+ "log",
+ "memchr",
  "mime",
  "spin 0.9.3",
  "version_check",
@@ -2016,7 +1782,7 @@ dependencies = [
  "libc",
  "redox_syscall",
  "smallvec",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -2217,20 +1983,9 @@ checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
 dependencies = [
  "cfg-if",
  "libc",
- "log 0.4.17",
+ "log",
  "wepoll-ffi",
- "winapi 0.3.9",
-]
-
-[[package]]
-name = "polyval"
-version = "0.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd"
-dependencies = [
- "cpuid-bool",
- "opaque-debug",
- "universal-hash",
+ "winapi",
 ]
 
 [[package]]
@@ -2293,7 +2048,7 @@ dependencies = [
  "fnv",
  "lazy_static",
  "libc",
- "memchr 2.5.0",
+ "memchr",
  "parking_lot 0.11.2",
  "procfs",
  "protobuf",
@@ -2334,7 +2089,7 @@ dependencies = [
  "libc",
  "rand_core 0.3.1",
  "rdrand",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -2353,7 +2108,7 @@ dependencies = [
  "rand_os",
  "rand_pcg 0.1.2",
  "rand_xorshift",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -2479,7 +2234,7 @@ checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
 dependencies = [
  "libc",
  "rand_core 0.4.2",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -2493,7 +2248,7 @@ dependencies = [
  "libc",
  "rand_core 0.4.2",
  "rdrand",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -2575,26 +2330,13 @@ dependencies = [
 
 [[package]]
 name = "regex"
-version = "0.1.80"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f"
-dependencies = [
- "aho-corasick 0.5.3",
- "memchr 0.1.11",
- "regex-syntax 0.3.9",
- "thread_local 0.2.7",
- "utf8-ranges",
-]
-
-[[package]]
-name = "regex"
 version = "1.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
 dependencies = [
- "aho-corasick 0.7.18",
- "memchr 2.5.0",
- "regex-syntax 0.6.25",
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
 ]
 
 [[package]]
@@ -2603,17 +2345,11 @@ version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
 dependencies = [
- "regex-syntax 0.6.25",
+ "regex-syntax",
 ]
 
 [[package]]
 name = "regex-syntax"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
-
-[[package]]
-name = "regex-syntax"
 version = "0.6.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
@@ -2630,7 +2366,7 @@ version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
 dependencies = [
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -2653,7 +2389,7 @@ dependencies = [
  "ipnet",
  "js-sys",
  "lazy_static",
- "log 0.4.17",
+ "log",
  "mime",
  "percent-encoding",
  "pin-project-lite",
@@ -2685,16 +2421,7 @@ dependencies = [
  "spin 0.5.2",
  "untrusted",
  "web-sys",
- "winapi 0.3.9",
-]
-
-[[package]]
-name = "rustc_version"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
-dependencies = [
- "semver 0.9.0",
+ "winapi",
 ]
 
 [[package]]
@@ -2703,7 +2430,7 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
 dependencies = [
- "semver 1.0.9",
+ "semver",
 ]
 
 [[package]]
@@ -2712,7 +2439,7 @@ version = "0.20.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921"
 dependencies = [
- "log 0.4.17",
+ "log",
  "ring",
  "sct",
  "webpki",
@@ -2746,7 +2473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
 dependencies = [
  "lazy_static",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -2775,7 +2502,7 @@ dependencies = [
  "cssparser",
  "derive_more",
  "fxhash",
- "log 0.4.17",
+ "log",
  "matches",
  "phf 0.8.0",
  "phf_codegen 0.8.0",
@@ -2787,26 +2514,11 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
-dependencies = [
- "semver-parser",
-]
-
-[[package]]
-name = "semver"
 version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd"
 
 [[package]]
-name = "semver-parser"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
-
-[[package]]
 name = "serde"
 version = "1.0.137"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2838,23 +2550,12 @@ 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_regex"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf"
 dependencies = [
- "regex 1.5.5",
+ "regex",
  "serde",
 ]
 
@@ -3004,7 +2705,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
 dependencies = [
  "libc",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -3026,64 +2727,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
 
 [[package]]
-name = "standback"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
-dependencies = [
- "version_check",
-]
-
-[[package]]
-name = "stdweb"
-version = "0.4.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
-dependencies = [
- "discard",
- "rustc_version 0.2.3",
- "stdweb-derive",
- "stdweb-internal-macros",
- "stdweb-internal-runtime",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "stdweb-derive"
-version = "0.5.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
-dependencies = [
- "proc-macro2 1.0.38",
- "quote 1.0.18",
- "serde",
- "serde_derive",
- "syn 1.0.93",
-]
-
-[[package]]
-name = "stdweb-internal-macros"
-version = "0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
-dependencies = [
- "base-x",
- "proc-macro2 1.0.38",
- "quote 1.0.18",
- "serde",
- "serde_derive",
- "serde_json",
- "sha1",
- "syn 1.0.93",
-]
-
-[[package]]
-name = "stdweb-internal-runtime"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
-
-[[package]]
 name = "string_cache"
 version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3144,12 +2787,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
 
 [[package]]
-name = "subtle"
-version = "2.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
-
-[[package]]
 name = "syn"
 version = "0.15.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3206,16 +2843,7 @@ checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
 dependencies = [
  "dirs-next",
  "rustversion",
- "winapi 0.3.9",
-]
-
-[[package]]
-name = "test-logger"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e55ec868b79cb8e63f8921843c10e3083137cfaa171a67209e6a2656ccd4d8a"
-dependencies = [
- "env_logger",
+ "winapi",
 ]
 
 [[package]]
@@ -3245,25 +2873,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "thread-id"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
-dependencies = [
- "kernel32-sys",
- "libc",
-]
-
-[[package]]
-name = "thread_local"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5"
-dependencies = [
- "thread-id",
-]
-
-[[package]]
 name = "thread_local"
 version = "1.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3280,45 +2889,7 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
 dependencies = [
  "libc",
  "wasi 0.10.0+wasi-snapshot-preview1",
- "winapi 0.3.9",
-]
-
-[[package]]
-name = "time"
-version = "0.2.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242"
-dependencies = [
- "const_fn",
- "libc",
- "standback",
- "stdweb",
- "time-macros",
- "version_check",
- "winapi 0.3.9",
-]
-
-[[package]]
-name = "time-macros"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
-dependencies = [
- "proc-macro-hack",
- "time-macros-impl",
-]
-
-[[package]]
-name = "time-macros-impl"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
-dependencies = [
- "proc-macro-hack",
- "proc-macro2 1.0.38",
- "quote 1.0.18",
- "standback",
- "syn 1.0.93",
+ "winapi",
 ]
 
 [[package]]
@@ -3353,7 +2924,7 @@ checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
 dependencies = [
  "bytes",
  "libc",
- "memchr 2.5.0",
+ "memchr",
  "mio",
  "num_cpus",
  "once_cell",
@@ -3363,7 +2934,7 @@ dependencies = [
  "socket2",
  "tokio-macros",
  "tracing",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -3408,7 +2979,7 @@ dependencies = [
  "bytes",
  "futures-core",
  "futures-sink",
- "log 0.4.17",
+ "log",
  "pin-project-lite",
  "tokio",
 ]
@@ -3482,7 +3053,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09"
 dependencies = [
  "cfg-if",
- "log 0.4.17",
+ "log",
  "pin-project-lite",
  "tracing-attributes",
  "tracing-core",
@@ -3526,7 +3097,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
 dependencies = [
  "lazy_static",
- "log 0.4.17",
+ "log",
  "tracing-core",
 ]
 
@@ -3549,12 +3120,12 @@ dependencies = [
  "ansi_term",
  "lazy_static",
  "matchers",
- "regex 1.5.5",
+ "regex",
  "serde",
  "serde_json",
  "sharded-slab",
  "smallvec",
- "thread_local 1.1.4",
+ "thread_local",
  "tracing",
  "tracing-core",
  "tracing-log",
@@ -3643,16 +3214,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
 
 [[package]]
-name = "universal-hash"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
-dependencies = [
- "generic-array",
- "subtle",
-]
-
-[[package]]
 name = "untrusted"
 version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3678,12 +3239,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
 
 [[package]]
-name = "utf8-ranges"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f"
-
-[[package]]
 name = "uuid"
 version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3729,7 +3284,7 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
 dependencies = [
- "log 0.4.17",
+ "log",
  "try-lock",
 ]
 
@@ -3769,7 +3324,7 @@ checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
 dependencies = [
  "bumpalo",
  "lazy_static",
- "log 0.4.17",
+ "log",
  "proc-macro2 1.0.38",
  "quote 1.0.18",
  "syn 1.0.93",
@@ -3857,12 +3412,6 @@ dependencies = [
 
 [[package]]
 name = "winapi"
-version = "0.2.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
-
-[[package]]
-name = "winapi"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
@@ -3872,12 +3421,6 @@ dependencies = [
 ]
 
 [[package]]
-name = "winapi-build"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
-
-[[package]]
 name = "winapi-i686-pc-windows-gnu"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3938,5 +3481,5 @@ version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
 dependencies = [
- "winapi 0.3.9",
+ "winapi",
 ]
diff --git a/kittybox-rs/Cargo.toml b/kittybox-rs/Cargo.toml
index 833785c..4ed65b3 100644
--- a/kittybox-rs/Cargo.toml
+++ b/kittybox-rs/Cargo.toml
@@ -45,7 +45,6 @@ features = ["axum"]
 [dev-dependencies]
 mockito = "^0.30.0"          # HTTP mocking for Rust.
 tempdir = "^0.3.7"           # A library for managing a temporary directory and deleting all contents when it's dropped
-test-logger = "^0.1.0"       # Simple helper to initialize env_logger before unit and integration tests
 httpmock = "^0.6"            # HTTP mocking library that allows you to simulate responses from HTTP based services
 faker_rand = "^0.1.1"        # Seedable, rand-compatible generators of fake data
 rand = "^0.8.5"              # Utilities for random number generation
@@ -62,7 +61,7 @@ futures-util = "^0.3.14"     # Common utilities and extension traits for the fut
 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
+#log = "^0.4.14"              # A lightweight logging facade for Rust
 markdown = "^0.3.0"          # 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.
@@ -76,7 +75,6 @@ tracing-tree = "0.2.1"
 tracing-subscriber = { version = "0.3.11", features = ["env-filter", "json"] }
 tower-http = { version = "0.3.3", features = ["trace", "cors", "catch-panic"] }
 tower = { version = "0.4.12", features = ["tracing"] }
-tracing-log = "0.1.3"
 [dependencies.tokio]
 version = "^1.16.1"
 features = ["full", "tracing"] # TODO determine if my app doesn't need some features
@@ -110,9 +108,6 @@ features = ["derive"]
 [dependencies.url]           # URL library for Rust, based on the WHATWG URL Standard
 version = "^2.2.1"
 features = ["serde"]
-[dependencies.http-types]    # Common types for HTTP operations
-version = "^2.11.0"
-features = ["http"]
 [dependencies.hyper]
 version = "^0.14.17"
 features = ["stream", "runtime"]
diff --git a/kittybox-rs/src/database/file/mod.rs b/kittybox-rs/src/database/file/mod.rs
index f83e682..bafb7bf 100644
--- a/kittybox-rs/src/database/file/mod.rs
+++ b/kittybox-rs/src/database/file/mod.rs
@@ -9,7 +9,7 @@ use std::path::{Path, PathBuf};
 use tokio::fs::{File, OpenOptions};
 use tokio::io::{AsyncReadExt, AsyncWriteExt};
 use tokio::task::spawn_blocking;
-use tracing::debug;
+use tracing::{debug, error};
 
 impl From<std::io::Error> for StorageError {
     fn from(source: std::io::Error) -> Self {
@@ -250,7 +250,7 @@ async fn hydrate_author<S: Storage>(
                                 None => json!(i),
                             },
                             Err(e) => {
-                                log::error!("Error while hydrating post {}: {}", url, e);
+                                error!("Error while hydrating post {}: {}", url, e);
                                 json!(i)
                             }
                         }
@@ -583,14 +583,14 @@ impl Storage for FileStorage {
 
     #[tracing::instrument(skip(self))]
     async fn get_setting(&self, setting: Settings, user: &'_ str) -> Result<String> {
-        log::debug!("User for getting settings: {}", user);
+        debug!("User for getting settings: {}", user);
         let url = axum::http::Uri::try_from(user).expect("Couldn't parse a URL");
         let mut path = relative_path::RelativePathBuf::new();
         path.push(url.authority().unwrap().to_string());
         path.push("settings");
 
         let path = path.to_path(&self.root_dir);
-        log::debug!("Getting settings from {:?}", &path);
+        debug!("Getting settings from {:?}", &path);
         let setting = setting.to_string();
         let mut file = File::open(path).await?;
         let mut content = String::new();
diff --git a/kittybox-rs/src/database/mod.rs b/kittybox-rs/src/database/mod.rs
index 10fbc72..9072d9d 100644
--- a/kittybox-rs/src/database/mod.rs
+++ b/kittybox-rs/src/database/mod.rs
@@ -544,8 +544,8 @@ mod tests {
     macro_rules! file_test {
         ($func_name:ident) => {
             #[tokio::test]
+            #[tracing_test::traced_test]
             async fn $func_name() {
-                test_logger::ensure_env_logger_initialized();
                 let tempdir = tempdir::TempDir::new("file").expect("Failed to create tempdir");
                 let backend = super::super::FileStorage::new(
                     tempdir.path().to_path_buf()
diff --git a/kittybox-rs/src/frontend/mod.rs b/kittybox-rs/src/frontend/mod.rs
index bc9925f..00d3ba6 100644
--- a/kittybox-rs/src/frontend/mod.rs
+++ b/kittybox-rs/src/frontend/mod.rs
@@ -14,8 +14,6 @@ pub mod onboarding;
 
 use kittybox_templates::{Entry, ErrorPage, Feed, MainPage, Template, VCard, POSTS_PER_PAGE};
 
-pub use kittybox_util::IndiewebEndpoints;
-
 #[derive(Debug, Deserialize)]
 pub struct QueryParams {
     after: Option<String>,
@@ -143,7 +141,6 @@ pub async fn homepage<D: Storage>(
                 Template {
                     title: &blogname,
                     blog_name: &blogname,
-                    endpoints: None, // XXX this will be deprecated soon anyway
                     feeds: channels,
                     user,
                     content: MainPage {
@@ -182,7 +179,6 @@ pub async fn homepage<D: Storage>(
                     Template {
                         title: &blogname,
                         blog_name: &blogname,
-                        endpoints: None, // XXX this will be deprecated soon anyway
                         feeds: channels,
                         user,
                         content: ErrorPage {
@@ -228,7 +224,6 @@ pub async fn catchall<D: Storage>(
                 Template {
                     title: &blogname,
                     blog_name: &blogname,
-                    endpoints: None, // XXX this will be deprecated soon anyway
                     feeds: channels,
                     user,
                     content: match post.pointer("/type/0").and_then(|i| i.as_str()) {
@@ -258,7 +253,6 @@ pub async fn catchall<D: Storage>(
                 Template {
                     title: &blogname,
                     blog_name: &blogname,
-                    endpoints: None,
                     feeds: channels,
                     user,
                     content: ErrorPage {
diff --git a/kittybox-rs/src/frontend/onboarding.rs b/kittybox-rs/src/frontend/onboarding.rs
index e9eceb2..b498aed 100644
--- a/kittybox-rs/src/frontend/onboarding.rs
+++ b/kittybox-rs/src/frontend/onboarding.rs
@@ -17,7 +17,6 @@ pub async fn get() -> Html<String> {
             title: "Kittybox - Onboarding",
             blog_name: "Kittybox",
             feeds: vec![],
-            endpoints: None,
             user: None,
             content: OnboardingPage {}.to_string(),
         }
@@ -83,7 +82,7 @@ async fn onboard<D: Storage + 'static>(
         if feed.name.is_empty() || feed.slug.is_empty() {
             continue;
         };
-        log::debug!("Creating feed {} with slug {}", &feed.name, &feed.slug);
+        debug!("Creating feed {} with slug {}", &feed.name, &feed.slug);
         let (_, feed) = crate::micropub::normalize_mf2(
             serde_json::json!({
                 "type": ["h-feed"],
@@ -130,7 +129,6 @@ pub async fn post<D: Storage + 'static>(
                             title: "Kittybox - Onboarding",
                             blog_name: "Kittybox",
                             feeds: vec![],
-                            endpoints: None,
                             user: None,
                             content: ErrorPage {
                                 code: err.code(),
diff --git a/kittybox-rs/src/indieauth/mod.rs b/kittybox-rs/src/indieauth/mod.rs
index b7d4597..8a37959 100644
--- a/kittybox-rs/src/indieauth/mod.rs
+++ b/kittybox-rs/src/indieauth/mod.rs
@@ -66,7 +66,6 @@ async fn authorization_endpoint_get(
     Html(kittybox_templates::Template {
         title: "Confirm sign-in via IndieAuth",
         blog_name: "Kittybox",
-        endpoints: None,
         feeds: vec![],
         // TODO
         user: None,
diff --git a/kittybox-rs/src/main.rs b/kittybox-rs/src/main.rs
index 9e81aad..7a363b8 100644
--- a/kittybox-rs/src/main.rs
+++ b/kittybox-rs/src/main.rs
@@ -5,7 +5,6 @@ use url::Url;
 
 #[tokio::main]
 async fn main() {
-    // TODO use tracing instead of log
     use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Registry};
     Registry::default()
         .with(EnvFilter::from_default_env())
diff --git a/kittybox-rs/templates/Cargo.toml b/kittybox-rs/templates/Cargo.toml
index fe8ac19..a32a3a2 100644
--- a/kittybox-rs/templates/Cargo.toml
+++ b/kittybox-rs/templates/Cargo.toml
@@ -8,19 +8,20 @@ edition = "2021"
 [dev-dependencies]
 faker_rand = "^0.1.1"
 rand = "^0.8.5"
-test-logger = "^0.1.0"
 [dev-dependencies.microformats]
 version="^0.2.0"
 
 [dependencies]
 ellipse = "^0.2.0"           # Truncate and ellipsize strings in a human-friendly way
 http = "^0.2.7"              # Hyper's strong HTTP types
-log = "^0.4.14"              # A lightweight logging facade for Rust
 markup = "^0.12.0"           # HTML templating engine
 serde_json = "^1.0.64"       # A JSON serialization file format
-[dependencies.chrono]        # Date and time library for Rust
+[dependencies.chrono]
 version = "^0.4.19"
 features = ["serde"]
 [dependencies.kittybox-util]
 version = "0.1.0"
-path = "../util"
\ No newline at end of file
+path = "../util"
+[dependencies.kittybox-indieauth]
+version = "0.1.0"
+path = "../indieauth"
\ No newline at end of file
diff --git a/kittybox-rs/templates/src/lib.rs b/kittybox-rs/templates/src/lib.rs
index 8e9abba..d58e831 100644
--- a/kittybox-rs/templates/src/lib.rs
+++ b/kittybox-rs/templates/src/lib.rs
@@ -1,9 +1,13 @@
 mod templates;
-pub use templates::{Entry, ErrorPage, Feed, MainPage, Template, VCard, POSTS_PER_PAGE};
+pub use templates::{ErrorPage, MainPage, Template};
 mod onboarding;
 pub use onboarding::OnboardingPage;
+mod indieauth;
+pub use indieauth::AuthorizationRequestPage;
 mod login;
 pub use login::LoginPage;
+mod mf2;
+pub use mf2::{Entry, VCard, Feed, Food, POSTS_PER_PAGE};
 
 #[cfg(test)]
 mod tests {
@@ -196,11 +200,9 @@ mod tests {
     #[test]
     #[ignore = "see https://gitlab.com/maxburon/microformats-parser/-/issues/7"]
     fn test_note() {
-        test_logger::ensure_env_logger_initialized();
-
         let mf2 = gen_random_post(&rand::random::<Domain>().to_string(), PostType::Note);
 
-        let html = crate::templates::Entry { post: &mf2 }.to_string();
+        let html = crate::mf2::Entry { post: &mf2 }.to_string();
 
         let url: Url = mf2
             .pointer("/properties/uid/0")
@@ -231,10 +233,8 @@ mod tests {
 
     #[test]
     fn test_article() {
-        test_logger::ensure_env_logger_initialized();
-
         let mf2 = gen_random_post(&rand::random::<Domain>().to_string(), PostType::Article);
-        let html = crate::templates::Entry { post: &mf2 }.to_string();
+        let html = crate::mf2::Entry { post: &mf2 }.to_string();
         let url: Url = mf2
             .pointer("/properties/uid/0")
             .and_then(|i| i.as_str())
@@ -273,8 +273,6 @@ mod tests {
 
     #[test]
     fn test_like_of() {
-        test_logger::ensure_env_logger_initialized();
-
         for likeof in [
             PostType::LikeOf(gen_random_post(
                 &rand::random::<Domain>().to_string(),
@@ -294,7 +292,7 @@ mod tests {
                 .and_then(|i| i.as_str())
                 .and_then(|u| u.parse().ok())
                 .unwrap();
-            let html = crate::templates::Entry { post: &mf2 }.to_string();
+            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) {
diff --git a/kittybox-rs/templates/src/mf2.rs b/kittybox-rs/templates/src/mf2.rs
new file mode 100644
index 0000000..d9fc5e7
--- /dev/null
+++ b/kittybox-rs/templates/src/mf2.rs
@@ -0,0 +1,409 @@
+use ellipse::Ellipse;
+
+pub static POSTS_PER_PAGE: usize = 20;
+
+/// Return a pretty location specifier from a geo: URI.
+fn decode_geo_uri(uri: &str) -> String {
+    if let Some(part) = uri.split(':').collect::<Vec<_>>().get(1) {
+        if let Some(part) = part.split(';').next() {
+            let mut parts = part.split(',');
+            let lat = parts.next().unwrap();
+            let lon = parts.next().unwrap();
+            // TODO - format them as proper latitude and longitude
+            return format!("{}, {}", lat, lon);
+        } else {
+            uri.to_string()
+        }
+    } else {
+        uri.to_string()
+    }
+}
+
+markup::define! {
+    Entry<'a>(post: &'a serde_json::Value) {
+        @if post.pointer("/properties/like-of").is_none() && post.pointer("/properties/bookmark-of").is_none() {
+            @FullEntry { post }
+        } else {
+            // Show a mini-post.
+            @MiniEntry { post }
+        }
+    }
+    MiniEntry<'a>(post: &'a serde_json::Value) {
+        article."h-entry mini-entry" {
+            @if let Some(author) = post["properties"]["author"][0].as_object() {
+                span."mini-h-card"."u-author" {
+                    a."u-author"[href=author["properties"]["uid"][0].as_str().unwrap()] {
+                        @if let Some(photo) = author["properties"]["photo"][0].as_str() {
+                            img[src=photo, loading="lazy"];
+                        }
+                        @author["properties"]["name"][0].as_str().unwrap()
+                    }
+                }
+                @if let Some(likeof) = post["properties"]["like-of"][0].as_str() {
+                    " ❤️ "
+                    a."u-like-of"[href=likeof] { @likeof }
+                } else if let Some(likeof) = post["properties"]["like-of"][0].as_object() {
+                    a."u-like-of"[href=likeof["properties"]["url"][0].as_str().unwrap()] {
+                        @likeof["properties"]["name"][0]
+                            .as_str()
+                            .unwrap_or_else(|| likeof["properties"]["url"][0].as_str().unwrap())
+                    }
+                }
+                @if let Some(bookmarkof) = post["properties"]["bookmark-of"][0].as_str() {
+                    " 🔖 "
+                    a."u-bookmark-of"[href=bookmarkof] { @bookmarkof }
+                } else if let Some(bookmarkof) = post["properties"]["bookmark-of"][0].as_object() {
+                    a."u-bookmark-of"[href=bookmarkof["properties"]["url"][0].as_str().unwrap()] {
+                        @bookmarkof["properties"]["name"][0]
+                            .as_str()
+                            .unwrap_or_else(|| bookmarkof["properties"]["url"][0].as_str().unwrap())
+                    }
+                }
+                @if let Some(published) = post["properties"]["published"][0].as_str() {
+                    time."dt-published"[datetime=published] {
+                        @chrono::DateTime::parse_from_rfc3339(published)
+                            .map(|dt| dt.format("on %a %b %e %T %Y").to_string())
+                            .unwrap_or("sometime in the past".to_string())
+                    }
+                }
+            }
+        }
+    }
+    FullEntry<'a>(post: &'a serde_json::Value) {
+        article."h-entry" {
+            header.metadata {
+                @if let Some(name) = post["properties"]["name"][0].as_str() {
+                    h1."p-name" { @name }
+                }
+                @if let Some(author) = post["properties"]["author"][0].as_object() {
+                    section."mini-h-card" {
+                        a.larger."u-author"[href=author["properties"]["uid"][0].as_str().unwrap()] {
+                            @if let Some(photo) = author["properties"]["photo"][0].as_str() {
+                                img[src=photo, loading="lazy"];
+                            }
+                            @author["properties"]["name"][0].as_str().unwrap()
+                        }
+                    }
+                }
+                div {
+                    span {
+                        a."u-url"."u-uid"[href=post["properties"]["uid"][0].as_str().unwrap()] {
+                            @if let Some(published) = post["properties"]["published"][0].as_str() {
+                                time."dt-published"[datetime=published] {
+                                    @chrono::DateTime::parse_from_rfc3339(published)
+                                        .map(|dt| dt.format("%a %b %e %T %Y").to_string())
+                                        .unwrap_or("sometime in the past".to_string())
+                                }
+                            }
+                        }
+                    }
+                    @if post["properties"]["visibility"][0].as_str().unwrap_or("public") != "public" {
+                        span."p-visibility"[value=post["properties"]["visibility"][0].as_str().unwrap()] {
+                            @post["properties"]["visibility"][0].as_str().unwrap()
+                        }
+                    }
+                    @if post["properties"]["category"].is_array() {
+                        span {
+                            ul.categories {
+                                "Tagged: "
+                                @for cat in post["properties"]["category"].as_array().unwrap() {
+                                    li."p-category" { @cat.as_str().unwrap() }
+                                }
+                            }
+                        }
+                    }
+                    @if post["properties"]["in-reply-to"].is_array() {
+                        // TODO: Rich reply contexts - blocked on MF2 parser
+                        span {
+                            "In reply to: "
+                            ul.replyctx {
+                                @for ctx in post["properties"]["in-reply-to"].as_array().unwrap() {
+                                    li { a."u-in-reply-to"[href=ctx.as_str().unwrap()] {
+                                        @ctx.as_str().unwrap().truncate_ellipse(24).as_ref()
+                                    } }
+                                }
+                            }
+                        }
+                    }
+                }
+                @if post["properties"]["url"].as_array().unwrap().len() > 1 {
+                    hr;
+                    ul {
+                        "Pretty permalinks for this post:"
+                        @for url in post["properties"]["url"].as_array().unwrap().iter().filter(|i| **i != post["properties"]["uid"][0]).map(|i| i.as_str().unwrap()) {
+                            li {
+                                a."u-url"[href=url] { @url }
+                            }
+                        }
+                    }
+                }
+                @if post["properties"]["location"].is_array() || post["properties"]["checkin"].is_array() {
+                    div {
+                        @if post["properties"]["checkin"].is_array() {
+                            span {
+                                "Check-in to: "
+                                @if post["properties"]["checkin"][0].is_string() {
+                                    // It's a URL
+                                    a."u-checkin"[href=post["properties"]["checkin"][0].as_str().unwrap()] {
+                                        @post["properties"]["checkin"][0].as_str().unwrap().truncate_ellipse(24).as_ref()
+                                    }
+                                } else {
+                                    a."u-checkin"[href=post["properties"]["checkin"][0]["properties"]["uid"][0].as_str().unwrap()] {
+                                        @post["properties"]["checkin"][0]["properties"]["name"][0].as_str().unwrap()
+                                    }
+                                }
+                            }
+                        }
+                        @if post["properties"]["location"].is_array() {
+                            span {
+                                "Location: "
+                                @if post["properties"]["location"][0].is_string() {
+                                    // It's a geo: URL
+                                    // We need to decode it
+                                    a."u-location"[href=post["properties"]["location"][0].as_str().unwrap()] {
+                                        @decode_geo_uri(post["properties"]["location"][0].as_str().unwrap())
+                                    }
+                                } else {
+                                    // It's an inner h-geo object
+                                    a."u-location"[href=post["properties"]["location"][0]["value"].as_str().map(|x| x.to_string()).unwrap_or(format!("geo:{},{}", post["properties"]["location"][0]["properties"]["latitude"][0].as_str().unwrap(), post["properties"]["location"][0]["properties"]["longitude"][0].as_str().unwrap()))] {
+                                        // I'm a lazy bitch
+                                        @decode_geo_uri(&post["properties"]["location"][0]["value"].as_str().map(|x| x.to_string()).unwrap_or(format!("geo:{},{}", post["properties"]["location"][0]["properties"]["latitude"][0].as_str().unwrap(), post["properties"]["location"][0]["properties"]["longitude"][0].as_str().unwrap())))
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                @if post["properties"]["ate"].is_array() || post["properties"]["drank"].is_array() {
+                    div {
+                        @if post["properties"]["ate"].is_array() {
+                            span { ul {
+                                "Ate:"
+                                @for food in post["properties"]["ate"].as_array().unwrap() {
+                                    li {
+                                        @if food.is_string() {
+                                            // If this is a string, it's a URL.
+                                            a."u-ate"[href=food.as_str().unwrap()] {
+                                                @food.as_str().unwrap().truncate_ellipse(24).as_ref()
+                                            }
+                                        } else {
+                                            // This is a rich food object (mm, sounds tasty! I wanna eat something tasty)
+                                            a."u-ate"[href=food["properties"]["uid"][0].as_str().unwrap_or("#")] {
+                                                @food["properties"]["name"][0].as_str()
+                                                    .unwrap_or(food["properties"]["uid"][0].as_str().unwrap_or("#").truncate_ellipse(24).as_ref())
+                                            }
+                                        }
+                                    }
+                                }
+                            } }
+                        }
+                        @if post["properties"]["drank"].is_array() {
+                            span { ul {
+                                "Drank:"
+                                @for food in post["properties"]["drank"].as_array().unwrap() {
+                                    li {
+                                        @if food.is_string() {
+                                            // If this is a string, it's a URL.
+                                            a."u-drank"[href=food.as_str().unwrap()] {
+                                                @food.as_str().unwrap().truncate_ellipse(24).as_ref()
+                                            }
+                                        } else {
+                                            // This is a rich food object (mm, sounds tasty! I wanna eat something tasty)
+                                            a."u-drank"[href=food["properties"]["uid"][0].as_str().unwrap_or("#")] {
+                                                @food["properties"]["name"][0].as_str()
+                                                    .unwrap_or(food["properties"]["uid"][0].as_str().unwrap_or("#").truncate_ellipse(24).as_ref())
+                                            }
+                                        }
+                                    }
+                                }
+                            } }
+                        }
+                    }
+                }
+            }
+            @PhotoGallery { photos: post["properties"]["photo"].as_array() }
+            @if post["properties"]["content"][0]["html"].is_string() {
+                main."e-content" {
+                    @markup::raw(post["properties"]["content"][0]["html"].as_str().unwrap().trim())
+                }
+            }
+            @WebInteractions { post }
+        }
+    }
+    VCard<'a>(card: &'a serde_json::Value) {
+        article."h-card" {
+            @if card["properties"]["photo"][0].is_string() {
+                img."u-photo"[src=card["properties"]["photo"][0].as_str().unwrap()];
+            }
+            h1 {
+                a."u-url"."u-uid"."p-name"[href=card["properties"]["uid"][0].as_str().unwrap()] {
+                    @card["properties"]["name"][0].as_str().unwrap()
+                }
+            }
+            @if card["properties"]["pronoun"].is_array() {
+                span {
+                    "("
+                    @for (i, pronoun) in card["properties"]["pronoun"].as_array().unwrap().iter().filter_map(|v| v.as_str()).enumerate() {
+                        span."p-pronoun" {
+                            @pronoun
+                        }
+                        // Insert commas between multiple sets of pronouns
+                        @if i < (card["properties"]["pronoun"].as_array().unwrap().len() - 1) {", "}
+                    }
+                    ")"
+                }
+            }
+            @if card["properties"]["note"].is_array() {
+                p."p-note" {
+                    @card["properties"]["note"][0]["value"].as_str().unwrap_or_else(|| card["properties"]["note"][0].as_str().unwrap())
+                }
+            }
+            @if card["properties"]["url"].is_array() {
+                ul {
+                    "Can be found elsewhere at:"
+                    @for url in card["properties"]["url"].as_array().unwrap().iter().filter_map(|v| v.as_str()).filter(|v| v != &card["properties"]["uid"][0].as_str().unwrap()).filter(|v| !v.starts_with(&card["properties"]["author"][0].as_str().unwrap())) {
+                        li { a."u-url"[href=url, rel="me"] { @url } }
+                    }
+                }
+            }
+        }
+    }
+    Food<'a>(food: &'a serde_json::Value) {
+        article."h-food" {
+            header.metadata {
+                h1 {
+                    a."p-name"."u-url"[href=food["properties"]["url"][0].as_str().unwrap()] {
+                        @food["properties"]["name"][0].as_str().unwrap()
+                    }
+                }
+            }
+            @PhotoGallery { photos: food["properties"]["photo"].as_array() }
+        }
+    }
+    Feed<'a>(feed: &'a serde_json::Value) {
+        div."h-feed" {
+            div.metadata {
+                @if feed["properties"]["name"][0].is_string() {
+                    h1."p-name".titanic {
+                        a[href=feed["properties"]["uid"][0].as_str().unwrap(), rel="feed"] {
+                            @feed["properties"]["name"][0].as_str().unwrap()
+                        }
+                    }
+                }
+            }
+            @if feed["children"].is_array() {
+                @for child in feed["children"].as_array().unwrap() {
+                    @match child["type"][0].as_str().unwrap() {
+                        "h-entry" => { @Entry { post: child } }
+                        "h-feed" => { @Feed { feed: child } }
+                        "h-food" => { @Food { food: child } }
+                        //"h-event" => { }
+                        "h-card" => { @VCard { card: child } }
+                        something_else => {
+                            p {
+                                "There's supposed to be an "
+                                    @something_else
+                                    " object here. But Kittybox can't render it right now."
+                                    small { "Sorry! TToTT" }
+                            }
+                        }
+                    }
+                }
+            }
+            @if feed["children"].as_array().map(|a| a.len()).unwrap_or(0) == 0 {
+                p {
+                    "Looks like you reached the end. Wanna jump back to the "
+                    a[href=feed["properties"]["uid"][0].as_str().unwrap()] {
+                        "beginning"
+                    } "?"
+                }
+            }
+            @if feed["children"].as_array().map(|a| a.len()).unwrap_or(0) == super::POSTS_PER_PAGE {
+                a[rel="prev", href=feed["properties"]["uid"][0].as_str().unwrap().to_string()
+                    + "?after=" + feed["children"][super::POSTS_PER_PAGE - 1]["properties"]["uid"][0].as_str().unwrap()] {
+                    "Older posts"
+                }
+            }
+        }
+    }
+
+    //=======================================
+    // Components library
+    //=======================================
+    PhotoGallery<'a>(photos: Option<&'a Vec<serde_json::Value>>) {
+        @if let Some(photos) = photos {
+            @for photo in photos.iter() {
+                @if let Some(photo) = photo.as_str() {
+                    img."u-photo"[src=photo, loading="lazy"];
+                } else if photo.is_object() {
+                    @if let Some(thumbnail) = photo["thumbnail"].as_str() {
+                        a."u-photo"[href=photo["value"].as_str().unwrap()] {
+                            img[src=thumbnail,
+                                loading="lazy",
+                                alt=photo["alt"].as_str().unwrap_or("")
+                            ];
+                        }
+                    } else {
+                        img."u-photo"[src=photo["value"].as_str().unwrap(),
+                                      loading="lazy",
+                                      alt=photo["alt"].as_str().unwrap_or("")
+                        ];
+                    }
+                }
+            }
+        }
+    }
+    WebInteractions<'a>(post: &'a serde_json::Value) {
+        footer.webinteractions {
+            ul.counters {
+                li {
+                    span.icon { "❤️" }
+                    span.counter { @post["properties"]["like"].as_array().map(|a| a.len()).unwrap_or(0) }
+                }
+                li {
+                    span.icon { "💬" }
+                    span.counter { @post["properties"]["comment"].as_array().map(|a| a.len()).unwrap_or(0) }
+                }
+                li {
+                    span.icon { "🔄" }
+                    span.counter { @post["properties"]["repost"].as_array().map(|a| a.len()).unwrap_or(0) }
+                }
+                li {
+                    span.icon { "🔖" }
+                    span.counter { @post["properties"]["bookmark"].as_array().map(|a| a.len()).unwrap_or(0) }
+                }
+            }
+            /*@if (
+                post["properties"]["like"].as_array().map(|a| a.len()).unwrap_or(0)
+                    + post["properties"]["bookmark"].as_array().map(|a| a.len()).unwrap_or(0)
+                    + post["properties"]["repost"].as_array().map(|a| a.len()).unwrap_or(0)
+                    + post["properties"]["comment"].as_array().map(|a| a.len()).unwrap_or(0)
+            ) > 0 {
+                details {
+                    summary { "Show comments and reactions" }
+                    // TODO actually render facepiles and comments
+                    @if let Some(likes) = post["properties"]["like"].as_array() {
+                        @if !likes.is_empty() {
+                            // Show a facepile of likes for a post
+                        }
+                    }
+                    @if let Some(bookmarks) = post["properties"]["bookmark"].as_array() {
+                        @if !bookmarks.is_empty() {
+                            // Show a facepile of bookmarks for a post
+                        }
+                    }
+                    @if let Some(reposts) = post["properties"]["repost"].as_array() {
+                        @if !reposts.is_empty() {
+                            // Show a facepile of reposts for a post
+                        }
+                    }
+                    @if let Some(comments) = post["properties"]["comment"].as_array() {
+                        @for comment in comments.iter() {
+                            // Show all the comments recursively (so we could do Salmention with them)
+                        }
+                    }
+                }
+            }*/
+        }
+    }
+}
diff --git a/kittybox-rs/templates/src/templates.rs b/kittybox-rs/templates/src/templates.rs
index 0b1db28..60da6af 100644
--- a/kittybox-rs/templates/src/templates.rs
+++ b/kittybox-rs/templates/src/templates.rs
@@ -1,29 +1,10 @@
-use ellipse::Ellipse;
 use http::StatusCode;
-use kittybox_util::{IndiewebEndpoints, MicropubChannel};
-use log::error;
+use kittybox_util::MicropubChannel;
 
-pub static POSTS_PER_PAGE: usize = 20;
-
-/// Return a pretty location specifier from a geo: URI.
-fn decode_geo_uri(uri: &str) -> String {
-    if let Some(part) = uri.split(':').collect::<Vec<_>>().get(1) {
-        if let Some(part) = part.split(';').next() {
-            let mut parts = part.split(',');
-            let lat = parts.next().unwrap();
-            let lon = parts.next().unwrap();
-            // TODO - format them as proper latitude and longitude
-            return format!("{}, {}", lat, lon);
-        } else {
-            uri.to_string()
-        }
-    } else {
-        uri.to_string()
-    }
-}
+use crate::{Feed, VCard};
 
 markup::define! {
-    Template<'a>(title: &'a str, blog_name: &'a str, endpoints: Option<IndiewebEndpoints>, feeds: Vec<MicropubChannel>, user: Option<String>, content: String) {
+    Template<'a>(title: &'a str, blog_name: &'a str, feeds: Vec<MicropubChannel>, user: Option<String>, content: String) {
         @markup::doctype()
         html {
             head {
@@ -31,20 +12,19 @@ markup::define! {
                 link[rel="preconnect", href="https://fonts.gstatic.com"];
                 link[rel="stylesheet", href="/.kittybox/static/style.css"];
                 meta[name="viewport", content="initial-scale=1, width=device-width"];
-                // Static, because it's built into the server itself
+
                 link[rel="micropub", href="/.kittybox/micropub"];
                 link[rel="micropub_media", href="/.kittybox/media"];
-                // TODO change this once neccesary endpoints are implemented
-                @if let Some(endpoints) = endpoints {
-                    link[rel="authorization_endpoint", href=&endpoints.authorization_endpoint];
-                    link[rel="token_endpoint", href=&endpoints.token_endpoint];
+                link[rel="indieauth_metadata", href="/.kittybox/indieauth/metadata"];
+
+                /*@if let Some(endpoints) = endpoints {
                     @if let Some(webmention) = &endpoints.webmention {
                         link[rel="webmention", href=&webmention];
                     }
                     @if let Some(microsub) = &endpoints.microsub {
                         link[rel="microsub", href=&microsub];
                     }
-                }
+                }*/
             }
             body {
                 // TODO Somehow compress headerbar into a menu when the screen space is tight
@@ -78,384 +58,6 @@ markup::define! {
             }
         }
     }
-    Entry<'a>(post: &'a serde_json::Value) {
-        @if post.pointer("/properties/like-of").is_none() && post.pointer("/properties/bookmark-of").is_none() {
-            @FullEntry { post }
-        } else {
-            // Show a mini-post.
-            @MiniEntry { post }
-        }
-    }
-    MiniEntry<'a>(post: &'a serde_json::Value) {
-        article."h-entry mini-entry" {
-            @if let Some(author) = post["properties"]["author"][0].as_object() {
-                span."mini-h-card"."u-author" {
-                    a."u-author"[href=author["properties"]["uid"][0].as_str().unwrap()] {
-                        @if let Some(photo) = author["properties"]["photo"][0].as_str() {
-                            img[src=photo, loading="lazy"];
-                        }
-                        @author["properties"]["name"][0].as_str().unwrap()
-                    }
-                }
-                @if let Some(likeof) = post["properties"]["like-of"][0].as_str() {
-                    " ❤️ "
-                    a."u-like-of"[href=likeof] { @likeof }
-                } else if let Some(likeof) = post["properties"]["like-of"][0].as_object() {
-                    a."u-like-of"[href=likeof["properties"]["url"][0].as_str().unwrap()] {
-                        @likeof["properties"]["name"][0]
-                            .as_str()
-                            .unwrap_or_else(|| likeof["properties"]["url"][0].as_str().unwrap())
-                    }
-                }
-                @if let Some(bookmarkof) = post["properties"]["bookmark-of"][0].as_str() {
-                    " 🔖 "
-                    a."u-bookmark-of"[href=bookmarkof] { @bookmarkof }
-                } else if let Some(bookmarkof) = post["properties"]["bookmark-of"][0].as_object() {
-                    a."u-bookmark-of"[href=bookmarkof["properties"]["url"][0].as_str().unwrap()] {
-                        @bookmarkof["properties"]["name"][0]
-                            .as_str()
-                            .unwrap_or_else(|| bookmarkof["properties"]["url"][0].as_str().unwrap())
-                    }
-                }
-                @if let Some(published) = post["properties"]["published"][0].as_str() {
-                    time."dt-published"[datetime=published] {
-                        @chrono::DateTime::parse_from_rfc3339(published)
-                            .map(|dt| dt.format("on %a %b %e %T %Y").to_string())
-                            .unwrap_or("sometime in the past".to_string())
-                    }
-                }
-            }
-        }
-    }
-    FullEntry<'a>(post: &'a serde_json::Value) {
-        article."h-entry" {
-            header.metadata {
-                @if let Some(name) = post["properties"]["name"][0].as_str() {
-                    h1."p-name" { @name }
-                }
-                @if let Some(author) = post["properties"]["author"][0].as_object() {
-                    section."mini-h-card" {
-                        a.larger."u-author"[href=author["properties"]["uid"][0].as_str().unwrap()] {
-                            @if let Some(photo) = author["properties"]["photo"][0].as_str() {
-                                img[src=photo, loading="lazy"];
-                            }
-                            @author["properties"]["name"][0].as_str().unwrap()
-                        }
-                    }
-                }
-                div {
-                    span {
-                        a."u-url"."u-uid"[href=post["properties"]["uid"][0].as_str().unwrap()] {
-                            @if let Some(published) = post["properties"]["published"][0].as_str() {
-                                time."dt-published"[datetime=published] {
-                                    @chrono::DateTime::parse_from_rfc3339(published)
-                                        .map(|dt| dt.format("%a %b %e %T %Y").to_string())
-                                        .unwrap_or("sometime in the past".to_string())
-                                }
-                            }
-                        }
-                    }
-                    @if post["properties"]["visibility"][0].as_str().unwrap_or("public") != "public" {
-                        span."p-visibility"[value=post["properties"]["visibility"][0].as_str().unwrap()] {
-                            @post["properties"]["visibility"][0].as_str().unwrap()
-                        }
-                    }
-                    @if post["properties"]["category"].is_array() {
-                        span {
-                            ul.categories {
-                                "Tagged: "
-                                @for cat in post["properties"]["category"].as_array().unwrap() {
-                                    li."p-category" { @cat.as_str().unwrap() }
-                                }
-                            }
-                        }
-                    }
-                    @if post["properties"]["in-reply-to"].is_array() {
-                        // TODO: Rich reply contexts - blocked on MF2 parser
-                        span {
-                            "In reply to: "
-                            ul.replyctx {
-                                @for ctx in post["properties"]["in-reply-to"].as_array().unwrap() {
-                                    li { a."u-in-reply-to"[href=ctx.as_str().unwrap()] {
-                                        @ctx.as_str().unwrap().truncate_ellipse(24).as_ref()
-                                    } }
-                                }
-                            }
-                        }
-                    }
-                }
-                @if post["properties"]["url"].as_array().unwrap().len() > 1 {
-                    hr;
-                    ul {
-                        "Pretty permalinks for this post:"
-                        @for url in post["properties"]["url"].as_array().unwrap().iter().filter(|i| **i != post["properties"]["uid"][0]).map(|i| i.as_str().unwrap()) {
-                            li {
-                                a."u-url"[href=url] { @url }
-                            }
-                        }
-                    }
-                }
-                @if post["properties"]["location"].is_array() || post["properties"]["checkin"].is_array() {
-                    div {
-                        @if post["properties"]["checkin"].is_array() {
-                            span {
-                                "Check-in to: "
-                                @if post["properties"]["checkin"][0].is_string() {
-                                    // It's a URL
-                                    a."u-checkin"[href=post["properties"]["checkin"][0].as_str().unwrap()] {
-                                        @post["properties"]["checkin"][0].as_str().unwrap().truncate_ellipse(24).as_ref()
-                                    }
-                                } else {
-                                    a."u-checkin"[href=post["properties"]["checkin"][0]["properties"]["uid"][0].as_str().unwrap()] {
-                                        @post["properties"]["checkin"][0]["properties"]["name"][0].as_str().unwrap()
-                                    }
-                                }
-                            }
-                        }
-                        @if post["properties"]["location"].is_array() {
-                            span {
-                                "Location: "
-                                @if post["properties"]["location"][0].is_string() {
-                                    // It's a geo: URL
-                                    // We need to decode it
-                                    a."u-location"[href=post["properties"]["location"][0].as_str().unwrap()] {
-                                        @decode_geo_uri(post["properties"]["location"][0].as_str().unwrap())
-                                    }
-                                } else {
-                                    // It's an inner h-geo object
-                                    a."u-location"[href=post["properties"]["location"][0]["value"].as_str().map(|x| x.to_string()).unwrap_or(format!("geo:{},{}", post["properties"]["location"][0]["properties"]["latitude"][0].as_str().unwrap(), post["properties"]["location"][0]["properties"]["longitude"][0].as_str().unwrap()))] {
-                                        // I'm a lazy bitch
-                                        @decode_geo_uri(&post["properties"]["location"][0]["value"].as_str().map(|x| x.to_string()).unwrap_or(format!("geo:{},{}", post["properties"]["location"][0]["properties"]["latitude"][0].as_str().unwrap(), post["properties"]["location"][0]["properties"]["longitude"][0].as_str().unwrap())))
-                                    }
-                                }
-                            }
-                        }
-                    }
-                }
-                @if post["properties"]["ate"].is_array() || post["properties"]["drank"].is_array() {
-                    div {
-                        @if post["properties"]["ate"].is_array() {
-                            span { ul {
-                                "Ate:"
-                                @for food in post["properties"]["ate"].as_array().unwrap() {
-                                    li {
-                                        @if food.is_string() {
-                                            // If this is a string, it's a URL.
-                                            a."u-ate"[href=food.as_str().unwrap()] {
-                                                @food.as_str().unwrap().truncate_ellipse(24).as_ref()
-                                            }
-                                        } else {
-                                            // This is a rich food object (mm, sounds tasty! I wanna eat something tasty)
-                                            a."u-ate"[href=food["properties"]["uid"][0].as_str().unwrap_or("#")] {
-                                                @food["properties"]["name"][0].as_str()
-                                                    .unwrap_or(food["properties"]["uid"][0].as_str().unwrap_or("#").truncate_ellipse(24).as_ref())
-                                            }
-                                        }
-                                    }
-                                }
-                            } }
-                        }
-                        @if post["properties"]["drank"].is_array() {
-                            span { ul {
-                                "Drank:"
-                                @for food in post["properties"]["drank"].as_array().unwrap() {
-                                    li {
-                                        @if food.is_string() {
-                                            // If this is a string, it's a URL.
-                                            a."u-drank"[href=food.as_str().unwrap()] {
-                                                @food.as_str().unwrap().truncate_ellipse(24).as_ref()
-                                            }
-                                        } else {
-                                            // This is a rich food object (mm, sounds tasty! I wanna eat something tasty)
-                                            a."u-drank"[href=food["properties"]["uid"][0].as_str().unwrap_or("#")] {
-                                                @food["properties"]["name"][0].as_str()
-                                                    .unwrap_or(food["properties"]["uid"][0].as_str().unwrap_or("#").truncate_ellipse(24).as_ref())
-                                            }
-                                        }
-                                    }
-                                }
-                            } }
-                        }
-                    }
-                }
-            }
-            @PhotoGallery { photos: post["properties"]["photo"].as_array() }
-            @if post["properties"]["content"][0]["html"].is_string() {
-                main."e-content" {
-                    @markup::raw(post["properties"]["content"][0]["html"].as_str().unwrap().trim())
-                }
-            }
-            @WebInteractions { post }
-        }
-    }
-    PhotoGallery<'a>(photos: Option<&'a Vec<serde_json::Value>>) {
-        @if let Some(photos) = photos {
-            @for photo in photos.iter() {
-                @if let Some(photo) = photo.as_str() {
-                    img."u-photo"[src=photo, loading="lazy"];
-                } else if photo.is_object() {
-                    @if let Some(thumbnail) = photo["thumbnail"].as_str() {
-                        a."u-photo"[href=photo["value"].as_str().unwrap()] {
-                            img[src=thumbnail,
-                                loading="lazy",
-                                alt=photo["alt"].as_str().unwrap_or("")
-                            ];
-                        }
-                    } else {
-                        img."u-photo"[src=photo["value"].as_str().unwrap(),
-                                      loading="lazy",
-                                      alt=photo["alt"].as_str().unwrap_or("")
-                        ];
-                    }
-                }
-            }
-        }
-    }
-    WebInteractions<'a>(post: &'a serde_json::Value) {
-        footer.webinteractions {
-            ul.counters {
-                li {
-                    span.icon { "❤️" }
-                    span.counter { @post["properties"]["like"].as_array().map(|a| a.len()).unwrap_or(0) }
-                }
-                li {
-                    span.icon { "💬" }
-                    span.counter { @post["properties"]["comment"].as_array().map(|a| a.len()).unwrap_or(0) }
-                }
-                li {
-                    span.icon { "🔄" }
-                    span.counter { @post["properties"]["repost"].as_array().map(|a| a.len()).unwrap_or(0) }
-                }
-                li {
-                    span.icon { "🔖" }
-                    span.counter { @post["properties"]["bookmark"].as_array().map(|a| a.len()).unwrap_or(0) }
-                }
-            }
-            /*@if (
-                post["properties"]["like"].as_array().map(|a| a.len()).unwrap_or(0)
-                    + post["properties"]["bookmark"].as_array().map(|a| a.len()).unwrap_or(0)
-                    + post["properties"]["repost"].as_array().map(|a| a.len()).unwrap_or(0)
-                    + post["properties"]["comment"].as_array().map(|a| a.len()).unwrap_or(0)
-            ) > 0 {
-                details {
-                    summary { "Show comments and reactions" }
-                    // TODO actually render facepiles and comments
-                    @if let Some(likes) = post["properties"]["like"].as_array() {
-                        @if !likes.is_empty() {
-                            // Show a facepile of likes for a post
-                        }
-                    }
-                    @if let Some(bookmarks) = post["properties"]["bookmark"].as_array() {
-                        @if !bookmarks.is_empty() {
-                            // Show a facepile of bookmarks for a post
-                        }
-                    }
-                    @if let Some(reposts) = post["properties"]["repost"].as_array() {
-                        @if !reposts.is_empty() {
-                            // Show a facepile of reposts for a post
-                        }
-                    }
-                    @if let Some(comments) = post["properties"]["comment"].as_array() {
-                        @for comment in comments.iter() {
-                            // Show all the comments recursively (so we could do Salmention with them)
-                        }
-                    }
-                }
-            }*/
-        }
-    }
-    VCard<'a>(card: &'a serde_json::Value) {
-        article."h-card" {
-            @if card["properties"]["photo"][0].is_string() {
-                img."u-photo"[src=card["properties"]["photo"][0].as_str().unwrap()];
-            }
-            h1 {
-                a."u-url"."u-uid"."p-name"[href=card["properties"]["uid"][0].as_str().unwrap()] {
-                    @card["properties"]["name"][0].as_str().unwrap()
-                }
-            }
-            @if card["properties"]["pronoun"].is_array() {
-                span {
-                    "("
-                    @for (i, pronoun) in card["properties"]["pronoun"].as_array().unwrap().iter().filter_map(|v| v.as_str()).enumerate() {
-                        span."p-pronoun" {
-                            @pronoun
-                        }
-                        // Insert commas between multiple sets of pronouns
-                        @if i < (card["properties"]["pronoun"].as_array().unwrap().len() - 1) {", "}
-                    }
-                    ")"
-                }
-            }
-            @if card["properties"]["note"].is_array() {
-                p."p-note" {
-                    @card["properties"]["note"][0]["value"].as_str().unwrap_or_else(|| card["properties"]["note"][0].as_str().unwrap())
-                }
-            }
-            @if card["properties"]["url"].is_array() {
-                ul {
-                    "Can be found elsewhere at:"
-                    @for url in card["properties"]["url"].as_array().unwrap().iter().filter_map(|v| v.as_str()).filter(|v| v != &card["properties"]["uid"][0].as_str().unwrap()).filter(|v| !v.starts_with(&card["properties"]["author"][0].as_str().unwrap())) {
-                        li { a."u-url"[href=url, rel="me"] { @url } }
-                    }
-                }
-            }
-        }
-    }
-    Food<'a>(food: &'a serde_json::Value) {
-        article."h-food" {
-            header.metadata {
-                h1 {
-                    a."p-name"."u-url"[href=food["properties"]["url"][0].as_str().unwrap()] {
-                        @food["properties"]["name"][0].as_str().unwrap()
-                    }
-                }
-            }
-            @PhotoGallery { photos: food["properties"]["photo"].as_array() }
-        }
-    }
-    Feed<'a>(feed: &'a serde_json::Value) {
-        div."h-feed" {
-            div.metadata {
-                @if feed["properties"]["name"][0].is_string() {
-                    h1."p-name".titanic {
-                        a[href=feed["properties"]["uid"][0].as_str().unwrap(), rel="feed"] {
-                            @feed["properties"]["name"][0].as_str().unwrap()
-                        }
-                    }
-                }
-            }
-            @if feed["children"].is_array() {
-                @for child in feed["children"].as_array().unwrap() {
-                    @match child["type"][0].as_str().unwrap() {
-                        "h-entry" => { @Entry { post: child } }
-                        "h-feed" => { @Feed { feed: child } }
-                        "h-event" => {
-                            @{error!("Templating error: h-events aren't implemented yet");}
-                        }
-                        "h-card" => { @VCard { card: child }}
-                        something_else => {
-                            @{error!("Templating error: found a {} object that couldn't be parsed", something_else);}
-                        }
-                    }
-                }
-            }
-            @if feed["children"].as_array().map(|a| a.len()).unwrap_or(0) == 0 {
-                p {
-                    "Looks like you reached the end. Wanna jump back to the "
-                    a[href=feed["properties"]["uid"][0].as_str().unwrap()] {
-                        "beginning"
-                    } "?"
-                }
-            }
-            @if feed["children"].as_array().map(|a| a.len()).unwrap_or(0) == super::POSTS_PER_PAGE {
-                a[rel="prev", href=feed["properties"]["uid"][0].as_str().unwrap().to_string()
-                    + "?after=" + feed["children"][super::POSTS_PER_PAGE - 1]["properties"]["uid"][0].as_str().unwrap()] {
-                    "Older posts"
-                }
-            }
-        }
-    }
     MainPage<'a>(feed: &'a serde_json::Value, card: &'a serde_json::Value) {
         .sidebyside {
             @VCard { card }