Compare commits
No commits in common. "82ccad196c4d42940fb8be79bd7115ceee43f236" and "5112a6ce35b1693c287c870dcf90316f89b5f498" have entirely different histories.
82ccad196c
...
5112a6ce35
30 changed files with 20 additions and 834 deletions
388
Cargo.lock
generated
388
Cargo.lock
generated
|
|
@ -213,21 +213,6 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-range"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8b09d24c2cfcf6596afc4b9d139ad62c53637c7e0f791ef8a25ce1cc431f73a"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"bytes",
|
||||
"futures",
|
||||
"http-body",
|
||||
"pin-project",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
|
|
@ -279,16 +264,6 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
|
|
@ -349,28 +324,6 @@ dependencies = [
|
|||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build",
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz-build"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.2"
|
||||
|
|
@ -591,12 +544,6 @@ dependencies = [
|
|||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deunicode"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc55fe0d1f6c107595572ec8b107c0999bb1a2e0b75e37429a4fb0d6474a0e7d"
|
||||
|
||||
[[package]]
|
||||
name = "diesel"
|
||||
version = "2.2.7"
|
||||
|
|
@ -708,20 +655,6 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
|
|
@ -729,7 +662,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -738,18 +670,6 @@ version = "0.3.31"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
|
|
@ -762,15 +682,10 @@ version = "0.3.31"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -800,30 +715,6 @@ version = "0.31.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globwalk"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"ignore",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpodder"
|
||||
version = "0.1.0"
|
||||
|
|
@ -945,15 +836,6 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "humansize"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.6.0"
|
||||
|
|
@ -1018,22 +900,6 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.7.1"
|
||||
|
|
@ -1104,12 +970,6 @@ version = "0.2.170"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.31.0"
|
||||
|
|
@ -1248,7 +1108,6 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"axum-range",
|
||||
"chrono",
|
||||
"clap",
|
||||
"cookie",
|
||||
|
|
@ -1258,7 +1117,6 @@ dependencies = [
|
|||
"http-body-util",
|
||||
"rand",
|
||||
"serde",
|
||||
"tera",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
|
|
@ -1294,15 +1152,6 @@ dependencies = [
|
|||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.5.0"
|
||||
|
|
@ -1343,109 +1192,6 @@ version = "2.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
|
|
@ -1753,17 +1499,6 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
|
|
@ -1788,31 +1523,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slug"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724"
|
||||
dependencies = [
|
||||
"deunicode",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.14.0"
|
||||
|
|
@ -1858,48 +1568,6 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||
|
||||
[[package]]
|
||||
name = "tera"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"globwalk",
|
||||
"humansize",
|
||||
"lazy_static",
|
||||
"percent-encoding",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"rand",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slug",
|
||||
"unic-segment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
|
|
@ -2122,12 +1790,6 @@ version = "1.18.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
|
||||
|
||||
[[package]]
|
||||
name = "uncased"
|
||||
version = "0.9.10"
|
||||
|
|
@ -2137,56 +1799,6 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-char-property"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
|
||||
dependencies = [
|
||||
"unic-char-range",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-char-range"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
|
||||
|
||||
[[package]]
|
||||
name = "unic-common"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
|
||||
|
||||
[[package]]
|
||||
name = "unic-segment"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
|
||||
dependencies = [
|
||||
"unic-ucd-segment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-ucd-segment"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
|
||||
dependencies = [
|
||||
"unic-char-property",
|
||||
"unic-char-range",
|
||||
"unic-ucd-version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-ucd-version"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
|
||||
dependencies = [
|
||||
"unic-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.17"
|
||||
|
|
|
|||
|
|
@ -26,5 +26,3 @@ tokio = { version = "1.43.0", features = ["full"] }
|
|||
tower-http = { version = "0.6.2", features = ["set-header", "trace"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
tera = "1.20.0"
|
||||
axum-range = "0.5.0"
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ pub struct EpisodeAction {
|
|||
pub struct Session {
|
||||
pub id: i64,
|
||||
pub last_seen: DateTime<Utc>,
|
||||
pub user_agent: Option<String>,
|
||||
pub user: User,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,16 +71,11 @@ impl GpodderRepository {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn create_session(
|
||||
&self,
|
||||
user: &models::User,
|
||||
user_agent: Option<String>,
|
||||
) -> Result<models::Session, AuthErr> {
|
||||
pub fn create_session(&self, user: &models::User) -> Result<models::Session, AuthErr> {
|
||||
let session = models::Session {
|
||||
id: rand::thread_rng().gen(),
|
||||
last_seen: Utc::now(),
|
||||
user: user.clone(),
|
||||
user_agent,
|
||||
};
|
||||
|
||||
self.store.insert_session(&session)?;
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
alter table sessions
|
||||
drop column user_agent;
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
alter table sessions
|
||||
add column user_agent text;
|
||||
|
|
@ -10,7 +10,6 @@ pub struct Session {
|
|||
pub id: i64,
|
||||
pub user_id: i64,
|
||||
pub last_seen: i64,
|
||||
pub user_agent: Option<String>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ impl gpodder::AuthStore for SqliteRepository {
|
|||
id: session.id,
|
||||
last_seen: DateTime::from_timestamp(session.last_seen, 0).unwrap(),
|
||||
user: user.into(),
|
||||
user_agent: session.user_agent.clone(),
|
||||
})),
|
||||
Ok(None) => Ok(None),
|
||||
Err(err) => Err(DbError::from(err).into()),
|
||||
|
|
@ -80,7 +79,6 @@ impl gpodder::AuthStore for SqliteRepository {
|
|||
id: session.id,
|
||||
user_id: session.user.id,
|
||||
last_seen: session.last_seen.timestamp(),
|
||||
user_agent: session.user_agent.clone(),
|
||||
}
|
||||
.insert_into(sessions::table)
|
||||
.execute(&mut self.pool.get().map_err(DbError::from)?)
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ diesel::table! {
|
|||
id -> BigInt,
|
||||
user_id -> BigInt,
|
||||
last_seen -> BigInt,
|
||||
user_agent -> Nullable<Text>,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
use std::{sync::Arc, time::Duration};
|
||||
use std::time::Duration;
|
||||
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
use crate::server;
|
||||
|
||||
|
|
@ -9,17 +11,12 @@ pub fn serve(config: &crate::config::Config) -> u8 {
|
|||
|
||||
tracing::info!("Initializing database and running migrations");
|
||||
|
||||
// TODO remove unwraps
|
||||
let store =
|
||||
gpodder_sqlite::SqliteRepository::from_path(config.data_dir.join(crate::DB_FILENAME))
|
||||
.unwrap();
|
||||
let tera = crate::web::initialize_tera().unwrap();
|
||||
let store = gpodder::GpodderRepository::new(store);
|
||||
|
||||
let ctx = server::Context {
|
||||
store,
|
||||
tera: Arc::new(tera),
|
||||
};
|
||||
let ctx = server::Context { store };
|
||||
let app = server::app(ctx.clone());
|
||||
|
||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
mod cli;
|
||||
mod config;
|
||||
mod server;
|
||||
mod web;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ pub type AppResult<T> = Result<T, AppError>;
|
|||
pub enum AppError {
|
||||
// Db(db::DbError),
|
||||
IO(std::io::Error),
|
||||
Tera(tera::Error),
|
||||
Other(Box<dyn std::error::Error + 'static + Send + Sync>),
|
||||
BadRequest,
|
||||
Unauthorized,
|
||||
|
|
@ -22,7 +21,6 @@ impl fmt::Display for AppError {
|
|||
match self {
|
||||
// Self::Db(_) => write!(f, "database error"),
|
||||
Self::IO(_) => write!(f, "io error"),
|
||||
Self::Tera(_) => write!(f, "tera error"),
|
||||
Self::Other(_) => write!(f, "other error"),
|
||||
Self::BadRequest => write!(f, "bad request"),
|
||||
Self::Unauthorized => write!(f, "unauthorized"),
|
||||
|
|
@ -35,7 +33,6 @@ impl std::error::Error for AppError {
|
|||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
// Self::Db(err) => Some(err),
|
||||
Self::Tera(err) => Some(err),
|
||||
Self::IO(err) => Some(err),
|
||||
Self::Other(err) => Some(err.as_ref()),
|
||||
Self::NotFound | Self::Unauthorized | Self::BadRequest => None,
|
||||
|
|
@ -49,12 +46,6 @@ impl From<std::io::Error> for AppError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<tera::Error> for AppError {
|
||||
fn from(value: tera::Error) -> Self {
|
||||
Self::Tera(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
match self {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ use axum::{
|
|||
};
|
||||
use axum_extra::{
|
||||
extract::{cookie::Cookie, CookieJar},
|
||||
headers::{authorization::Basic, Authorization, UserAgent},
|
||||
headers::{authorization::Basic, Authorization},
|
||||
TypedHeader,
|
||||
};
|
||||
use cookie::time::Duration;
|
||||
use gpodder::AuthErr;
|
||||
|
||||
use crate::server::{
|
||||
error::{AppError, AppResult},
|
||||
|
|
@ -28,7 +27,6 @@ async fn post_login(
|
|||
Path(username): Path<String>,
|
||||
jar: CookieJar,
|
||||
TypedHeader(auth): TypedHeader<Authorization<Basic>>,
|
||||
user_agent: Option<TypedHeader<UserAgent>>,
|
||||
) -> AppResult<CookieJar> {
|
||||
// These should be the same according to the spec
|
||||
if username != auth.username() {
|
||||
|
|
@ -64,11 +62,7 @@ async fn post_login(
|
|||
let user = ctx
|
||||
.store
|
||||
.validate_credentials(auth.username(), auth.password())?;
|
||||
|
||||
let user_agent = user_agent.map(|header| header.to_string());
|
||||
let session = ctx.store.create_session(&user, user_agent)?;
|
||||
|
||||
Ok::<_, AuthErr>(session)
|
||||
ctx.store.create_session(&user)
|
||||
})
|
||||
.await
|
||||
.unwrap()?;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use axum::{
|
|||
use crate::server::{
|
||||
error::{AppError, AppResult},
|
||||
gpodder::{
|
||||
auth_api_middleware,
|
||||
auth_middleware,
|
||||
format::{Format, StringWithFormat},
|
||||
models,
|
||||
},
|
||||
|
|
@ -19,7 +19,7 @@ pub fn router(ctx: Context) -> Router<Context> {
|
|||
Router::new()
|
||||
.route("/{username}", get(get_devices))
|
||||
.route("/{username}/{id}", post(post_device))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware))
|
||||
}
|
||||
|
||||
async fn get_devices(
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::server::{
|
||||
error::{AppError, AppResult},
|
||||
gpodder::{
|
||||
auth_api_middleware,
|
||||
auth_middleware,
|
||||
format::{Format, StringWithFormat},
|
||||
models,
|
||||
models::UpdatedUrlsResponse,
|
||||
|
|
@ -24,7 +24,7 @@ pub fn router(ctx: Context) -> Router<Context> {
|
|||
"/{username}",
|
||||
post(post_episode_actions).get(get_episode_actions),
|
||||
)
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware))
|
||||
}
|
||||
|
||||
async fn post_episode_actions(
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use serde::Deserialize;
|
|||
use crate::server::{
|
||||
error::{AppError, AppResult},
|
||||
gpodder::{
|
||||
auth_api_middleware,
|
||||
auth_middleware,
|
||||
format::{Format, StringWithFormat},
|
||||
models::{SubscriptionDelta, SubscriptionDeltaResponse, UpdatedUrlsResponse},
|
||||
},
|
||||
|
|
@ -22,7 +22,7 @@ pub fn router(ctx: Context) -> Router<Context> {
|
|||
"/{username}/{id}",
|
||||
post(post_subscription_changes).get(get_subscription_changes),
|
||||
)
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware))
|
||||
}
|
||||
|
||||
pub async fn post_subscription_changes(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use axum::{
|
|||
use crate::server::{
|
||||
error::{AppError, AppResult},
|
||||
gpodder::{
|
||||
auth_api_middleware,
|
||||
auth_middleware,
|
||||
format::{Format, StringWithFormat},
|
||||
models::{SyncStatus, SyncStatusDelta},
|
||||
},
|
||||
|
|
@ -21,7 +21,7 @@ pub fn router(ctx: Context) -> Router<Context> {
|
|||
"/{username}",
|
||||
get(get_sync_status).post(post_sync_status_changes),
|
||||
)
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware))
|
||||
}
|
||||
|
||||
pub async fn get_sync_status(
|
||||
|
|
|
|||
|
|
@ -36,13 +36,8 @@ pub fn router(ctx: Context) -> Router<Context> {
|
|||
))
|
||||
}
|
||||
|
||||
/// Middleware that can authenticate both with session cookies and basic auth. If basic auth is
|
||||
/// used, no session is created. If authentication fails, the server returns a 401.
|
||||
pub async fn auth_api_middleware(
|
||||
State(ctx): State<Context>,
|
||||
mut req: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
/// This middleware accepts
|
||||
pub async fn auth_middleware(State(ctx): State<Context>, mut req: Request, next: Next) -> Response {
|
||||
// SAFETY: this extractor's error type is Infallible
|
||||
let mut jar: CookieJar = req.extract_parts().await.unwrap();
|
||||
let mut auth_user = None;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use axum::{
|
|||
|
||||
use crate::server::{
|
||||
error::{AppError, AppResult},
|
||||
gpodder::{auth_api_middleware, format::StringWithFormat},
|
||||
gpodder::{auth_middleware, format::StringWithFormat},
|
||||
Context,
|
||||
};
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ pub fn router(ctx: Context) -> Router<Context> {
|
|||
get(get_device_subscriptions).put(put_device_subscriptions),
|
||||
)
|
||||
.route("/{username}", get(get_user_subscriptions))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
|
||||
.layer(middleware::from_fn_with_state(ctx.clone(), auth_middleware))
|
||||
}
|
||||
|
||||
pub async fn get_device_subscriptions(
|
||||
|
|
|
|||
|
|
@ -1,17 +1,12 @@
|
|||
mod error;
|
||||
mod gpodder;
|
||||
mod r#static;
|
||||
mod web;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::Request,
|
||||
http::StatusCode,
|
||||
middleware::Next,
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
routing::get,
|
||||
response::{IntoResponse, Response},
|
||||
Router,
|
||||
};
|
||||
use http_body_util::BodyExt;
|
||||
|
|
@ -20,15 +15,11 @@ use tower_http::trace::TraceLayer;
|
|||
#[derive(Clone)]
|
||||
pub struct Context {
|
||||
pub store: ::gpodder::GpodderRepository,
|
||||
pub tera: Arc<tera::Tera>,
|
||||
}
|
||||
|
||||
pub fn app(ctx: Context) -> Router {
|
||||
Router::new()
|
||||
.merge(gpodder::router(ctx.clone()))
|
||||
.nest("/static", r#static::router())
|
||||
.nest("/_", web::router(ctx.clone()))
|
||||
.route("/", get(|| async { Redirect::to("/_") }))
|
||||
.layer(axum::middleware::from_fn(header_logger))
|
||||
.layer(axum::middleware::from_fn(body_logger))
|
||||
.layer(TraceLayer::new_for_http())
|
||||
|
|
|
|||
1
src/server/static/htmx_2.0.4.min.js
vendored
1
src/server/static/htmx_2.0.4.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,34 +0,0 @@
|
|||
use std::io::Cursor;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
use axum_extra::{headers::Range, TypedHeader};
|
||||
use axum_range::{KnownSize, Ranged};
|
||||
|
||||
use super::Context;
|
||||
|
||||
const HTMX: &str = include_str!("./htmx_2.0.4.min.js");
|
||||
const PICOCSS: &str = include_str!("./pico_2.1.1.classless.jade.min.css");
|
||||
|
||||
type RangedResponse = Ranged<KnownSize<Cursor<&'static str>>>;
|
||||
|
||||
pub fn router() -> Router<Context> {
|
||||
Router::new()
|
||||
.route("/htmx_2.0.4.min.js", get(get_htmx))
|
||||
.route("/pico_2.1.1.classless.jade.min.css", get(get_picocss))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn serve_static(data: &'static str, range: Option<Range>) -> RangedResponse {
|
||||
let cursor = Cursor::new(data);
|
||||
let body = KnownSize::sized(cursor, data.len() as u64);
|
||||
|
||||
Ranged::new(range, body)
|
||||
}
|
||||
|
||||
async fn get_htmx(range: Option<TypedHeader<Range>>) -> RangedResponse {
|
||||
serve_static(HTMX, range.map(|TypedHeader(range)| range))
|
||||
}
|
||||
|
||||
async fn get_picocss(range: Option<TypedHeader<Range>>) -> RangedResponse {
|
||||
serve_static(PICOCSS, range.map(|TypedHeader(range)| range))
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,143 +0,0 @@
|
|||
use axum::{
|
||||
extract::{Request, State},
|
||||
http::HeaderMap,
|
||||
middleware::{self, Next},
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
routing::get,
|
||||
Form, RequestExt, Router,
|
||||
};
|
||||
use axum_extra::{extract::CookieJar, headers::UserAgent, TypedHeader};
|
||||
use cookie::{time::Duration, Cookie};
|
||||
use gpodder::{AuthErr, Session};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::web::{Page, TemplateExt, TemplateResponse, View};
|
||||
|
||||
use super::{
|
||||
error::{AppError, AppResult},
|
||||
Context,
|
||||
};
|
||||
|
||||
const SESSION_ID_COOKIE: &str = "sessionid";
|
||||
|
||||
pub fn router(ctx: Context) -> Router<Context> {
|
||||
Router::new()
|
||||
.route("/", get(get_index))
|
||||
.layer(middleware::from_fn_with_state(
|
||||
ctx.clone(),
|
||||
auth_web_middleware,
|
||||
))
|
||||
// Login route needs to be handled differently, as the middleware turns it into a redirect
|
||||
// loop
|
||||
.route("/login", get(get_login).post(post_login))
|
||||
}
|
||||
|
||||
async fn get_index(State(ctx): State<Context>, headers: HeaderMap) -> TemplateResponse<Page<View>> {
|
||||
View::Index.page(&headers).response(&ctx.tera)
|
||||
}
|
||||
|
||||
async fn get_login(State(ctx): State<Context>, headers: HeaderMap, jar: CookieJar) -> Response {
|
||||
if extract_session(ctx.clone(), &jar)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.is_some()
|
||||
{
|
||||
Redirect::to("/_").into_response()
|
||||
} else {
|
||||
View::Login
|
||||
.page(&headers)
|
||||
.response(&ctx.tera)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LoginForm {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
async fn post_login(
|
||||
State(ctx): State<Context>,
|
||||
user_agent: Option<TypedHeader<UserAgent>>,
|
||||
_headers: HeaderMap,
|
||||
jar: CookieJar,
|
||||
Form(login): Form<LoginForm>,
|
||||
) -> AppResult<Response> {
|
||||
match tokio::task::spawn_blocking(move || {
|
||||
let user = ctx
|
||||
.store
|
||||
.validate_credentials(&login.username, &login.password)?;
|
||||
|
||||
let user_agent = user_agent.map(|header| header.to_string());
|
||||
let session = ctx.store.create_session(&user, user_agent)?;
|
||||
|
||||
Ok::<_, AuthErr>(session)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
Ok(session) => Ok((
|
||||
jar.add(
|
||||
Cookie::build((SESSION_ID_COOKIE, session.id.to_string()))
|
||||
.secure(true)
|
||||
.same_site(cookie::SameSite::Lax)
|
||||
.http_only(true)
|
||||
.path("/")
|
||||
.max_age(Duration::days(365)),
|
||||
),
|
||||
Redirect::to("/_"),
|
||||
)
|
||||
.into_response()),
|
||||
Err(AuthErr::UnknownUser | AuthErr::InvalidPassword) => {
|
||||
todo!("serve login form with error messages")
|
||||
}
|
||||
Err(err) => Err(AppError::from(err)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn extract_session(ctx: Context, jar: &CookieJar) -> AppResult<Option<Session>> {
|
||||
if let Some(session_id) = jar
|
||||
.get(SESSION_ID_COOKIE)
|
||||
.and_then(|c| c.value().parse::<i64>().ok())
|
||||
{
|
||||
match tokio::task::spawn_blocking(move || {
|
||||
let session = ctx.store.get_session(session_id)?;
|
||||
ctx.store.refresh_session(&session)?;
|
||||
|
||||
Ok(session)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
Ok(session) => Ok(Some(session)),
|
||||
Err(gpodder::AuthErr::UnknownSession) => Ok(None),
|
||||
Err(err) => Err(AppError::from(err)),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Middleware that authenticates the current user via the session token. If the credentials are
|
||||
/// invalid, the user is redirected to the login page.
|
||||
pub async fn auth_web_middleware(
|
||||
State(ctx): State<Context>,
|
||||
mut req: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
// SAFETY: this extractor's error type is Infallible
|
||||
let jar: CookieJar = req.extract_parts().await.unwrap();
|
||||
let redirect = Redirect::to("/_/login");
|
||||
|
||||
match extract_session(ctx, &jar).await {
|
||||
Ok(Some(session)) => {
|
||||
req.extensions_mut().insert(session.user);
|
||||
|
||||
next.run(req).await
|
||||
}
|
||||
Ok(None) => redirect.into_response(),
|
||||
Err(err) => err.into_response(),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
mod page;
|
||||
mod view;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
body::Body,
|
||||
http::{HeaderMap, Response, StatusCode},
|
||||
response::{Html, IntoResponse},
|
||||
};
|
||||
|
||||
pub use page::Page;
|
||||
pub use view::View;
|
||||
|
||||
const BASE_TEMPLATE: &str = "base.html";
|
||||
|
||||
/// Trait defining shared methods for working with typed Tera templates
|
||||
pub trait Template {
|
||||
/// Returns the name or path used to identify the template in the Tera struct
|
||||
fn template(&self) -> &'static str;
|
||||
|
||||
/// Render the template using the given Tera instance.
|
||||
///
|
||||
/// Templates are expected to manage their own context requirements if needed.
|
||||
fn render(&self, tera: &tera::Tera) -> tera::Result<String>;
|
||||
}
|
||||
|
||||
/// Useful additional functions on sized Template implementors
|
||||
pub trait TemplateExt: Sized + Template {
|
||||
fn response(self, tera: &Arc<tera::Tera>) -> TemplateResponse<Self> {
|
||||
TemplateResponse::new(tera, self)
|
||||
}
|
||||
|
||||
fn page(self, headers: &HeaderMap) -> Page<Self> {
|
||||
Page::new(self).headers(headers)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sized + Template> TemplateExt for T {}
|
||||
|
||||
/// A specific instance of a template. This type can be used as a return type from Axum handlers.
|
||||
pub struct TemplateResponse<T> {
|
||||
tera: Arc<tera::Tera>,
|
||||
template: T,
|
||||
}
|
||||
|
||||
impl<T> TemplateResponse<T> {
|
||||
pub fn new(tera: &Arc<tera::Tera>, template: T) -> Self {
|
||||
Self {
|
||||
tera: Arc::clone(&tera),
|
||||
template,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Template> IntoResponse for TemplateResponse<T> {
|
||||
fn into_response(self) -> Response<Body> {
|
||||
match self.template.render(&self.tera) {
|
||||
Ok(s) => Html(s).into_response(),
|
||||
Err(err) => {
|
||||
tracing::error!("tera template failed: {err}");
|
||||
|
||||
StatusCode::INTERNAL_SERVER_ERROR.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialize_tera() -> tera::Result<tera::Tera> {
|
||||
let mut tera = tera::Tera::default();
|
||||
|
||||
tera.add_raw_templates([
|
||||
(BASE_TEMPLATE, include_str!("templates/base.html")),
|
||||
(
|
||||
View::Index.template(),
|
||||
include_str!("templates/views/index.html"),
|
||||
),
|
||||
(
|
||||
View::Login.template(),
|
||||
include_str!("templates/views/login.html"),
|
||||
),
|
||||
])?;
|
||||
|
||||
Ok(tera)
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
use axum::http::{HeaderMap, HeaderValue};
|
||||
|
||||
use super::Template;
|
||||
|
||||
const HX_REQUEST_HEADER: &str = "HX-Request";
|
||||
const HX_HISTORY_RESTORE_HEADER: &str = "HX-History-Restore-Request";
|
||||
|
||||
/// Overarching template type that conditionally wraps its inner template with the base template if
|
||||
/// required, as derived from the request headers
|
||||
pub struct Page<T> {
|
||||
template: T,
|
||||
wrap_with_base: bool,
|
||||
}
|
||||
|
||||
impl<T: Template> Template for Page<T> {
|
||||
fn template(&self) -> &'static str {
|
||||
self.template.template()
|
||||
}
|
||||
|
||||
fn render(&self, tera: &tera::Tera) -> tera::Result<String> {
|
||||
let inner = self.template.render(tera)?;
|
||||
|
||||
if self.wrap_with_base {
|
||||
let mut ctx = tera::Context::new();
|
||||
ctx.insert("inner", &inner);
|
||||
|
||||
tera.render(super::BASE_TEMPLATE, &ctx)
|
||||
} else {
|
||||
Ok(inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Page<T> {
|
||||
pub fn new(template: T) -> Self {
|
||||
Self {
|
||||
template,
|
||||
wrap_with_base: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn headers(mut self, headers: &HeaderMap) -> Self {
|
||||
let is_htmx_req = headers.get(HX_REQUEST_HEADER).is_some();
|
||||
let is_hist_restore_req = headers
|
||||
.get(HX_HISTORY_RESTORE_HEADER)
|
||||
.map(|val| val == HeaderValue::from_static("true"))
|
||||
.unwrap_or(false);
|
||||
|
||||
self.wrap_with_base = !is_htmx_req || is_hist_restore_req;
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<script src="/static/htmx_2.0.4.min.js" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+"></script>
|
||||
<link rel="stylesheet" href="/static/pico_2.1.1.classless.jade.min.css" />
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<style type="text/css">
|
||||
a:hover {
|
||||
cursor:pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<nav>
|
||||
</nav>
|
||||
<article id="inner">
|
||||
{{ inner | safe }}
|
||||
</article>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<h1>Otter</h1>
|
||||
|
||||
Otter is a self-hostable Gpodder implementation.
|
||||
|
||||
If you're seeing this, you're logged in.
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<article>
|
||||
<form hx-post="/_/login" hx-target="#inner">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password">
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
</article>
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
use super::Template;
|
||||
|
||||
pub enum View {
|
||||
Index,
|
||||
Login,
|
||||
}
|
||||
|
||||
impl Template for View {
|
||||
fn template(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Index => "views/index.html",
|
||||
Self::Login => "views/login.html",
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self, tera: &tera::Tera) -> tera::Result<String> {
|
||||
tera.render(self.template(), &tera::Context::new())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue