diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3df3de1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +root = true + +[*.html.tera] +indent_style = space +indent_size = 2 diff --git a/Cargo.lock b/Cargo.lock index ce808cd..6d423ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -181,6 +181,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.0" @@ -196,6 +202,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -256,6 +272,28 @@ dependencies = [ "windows-link", ] +[[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 = "cipher" version = "0.4.4" @@ -341,6 +379,40 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -490,6 +562,12 @@ dependencies = [ "syn", ] +[[package]] +name = "deunicode" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc55fe0d1f6c107595572ec8b107c0999bb1a2e0b75e37429a4fb0d6474a0e7d" + [[package]] name = "devise" version = "0.4.2" @@ -516,7 +594,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" dependencies = [ - "bitflags", + "bitflags 2.9.0", "proc-macro2", "proc-macro2-diagnostics", "quote", @@ -711,6 +789,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + [[package]] name = "fnv" version = "1.0.7" @@ -741,6 +831,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futf" version = "0.1.5" @@ -922,6 +1021,30 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[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 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags 2.9.0", + "ignore", + "walkdir", +] + [[package]] name = "group" version = "0.13.0" @@ -1113,6 +1236,15 @@ 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 = "0.14.32" @@ -1380,6 +1512,22 @@ dependencies = [ "icu_properties", ] +[[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 0.4.9", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1408,6 +1556,26 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "inout" version = "0.1.4" @@ -1481,6 +1649,26 @@ dependencies = [ "serde_json", ] +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1502,6 +1690,17 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.0", + "libc", + "redox_syscall", +] + [[package]] name = "linux-raw-sys" version = "0.9.3" @@ -1678,6 +1877,18 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.3" @@ -1740,6 +1951,34 @@ dependencies = [ "memchr", ] +[[package]] +name = "normpath" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.9.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio 0.8.11", + "walkdir", + "windows-sys 0.48.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1891,7 +2130,7 @@ version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ - "bitflags", + "bitflags 2.9.0", "cfg-if", "foreign-types", "libc", @@ -1991,6 +2230,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + [[package]] name = "pear" version = "0.2.9" @@ -2029,6 +2277,51 @@ 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 2.0.12", + "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" @@ -2335,7 +2628,7 @@ version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] @@ -2534,6 +2827,19 @@ dependencies = [ "version_check", ] +[[package]] +name = "rocket_dyn_templates" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bbab919c9e67df3f7ac6624a32ef897df4cd61c0969f4d66f3ced0534660d7a" +dependencies = [ + "normpath", + "notify", + "rocket", + "tera", + "walkdir", +] + [[package]] name = "rocket_http" version = "0.5.1" @@ -2608,7 +2914,7 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ - "bitflags", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys", @@ -2670,6 +2976,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.27" @@ -2725,7 +3040,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.9.0", "core-foundation", "core-foundation-sys", "libc", @@ -2756,6 +3071,7 @@ dependencies = [ "rand 0.9.0", "reqwest", "rocket", + "rocket_dyn_templates", "scraper", "serde", "thiserror 2.0.12", @@ -2769,7 +3085,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" dependencies = [ - "bitflags", + "bitflags 2.9.0", "cssparser", "derive_more", "fxhash", @@ -2969,6 +3285,16 @@ 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" @@ -3122,7 +3448,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags", + "bitflags 2.9.0", "core-foundation", "system-configuration-sys", ] @@ -3161,6 +3487,28 @@ dependencies = [ "utf-8", ] +[[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 0.8.5", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -3276,7 +3624,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.3", "pin-project-lite", "signal-hook-registry", "socket2", @@ -3482,6 +3830,12 @@ dependencies = [ "serde", ] +[[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" @@ -3492,6 +3846,56 @@ 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.18" @@ -3585,6 +3989,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3738,6 +4152,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3832,6 +4255,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -4050,7 +4482,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 296cbe1..1ddba8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ openidconnect = { version = "4.0.0", default-features = false, features = ["reqw rand = "0.9.0" reqwest = { version = "0.12.15", features = ["json", "native-tls"] } rocket = { version = "0.5.1", features = ["json", "secrets"] } +rocket_dyn_templates = { version = "0.2.0", features = ["tera"] } serde = { version = "1.0.219", features = ["derive"] } thiserror = "2.0.12" tracing = "0.1.41" diff --git a/src/auth.rs b/src/auth.rs index 0ab5526..e2b5234 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,6 +1,6 @@ -use std::path::Path; use std::collections::HashMap; use std::io::Read; +use std::path::Path; use std::time::SystemTime; use jsonwebtoken::{ @@ -47,9 +47,7 @@ pub struct User { impl User { pub fn new(id: &'static str) -> Self { - Self { - id: id.into(), - } + Self { id: id.into() } } /// Return the user ID @@ -198,7 +196,9 @@ pub async fn get_oidc_client( } /// Load a secret from the specified path -pub fn load_secret>(path: P) -> Result, std::io::Error> { +pub fn load_secret>( + path: P, +) -> Result, std::io::Error> { let mut secret = vec![]; let mut f = std::fs::File::open(path)?; f.read_to_end(&mut secret)?; @@ -360,6 +360,7 @@ pub async fn oidc_callback( cookies: &CookieJar<'_>, ctx: &State, config: &State, + rconfig: &RocketConfig, ) -> Result { trace!("{:?}", params); let state = cookies @@ -390,7 +391,12 @@ pub async fn oidc_callback( ctx.make_jwt(&user, config.auth.login_ttl) .map_err(LoginError::from)?, )) - .secure(true) + .secure(rconfig.profile != RocketConfig::DEBUG_PROFILE) + .same_site(if rconfig.profile == RocketConfig::DEBUG_PROFILE { + SameSite::Lax + } else { + SameSite::Strict + }) .http_only(true) .expires(expires) .build(), diff --git a/src/lib.rs b/src/lib.rs index f8d1da6..ae8375d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod page; use meilisearch_sdk::client::Client as MeilisearchClient; use rocket::fairing::{self, AdHoc}; use rocket::Rocket; +use rocket_dyn_templates::Template; use tracing::error; use config::Config; @@ -99,6 +100,7 @@ pub fn rocket() -> Rocket { "/", rocket::routes![ page::index, + page::get_page, auth::oidc_callback, auth::oidc_login, page::post_page @@ -107,4 +109,5 @@ pub fn rocket() -> Rocket { .attach(AdHoc::config::()) .attach(AdHoc::try_on_ignite("Initialize context", init_context)) .attach(AdHoc::try_on_ignite("Meilisearch Setup", meilisearch_setup)) + .attach(Template::fairing()) } diff --git a/src/meilisearch.rs b/src/meilisearch.rs index 27527cc..55f4fe9 100644 --- a/src/meilisearch.rs +++ b/src/meilisearch.rs @@ -20,7 +20,10 @@ impl TryFrom<&Config> for Client { Some(t) => Some(std::fs::read_to_string(t).map_err(Error::Token)?), None => None, }; - Ok(Client::new(&config.meilisearch.url, token.as_deref().map(str::trim))?) + Ok(Client::new( + &config.meilisearch.url, + token.as_deref().map(str::trim), + )?) } } @@ -31,24 +34,35 @@ pub async fn ensure_index( match client.get_index(name).await { Ok(_) => { debug!("Meilisearch index '{}' already exists", name); - Ok(()) }, Err(MeilisearchError::Meilisearch(e)) if e.error_code == ErrorCode::IndexNotFound => { info!("Creating Meilisearch index: {}", name); - if let Err(e) = create_index(client, name).await { + create_index(client, name).await.inspect_err(|e| { error!("Failed to create index: {}", e); - Err(e) - } else { - Ok(()) - } + })?; }, Err(e) => { error!("Failed to check index: {}", e); - Err(e) + return Err(e); }, - } + }; + client + .index(name) + .set_sortable_attributes(&["timestamp", "title"]) + .await + .inspect_err(|e| { + error!("Failed to set index sortable attributes: {}", e); + })?; + client + .index(name) + .set_filterable_attributes(&["id", "user_id", "timestamp"]) + .await + .inspect_err(|e| { + error!("Failed to set index filterable attributes: {}", e); + })?; + Ok(()) } async fn create_index( diff --git a/src/page.rs b/src/page.rs index a9d7c93..341c14a 100644 --- a/src/page.rs +++ b/src/page.rs @@ -2,13 +2,16 @@ use chrono::{DateTime, Utc}; use html5ever::parse_document; use html5ever::tendril::TendrilSink; use markup5ever_rcdom::{Handle, NodeData, RcDom}; +use meilisearch_sdk::documents::DocumentsQuery; use meilisearch_sdk::errors::Error; +use meilisearch_sdk::search::{SearchResults, Selectors}; use rand::Rng; use rocket::form::Form; use rocket::response::Redirect; use rocket::serde::json::Json; use rocket::State; -use serde::Serialize; +use rocket_dyn_templates::{context, Template}; +use serde::{Deserialize, Serialize}; use tracing::{debug, error, event, instrument, Level}; use crate::auth::User; @@ -18,14 +21,16 @@ use crate::Context; static ID_CHARSET: &[u8] = b"0123456789abcdefghijklmnopqrstuvwxyz"; /// A saved page -#[derive(Clone, Serialize)] +#[derive(Clone, Deserialize, Serialize)] pub struct Page { /// Unique saved page ID id: String, /// User ID of page owner user_id: String, - /// Visit timestamp - timestamp: DateTime, + /// Visit date/time + datetime: DateTime, + /// Visit date/time (as Unix timestamp, for sorting) + timestamp: i64, /// Page URL url: String, /// Page title (extracted from HTML document) @@ -34,6 +39,21 @@ pub struct Page { data: String, } +/// Saved page info (no contents) +#[derive(Clone, Deserialize, Serialize)] +pub struct PageInfo { + /// Unique saved page ID + id: String, + /// User ID of page owner + user_id: String, + /// Visit date/time + datetime: DateTime, + /// Page URL + url: String, + /// Page title (extracted from HTML document) + title: Option, +} + /// Save page form #[derive(rocket::FromForm)] pub struct SavePageForm { @@ -43,10 +63,70 @@ pub struct SavePageForm { data: String, } +#[derive(rocket::Responder)] +pub enum IndexResponse { + Success(Template), + #[response(status = 500)] + Error(Template), +} + #[rocket::get("/")] -pub async fn index(user: Option) -> Result { +pub async fn index( + user: Option, + ctx: &State, + config: &State, +) -> Result { if let Some(user) = user { - Ok(format!("Hello, {}", user.id())) + match get_page_list(&user, ctx, config).await { + Ok(r) => Ok(IndexResponse::Success(Template::render( + "index", + context! { + user: user, + pages: r.hits.into_iter().map(|r| r.result).collect::>(), + total: r.estimated_total_hits, + }, + ))), + Err(e) => { + error!("Failed to retrieve page list: {}", e); + Ok(IndexResponse::Error(Template::render( + "error", + context! { + error: e.to_string(), + }, + ))) + }, + } + } else { + Err(Redirect::to(rocket::uri![crate::auth::oidc_login])) + } +} + +#[derive(rocket::Responder)] +pub enum PageResponse { + #[response(content_type = "html")] + Success(String), + #[response(status = 500)] + Error(Template), +} + +#[rocket::get("/page/")] +pub async fn get_page( + id: &str, + user: Option, + ctx: &State, + config: &State, +) -> Result, Redirect> { + if let Some(user) = user { + match _get_page(id, &user, ctx, config).await { + Ok(Some(p)) => Ok(Some(PageResponse::Success(p.data))), + Ok(None) => Ok(None), + Err(e) => Ok(Some(PageResponse::Error(Template::render( + "error", + context! { + error: e.to_string(), + }, + )))), + } } else { Err(Redirect::to(rocket::uri![crate::auth::oidc_login])) } @@ -69,6 +149,49 @@ pub async fn post_page( } } +async fn get_page_list( + user: &User, + ctx: &Context, + config: &Config, +) -> Result, Error> { + let index_name = &config.meilisearch.index; + debug!( + "Searching for pages in Meilisearch index {} owned by user {}", + index_name, + user.id() + ); + let index = ctx.client.index(index_name); + let documents = index + .search() + .with_attributes_to_retrieve(Selectors::Some(&[ + "id", "user_id", "datetime", "title", "url", + ])) + .with_filter(&format!("user_id = {}", user.id())) + .with_sort(&["timestamp:desc"]) + .execute() + .await?; + Ok(documents) +} + +async fn _get_page( + id: &str, + user: &User, + ctx: &Context, + config: &Config, +) -> Result, Error> { + let index_name = &config.meilisearch.index; + debug!( + "Retreiving page {} from Meilisearch index {}", + id, index_name, + ); + let index = ctx.client.index(index_name); + let mut results = DocumentsQuery::new(&index) + .with_filter(&format!("user_id = {} AND id = {}", user.id(), id)) + .execute() + .await?; + Ok(results.results.pop()) +} + /// Save the page #[instrument(level = "info", skip(data, ctx, config))] pub async fn save_page( @@ -81,10 +204,12 @@ pub async fn save_page( let index_name = &config.meilisearch.index; debug!("Saving page in Meilisearch index {}", index_name); let index = ctx.client.get_index(index_name).await?; + let now = Utc::now(); let doc = Page { id: gen_id(), user_id: user.into(), - timestamp: Utc::now(), + datetime: now, + timestamp: now.timestamp(), url: url.into(), title: extract_title(data), data: data.into(), diff --git a/templates/index.html.tera b/templates/index.html.tera new file mode 100644 index 0000000..f2ff935 --- /dev/null +++ b/templates/index.html.tera @@ -0,0 +1,69 @@ + + + + + +seensite + + + + +

Welcome

+

Displaying {{ pages | length }} of {{ total }} pages.

+ + + + + + + + + + + + + + +{% for page in pages -%} + + + + + +{%- endfor %} + + + +
DateTitleURL
{{ page.datetime | date(format="%x %X") }}
{{ page.title }}