# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
name = "addr2line"
[[package]]
name = "allocator-api2"
-version = "0.2.20"
+version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android-tzdata"
dependencies = [
"askama",
"axum-core",
- "http",
+ "http 1.2.0",
]
[[package]]
[[package]]
name = "axum"
-version = "0.7.7"
+version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae"
+checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [
"async-trait",
"axum-core",
"axum-macros",
"bytes",
"futures-util",
- "http",
+ "http 1.2.0",
"http-body",
"http-body-util",
"hyper",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
- "sync_wrapper 1.0.1",
+ "sync_wrapper 1.0.2",
"tokio",
"tower",
"tower-layer",
"async-trait",
"bytes",
"futures-util",
- "http",
+ "http 1.2.0",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
- "sync_wrapper 1.0.1",
+ "sync_wrapper 1.0.2",
"tower-layer",
"tower-service",
"tracing",
[[package]]
name = "axum-extra"
-version = "0.9.4"
+version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73c3220b188aea709cf1b6c5f9b01c3bd936bb08bd2b5184a12b35ac8131b1f9"
+checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04"
dependencies = [
"axum",
"axum-core",
"bytes",
"cookie",
+ "fastrand",
"futures-util",
- "http",
+ "http 1.2.0",
"http-body",
"http-body-util",
"mime",
+ "multer",
"pin-project-lite",
"serde",
"tower",
"tower-layer",
"tower-service",
- "tracing",
]
[[package]]
"serde",
]
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "bitflags"
version = "2.6.0"
[[package]]
name = "bytes"
-version = "1.8.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
+checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]]
name = "cc"
-version = "1.2.0"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8"
+checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc"
dependencies = [
"shlex",
]
[[package]]
name = "clap"
-version = "4.5.21"
+version = "4.5.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
+checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b"
dependencies = [
"clap_builder",
"clap_derive",
[[package]]
name = "clap_builder"
-version = "4.5.21"
+version = "4.5.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
+checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1"
dependencies = [
"anstream",
"anstyle",
[[package]]
name = "cpufeatures"
-version = "0.2.15"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6"
+checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
dependencies = [
"libc",
]
[[package]]
name = "email-encoding"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f"
+checksum = "ea3d894bbbab314476b265f9b2d46bf24b123a36dd0e96b06a1b49545b9d9dcc"
dependencies = [
"base64 0.22.1",
"memchr",
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
[[package]]
name = "entities"
version = "1.0.1"
[[package]]
name = "errno"
-version = "0.3.9"
+version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.59.0",
]
[[package]]
dependencies = [
"common",
"console_error_panic_hook",
+ "gloo",
"wasm-bindgen",
+ "wasm-bindgen-futures",
"web-sys",
]
+[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
+ "futures-channel",
"futures-core",
"futures-io",
+ "futures-macro",
"futures-sink",
"futures-task",
"memchr",
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
"wasi",
+ "wasm-bindgen",
]
[[package]]
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+[[package]]
+name = "gloo"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d15282ece24eaf4bd338d73ef580c6714c8615155c4190c781290ee3fa0fd372"
+dependencies = [
+ "gloo-console",
+ "gloo-dialogs",
+ "gloo-events",
+ "gloo-file",
+ "gloo-history",
+ "gloo-net",
+ "gloo-render",
+ "gloo-storage",
+ "gloo-timers",
+ "gloo-utils",
+ "gloo-worker",
+]
+
+[[package]]
+name = "gloo-console"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261"
+dependencies = [
+ "gloo-utils",
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-dialogs"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-events"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-file"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f"
+dependencies = [
+ "gloo-events",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-history"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6"
+dependencies = [
+ "getrandom",
+ "gloo-events",
+ "gloo-utils",
+ "serde",
+ "serde-wasm-bindgen",
+ "serde_urlencoded",
+ "thiserror 1.0.69",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-net"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-sink",
+ "gloo-utils",
+ "http 0.2.12",
+ "js-sys",
+ "pin-project",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-render"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56008b6744713a8e8d98ac3dcb7d06543d5662358c9c805b4ce2167ad4649833"
+dependencies = [
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-storage"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a"
+dependencies = [
+ "gloo-utils",
+ "js-sys",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-timers"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gloo-utils"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
+dependencies = [
+ "js-sys",
+ "serde",
+ "serde_json",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-worker"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "085f262d7604911c8150162529cefab3782e91adb20202e8658f7275d2aefe5d"
+dependencies = [
+ "bincode",
+ "futures",
+ "gloo-utils",
+ "gloo-worker-macros",
+ "js-sys",
+ "pinned",
+ "serde",
+ "thiserror 1.0.69",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "gloo-worker-macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "956caa58d4857bc9941749d55e4bd3000032d8212762586fa5705632967140e7"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "hashbrown"
version = "0.14.5"
[[package]]
name = "hashbrown"
-version = "0.15.1"
+version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "hashlink"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
-[[package]]
-name = "hermit-abi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
-
[[package]]
name = "hex"
version = "0.4.3"
[[package]]
name = "http"
-version = "1.1.0"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
dependencies = [
"bytes",
"fnv",
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
- "http",
+ "http 1.2.0",
]
[[package]]
dependencies = [
"bytes",
"futures-util",
- "http",
+ "http 1.2.0",
"http-body",
"pin-project-lite",
]
[[package]]
name = "http-range-header"
-version = "0.4.1"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a"
+checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
[[package]]
name = "httparse"
[[package]]
name = "hyper"
-version = "1.5.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a"
+checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
- "http",
+ "http 1.2.0",
"http-body",
"httparse",
"httpdate",
dependencies = [
"bytes",
"futures-util",
- "http",
+ "http 1.2.0",
"http-body",
"hyper",
"pin-project-lite",
[[package]]
name = "indexmap"
-version = "2.6.0"
+version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
+checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
- "hashbrown 0.15.1",
+ "hashbrown 0.15.2",
]
[[package]]
[[package]]
name = "itoa"
-version = "1.0.11"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "js-sys"
-version = "0.3.72"
+version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
+checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705"
dependencies = [
+ "once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
-version = "0.2.162"
+version = "0.2.167"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
+checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
[[package]]
name = "libm"
[[package]]
name = "litemap"
-version = "0.7.3"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
+checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
[[package]]
name = "lock_api"
[[package]]
name = "mio"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
+checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
- "hermit-abi",
"libc",
"wasi",
"windows-sys 0.52.0",
]
+[[package]]
+name = "multer"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b"
+dependencies = [
+ "bytes",
+ "encoding_rs",
+ "futures-util",
+ "http 1.2.0",
+ "httparse",
+ "memchr",
+ "mime",
+ "spin",
+ "version_check",
+]
+
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+[[package]]
+name = "pin-project"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "pin-project-lite"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+[[package]]
+name = "pinned"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b"
+dependencies = [
+ "futures",
+ "rustversion",
+ "thiserror 1.0.69",
+]
+
[[package]]
name = "pkcs1"
version = "0.7.5"
"zerocopy",
]
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit",
+]
+
[[package]]
name = "proc-macro2"
-version = "1.0.89"
+version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
+checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
"ron",
"serde",
"sqlx",
- "thiserror 2.0.3",
+ "thiserror 2.0.4",
"tokio",
"tower",
"tower-http",
[[package]]
name = "rsa"
-version = "0.9.6"
+version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc"
+checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519"
dependencies = [
"const-oid",
"digest",
[[package]]
name = "rustix"
-version = "0.38.40"
+version = "0.38.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0"
+checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
dependencies = [
"bitflags",
"errno",
[[package]]
name = "rustls"
-version = "0.23.16"
+version = "0.23.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e"
+checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1"
dependencies = [
"log",
"once_cell",
"serde_derive",
]
+[[package]]
+name = "serde-wasm-bindgen"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
+dependencies = [
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+]
+
[[package]]
name = "serde_derive"
version = "1.0.215"
[[package]]
name = "serde_json"
-version = "1.0.132"
+version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
+checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
"memchr",
[[package]]
name = "socket2"
-version = "0.5.7"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
"windows-sys 0.52.0",
[[package]]
name = "syn"
-version = "2.0.87"
+version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
+checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
[[package]]
name = "sync_wrapper"
-version = "1.0.1"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
[[package]]
name = "synstructure"
[[package]]
name = "thiserror"
-version = "2.0.3"
+version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
+checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490"
dependencies = [
- "thiserror-impl 2.0.3",
+ "thiserror-impl 2.0.4",
]
[[package]]
[[package]]
name = "thiserror-impl"
-version = "2.0.3"
+version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
+checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061"
dependencies = [
"proc-macro2",
"quote",
[[package]]
name = "time"
-version = "0.3.36"
+version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
[[package]]
name = "time-macros"
-version = "0.2.18"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
[[package]]
name = "tokio"
-version = "1.41.1"
+version = "1.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
+checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
dependencies = [
"backtrace",
"bytes",
[[package]]
name = "tokio-util"
-version = "0.7.12"
+version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
+checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
dependencies = [
"bytes",
"futures-core",
"tokio",
]
+[[package]]
+name = "toml_datetime"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
[[package]]
name = "tower"
version = "0.5.1"
[[package]]
name = "tower-http"
-version = "0.6.1"
+version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97"
+checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697"
dependencies = [
"bitflags",
"bytes",
"futures-util",
- "http",
+ "http 1.2.0",
"http-body",
"http-body-util",
"http-range-header",
[[package]]
name = "tracing"
-version = "0.1.40"
+version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"log",
"pin-project-lite",
[[package]]
name = "tracing-attributes"
-version = "0.1.27"
+version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
[[package]]
name = "tracing-core"
-version = "0.1.32"
+version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
dependencies = [
"once_cell",
"valuable",
[[package]]
name = "tracing-subscriber"
-version = "0.3.18"
+version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
dependencies = [
"matchers",
"nu-ansi-term",
[[package]]
name = "unicode-ident"
-version = "1.0.13"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-normalization"
[[package]]
name = "url"
-version = "2.5.3"
+version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
[[package]]
name = "wasm-bindgen"
-version = "0.2.95"
+version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
+checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c"
dependencies = [
"cfg-if",
"once_cell",
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.95"
+version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
+checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd"
dependencies = [
"bumpalo",
"log",
"wasm-bindgen-shared",
]
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.95"
+version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
+checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.95"
+version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
+checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d"
dependencies = [
"proc-macro2",
"quote",
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.95"
+version = "0.2.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
+checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49"
[[package]]
name = "web-sys"
-version = "0.3.72"
+version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
+checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c"
dependencies = [
"js-sys",
"wasm-bindgen",
[[package]]
name = "webpki-roots"
-version = "0.26.6"
+version = "0.26.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958"
+checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e"
dependencies = [
"rustls-pki-types",
]
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "write16"
version = "1.0.0"
[[package]]
name = "yoke"
-version = "0.7.4"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
"serde",
"stable_deref_trait",
[[package]]
name = "yoke-derive"
-version = "0.7.4"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
[[package]]
name = "zerofrom"
-version = "0.1.4"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
+checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
-version = "0.1.4"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
+checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [
"proc-macro2",
"quote",
fn run_sass(command: &mut Command) -> Output {
command
- .arg("style.scss")
+ .arg("scss/style.scss")
.arg("static/style.css")
.output()
.expect("Unable to compile SASS file, install SASS, see https://sass-lang.com/")
--- /dev/null
+@use 'toast.scss';
+
+@font-face {
+ font-family: Fira Code;
+ font-weight: 200;
+ src: url(FiraCode-Light.woff2) format("woff2");
+}
+
+@font-face {
+ font-family: Fira Code;
+ font-weight: 400;
+ src: url(FiraCode-Regular.woff2) format("woff2");
+}
+
+@font-face {
+ font-family: Fira Code;
+ font-weight: 600;
+ src: url(FiraCode-SemiBold.woff2) format("woff2");
+}
+
+@font-face {
+ font-family: Fira Code;
+ font-weight: 700;
+ src: url(FiraCode-Bold.woff2) format("woff2");
+}
+
+$primary: #182430;
+$background: darken($primary, 5%);
+$background-container: lighten($primary, 10%);
+
+* {
+ margin: 5px;
+ padding: 0px;
+}
+
+html {
+ font-size: 80%
+}
+
+a {
+ color: lighten($primary, 40%);
+ text-decoration: none;
+
+ &:hover {
+ color: lighten($primary, 60%);
+ }
+}
+
+body {
+ display: flex;
+ flex-direction: column;
+
+ font-family: Fira Code, Helvetica Neue, Helvetica, Arial, sans-serif;
+ text-shadow: 2px 2px 2px rgb(0, 0, 0);
+ // line-height: 18px;
+ color: rgb(255, 255, 255);
+ background-color: $background;
+ margin: 0px;
+
+ .recipe-item {
+ padding: 4px;
+ }
+
+ .recipe-item-current {
+ padding: 3px;
+ border: 1px solid white;
+ }
+
+ .header-container {
+ align-self: center;
+ }
+
+ .footer-container {
+ align-self: center;
+ font-size: 0.5em;
+ }
+
+ .main-container {
+ display: flex;
+ flex-direction: row;
+
+ // .recipes-list {
+ // text-align: left;
+ // }
+
+ .content {
+ flex-grow: 1;
+
+ background-color: $background-container;
+ border: 0.1em solid white;
+ padding: 0.5em;
+
+ h1 {
+ text-align: center;
+ }
+ }
+
+ form {
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 3px;
+
+ input,
+ button {
+ background-color: rgb(52, 40, 85);
+ border-width: 1px;
+ border-color: white;
+ color: white;
+ }
+ }
+
+ // #user-edit {
+ // .label-name {
+ // grid-column: 1;
+ // grid-row: 1;
+ // }
+
+ // .input-name {
+ // grid-column: 2;
+ // grid-row: 1;
+ // }
+
+ // .label-password-1 {
+ // grid-column: 1;
+ // grid-row: 2;
+ // }
+
+ // .input-password-1 {
+ // grid-column: 2;
+ // grid-row: 2;
+ // }
+
+ // .label-password-2 {
+ // grid-column: 1;
+ // grid-row: 3;
+ // }
+
+ // .input-password-2 {
+ // grid-column: 2;
+ // grid-row: 3;
+ // }
+
+ // .button-save {
+ // grid-column: 2;
+ // grid-row: 4;
+ // width: fit-content;
+ // justify-self: flex-end;
+ // }
+ // }
+
+ // #sign-in {
+
+ // }
+ }
+
+ img {
+ border: 0px;
+ }
+}
\ No newline at end of file
--- /dev/null
+#toast {
+ visibility: hidden;
+ max-width: 300px;
+ margin-left: -125px;
+ background-color: black;
+ text-align: center;
+ border-radius: 2px;
+ padding: 16px; // TODO: 'rem' better?
+ position: fixed;
+ z-index: 1;
+ left: 50%;
+ bottom: 30px;
+ box-shadow: -1px 1px 10px rgba(0, 0, 0, 0.3);
+}
+
+#toast.show {
+ visibility: visible;
+ animation: fadein 0.5s, fadeout 0.5s 3.6s;
+ animation-iteration-count: 1;
+}
+
+@keyframes fadein {
+ from {
+ bottom: 0;
+ opacity: 0;
+ }
+
+ to {
+ bottom: 30px;
+ opacity: 1;
+ }
+}
+
+@keyframes fadeout {
+ from {
+ bottom: 30px;
+ opacity: 1;
+ }
+
+ to {
+ bottom: 0;
+ opacity: 0;
+ }
+}
\ No newline at end of file
--- /dev/null
+use askama::Template;
+
+use crate::model;
+
+#[derive(Template)]
+#[template(path = "home.html")]
+pub struct HomeTemplate {
+ pub user: Option<model::User>,
+ pub recipes: Vec<(i64, String)>,
+ pub current_recipe_id: Option<i64>,
+}
+
+#[derive(Template)]
+#[template(path = "view_recipe.html")]
+pub struct ViewRecipeTemplate {
+ pub user: Option<model::User>,
+ pub recipes: Vec<(i64, String)>,
+ pub current_recipe_id: Option<i64>,
+ pub current_recipe: model::Recipe,
+}
+
+#[derive(Template)]
+#[template(path = "message.html")]
+pub struct MessageTemplate<'a> {
+ pub user: Option<model::User>,
+ pub message: &'a str,
+ pub as_code: bool, // Display the message in <pre> markup.
+}
+
+#[derive(Template)]
+#[template(path = "sign_up_form.html")]
+pub struct SignUpFormTemplate {
+ pub user: Option<model::User>,
+ pub email: String,
+ pub message: String,
+ pub message_email: String,
+ pub message_password: String,
+}
+
+#[derive(Template)]
+#[template(path = "sign_in_form.html")]
+pub struct SignInFormTemplate {
+ pub user: Option<model::User>,
+ pub email: String,
+ pub message: String,
+}
+
+#[derive(Template)]
+#[template(path = "ask_reset_password.html")]
+pub struct AskResetPasswordTemplate {
+ pub user: Option<model::User>,
+ pub email: String,
+ pub message: String,
+ pub message_email: String,
+}
+
+#[derive(Template)]
+#[template(path = "reset_password.html")]
+pub struct ResetPasswordTemplate {
+ pub user: Option<model::User>,
+ pub reset_token: String,
+ pub message: String,
+ pub message_password: String,
+}
+
+#[derive(Template)]
+#[template(path = "profile.html")]
+pub struct ProfileTemplate {
+ pub user: Option<model::User>,
+}
use axum::{
extract::{ConnectInfo, FromRef, Request, State},
+ http::StatusCode,
middleware::{self, Next},
response::{Response, Result},
- routing::{get, post, put},
+ routing::{get, put},
Router,
};
use axum_extra::extract::cookie::CookieJar;
mod data;
mod email;
mod hash;
+mod html_templates;
mod model;
mod ron_extractor;
+mod ron_utils;
mod services;
mod utils;
}
}
+impl axum::response::IntoResponse for db::DBError {
+ fn into_response(self) -> Response {
+ ron_utils::ron_error(StatusCode::INTERNAL_SERVER_ERROR, &self.to_string()).into_response()
+ }
+}
+
#[cfg(debug_assertions)]
const TRACING_LEVEL: tracing::Level = tracing::Level::DEBUG;
db_connection,
};
- let app = Router::new()
+ // TODO: Add fallback fo ron_api_routes.
+ let ron_api_routes = Router::new()
+ .route("/user/update", put(services::ron::update_user))
+ .fallback(services::ron::not_found);
+
+ let html_routes = Router::new()
.route("/", get(services::home_page))
.route(
"/signup",
.route("/recipe/view/:id", get(services::view_recipe))
// User.
.route("/user/edit", get(services::edit_user))
- // RON API.
- .route("/user/set_name", put(services::ron::update_user))
+ .route_layer(middleware::from_fn(services::ron_error_to_html));
+
+ let app = Router::new()
+ .merge(html_routes)
+ .nest("/ron-api", ron_api_routes)
+ .fallback(services::not_found)
.layer(TraceLayer::new_for_http())
- .route_layer(middleware::from_fn_with_state(
+ // FIXME: Should be 'route_layer' but it doesn't work for 'fallback(..)'.
+ .layer(middleware::from_fn_with_state(
state.clone(),
user_authentication,
))
.nest_service("/static", ServeDir::new("static"))
- .fallback(services::not_found)
.with_state(state)
.into_make_service_with_connect_info::<SocketAddr>();
http::{header, StatusCode},
response::{IntoResponse, Response},
};
-use ron::{
- de::from_bytes,
- ser::{to_string_pretty, PrettyConfig},
-};
-use serde::{de::DeserializeOwned, Serialize};
-
-const RON_CONTENT_TYPE: &'static str = "application/ron";
-
-#[derive(Debug, Serialize, Clone)]
-pub struct RonError {
- pub error: String,
-}
-
-impl axum::response::IntoResponse for RonError {
- fn into_response(self) -> Response {
- let ron_as_str = to_string_pretty(&self, PrettyConfig::new()).unwrap();
- ron_as_str.into_response()
- }
-}
-
-impl From<RonError> for Response {
- fn from(value: RonError) -> Self {
- value.into_response()
- }
-}
-
-pub fn ron_error(status: StatusCode, message: &str) -> impl IntoResponse {
- (
- status,
- [(header::CONTENT_TYPE, RON_CONTENT_TYPE)],
- RonError {
- error: message.to_string(),
- },
- )
-}
-
-pub fn ron_response<T>(ron: T) -> impl IntoResponse
-where
- T: Serialize,
-{
- let ron_as_str = to_string_pretty(&ron, PrettyConfig::new()).unwrap();
- ([(header::CONTENT_TYPE, RON_CONTENT_TYPE)], ron_as_str)
-}
+use serde::de::DeserializeOwned;
-fn parse_body<T>(body: Bytes) -> Result<T, RonError>
-where
- T: DeserializeOwned,
-{
- match from_bytes::<T>(&body) {
- Ok(ron) => Ok(ron),
- Err(error) => {
- return Err(RonError {
- error: format!("Ron parsing error: {}", error),
- });
- }
- }
-}
+use crate::ron_utils;
pub struct ExtractRon<T: DeserializeOwned>(pub T);
S: Send + Sync,
T: DeserializeOwned,
{
- type Rejection = Response; // axum::Error::ErrorResponse;
+ type Rejection = Response;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
match req.headers().get(header::CONTENT_TYPE) {
Some(content_type) => {
- if content_type != RON_CONTENT_TYPE {
- return Err(ron_error(
+ if content_type != ron_utils::RON_CONTENT_TYPE {
+ return Err(ron_utils::ron_error(
StatusCode::BAD_REQUEST,
- &format!("Invalid content type, must be {}", RON_CONTENT_TYPE),
+ &format!(
+ "Invalid content type, must be {:?}",
+ ron_utils::RON_CONTENT_TYPE
+ ),
)
.into_response());
}
}
None => {
return Err(
- ron_error(StatusCode::BAD_REQUEST, "No content type given").into_response()
+ ron_utils::ron_error(StatusCode::BAD_REQUEST, "No content type given")
+ .into_response(),
)
}
}
.await
.map_err(IntoResponse::into_response)?;
- let ron = parse_body(body)?;
+ let ron = ron_utils::parse_body(body)?;
Ok(Self(ron))
}
--- /dev/null
+use axum::{
+ async_trait,
+ body::Bytes,
+ extract::{FromRequest, Request},
+ http::{header, HeaderValue, StatusCode},
+ response::{IntoResponse, Response},
+};
+use common::ron_api;
+use ron::de::from_bytes;
+use serde::{de::DeserializeOwned, Serialize};
+
+pub const RON_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("application/ron");
+
+#[derive(Debug, Serialize, Clone)]
+pub struct RonError {
+ pub error: String,
+}
+
+impl axum::response::IntoResponse for RonError {
+ fn into_response(self) -> Response {
+ let ron_as_str = ron_api::to_string(&self);
+ (
+ StatusCode::BAD_REQUEST,
+ [(header::CONTENT_TYPE, RON_CONTENT_TYPE)],
+ ron_as_str,
+ )
+ .into_response()
+ }
+}
+
+impl From<RonError> for Response {
+ fn from(value: RonError) -> Self {
+ value.into_response()
+ }
+}
+
+pub fn ron_error(status: StatusCode, message: &str) -> impl IntoResponse {
+ (
+ status,
+ [(header::CONTENT_TYPE, RON_CONTENT_TYPE)],
+ RonError {
+ error: message.to_string(),
+ },
+ )
+}
+
+pub fn ron_response<T>(status: StatusCode, ron: T) -> impl IntoResponse
+where
+ T: Serialize,
+{
+ let ron_as_str = ron_api::to_string(&ron);
+ (
+ status,
+ [(header::CONTENT_TYPE, RON_CONTENT_TYPE)],
+ ron_as_str,
+ )
+}
+
+pub fn parse_body<T>(body: Bytes) -> Result<T, RonError>
+where
+ T: DeserializeOwned,
+{
+ match from_bytes::<T>(&body) {
+ Ok(ron) => Ok(ron),
+ Err(error) => {
+ return Err(RonError {
+ error: format!("Ron parsing error: {}", error),
+ });
+ }
+ }
+}
use std::{collections::HashMap, net::SocketAddr};
-use askama::Template;
use axum::{
- body::Body,
+ body::{self, Body},
debug_handler,
extract::{ConnectInfo, Extension, Host, Path, Query, Request, State},
- http::{HeaderMap, StatusCode},
+ http::{header, HeaderMap},
+ middleware::Next,
response::{IntoResponse, Redirect, Response, Result},
Form,
};
use serde::Deserialize;
use tracing::{event, Level};
-use crate::{config::Config, consts, data::db, email, model, utils, AppState};
+use crate::{
+ config::Config, consts, data::db, email, html_templates::*, model, ron_utils, utils, AppState,
+};
pub mod ron;
-impl axum::response::IntoResponse for db::DBError {
- fn into_response(self) -> Response {
- let body = MessageTemplate {
- user: None,
- message: &self.to_string(),
- };
- (StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
+// Will embed RON error in HTML page.
+pub async fn ron_error_to_html(req: Request, next: Next) -> Result<Response> {
+ let response = next.run(req).await;
+
+ if let Some(content_type) = response.headers().get(header::CONTENT_TYPE) {
+ if content_type == ron_utils::RON_CONTENT_TYPE {
+ let message = match body::to_bytes(response.into_body(), usize::MAX).await {
+ Ok(bytes) => String::from_utf8(bytes.to_vec()).unwrap_or_default(),
+ Err(error) => error.to_string(),
+ };
+ return Ok(MessageTemplate {
+ user: None,
+ message: &message,
+ as_code: true,
+ }
+ .into_response());
+ }
}
+
+ Ok(response)
}
///// HOME /////
-#[derive(Template)]
-#[template(path = "home.html")]
-struct HomeTemplate {
- user: Option<model::User>,
- recipes: Vec<(i64, String)>,
- current_recipe_id: Option<i64>,
-}
-
#[debug_handler]
pub async fn home_page(
State(connection): State<db::Connection>,
///// VIEW RECIPE /////
-#[derive(Template)]
-#[template(path = "view_recipe.html")]
-struct ViewRecipeTemplate {
- user: Option<model::User>,
- recipes: Vec<(i64, String)>,
- current_recipe_id: Option<i64>,
- current_recipe: model::Recipe,
-}
-
#[debug_handler]
pub async fn view_recipe(
State(connection): State<db::Connection>,
current_recipe: recipe,
}
.into_response()),
- None => Ok(MessageTemplate {
+ None => Ok(MessageTemplate::new_with_user(
+ &format!("Cannot find the recipe {}", recipe_id),
user,
- message: &format!("Cannot find the recipe {}", recipe_id),
- }
+ )
.into_response()),
}
}
-///// EDIT/NEW RECIPE /////
-
-// #[derive(Template)]
-// #[template(path = "edit_recipe.html")]
-// struct EditRecipeTemplate {
-// user: Option<model::User>,
-// recipes: Vec<(i64, String)>,
-// current_recipe_id: Option<i64>,
-
-// current_recipe: model::Recipe,
-// }
-
-// #[get("/recipe/edit/{id}")]
-// pub async fn edit_recipe(
-// req: HttpRequest,
-// path: web::Path<(i64,)>,
-// connection: web::Data<db::Connection>,
-// ) -> Result<HttpResponse> {
-// let (id,) = path.into_inner();
-// let user = match get_current_user(&req, connection.clone()).await {
-// Some(u) => u,
-// None => {
-// return Ok(MessageTemplate {
-// user: None,
-// message: "Cannot edit a recipe without being logged in",
-// }
-// .to_response())
-// }
-// };
-
-// let recipe = connection.get_recipe_async(id).await?;
-
-// if recipe.user_id != user.id {
-// return Ok(MessageTemplate {
-// message: "Cannot edit a recipe you don't own",
-// user: Some(user),
-// }
-// .to_response());
-// }
-
-// let recipes = connection.get_all_recipe_titles_async().await?;
-
-// Ok(EditRecipeTemplate {
-// user: Some(user),
-// current_recipe_id: Some(recipe.id),
-// recipes,
-// current_recipe: recipe,
-// }
-// .to_response())
-// }
-
-// #[get("/recipe/new")]
-// pub async fn new_recipe(
-// req: HttpRequest,
-// connection: web::Data<db::Connection>,
-// ) -> Result<HttpResponse> {
-// let user = match get_current_user(&req, connection.clone()).await {
-// Some(u) => u,
-// None => {
-// return Ok(MessageTemplate {
-// message: "Cannot create a recipe without being logged in",
-// user: None,
-// }
-// .to_response())
-// }
-// };
-
-// let recipe_id = connection.create_recipe_async(user.id).await?;
-// let recipes = connection.get_all_recipe_titles_async().await?;
-// let user_id = user.id;
-
-// Ok(EditRecipeTemplate {
-// user: Some(user),
-// current_recipe_id: Some(recipe_id),
-// recipes,
-// current_recipe: model::Recipe::empty(recipe_id, user_id),
-// }
-// .to_response())
-// }
-
///// MESSAGE /////
-#[derive(Template)]
-#[template(path = "message_without_user.html")]
-struct MessageWithoutUser<'a> {
- message: &'a str,
-}
+impl<'a> MessageTemplate<'a> {
+ pub fn new(message: &'a str) -> MessageTemplate<'a> {
+ MessageTemplate {
+ user: None,
+ message,
+ as_code: false,
+ }
+ }
-#[derive(Template)]
-#[template(path = "message.html")]
-struct MessageTemplate<'a> {
- user: Option<model::User>,
- message: &'a str,
+ pub fn new_with_user(message: &'a str, user: Option<model::User>) -> MessageTemplate<'a> {
+ MessageTemplate {
+ user,
+ message,
+ as_code: false,
+ }
+ }
}
//// SIGN UP /////
-#[derive(Template)]
-#[template(path = "sign_up_form.html")]
-struct SignUpFormTemplate {
- user: Option<model::User>,
- email: String,
- message: String,
- message_email: String,
- message_password: String,
-}
-
#[debug_handler]
pub async fn sign_up_get(
Extension(user): Extension<Option<model::User>>,
)
.await
{
- Ok(()) => Ok(MessageTemplate {
- user,
- message: "An email has been sent, follow the link to validate your account.",
- }
- .into_response()),
+ Ok(()) => Ok(
+ MessageTemplate::new_with_user(
+ "An email has been sent, follow the link to validate your account.",
+ user).into_response()),
Err(_) => {
// error!("Email validation error: {}", error); // TODO: log
error_response(SignUpError::UnableSendEmail, &form_data, user)
if user.is_some() {
return Ok((
jar,
- MessageTemplate {
- user,
- message: "User already exists",
- },
+ MessageTemplate::new_with_user("User already exists", user),
));
}
let (client_ip, client_user_agent) = utils::get_ip_and_user_agent(&headers, addr);
let user = connection.load_user(user_id).await?;
Ok((
jar,
- MessageTemplate {
+ MessageTemplate::new_with_user(
+ "Email validation successful, your account has been created",
user,
- message: "Email validation successful, your account has been created",
- },
+ ),
))
}
db::ValidationResult::ValidationExpired => Ok((
jar,
- MessageTemplate {
+ MessageTemplate::new_with_user(
+ "The validation has expired. Try to sign up again",
user,
- message: "The validation has expired. Try to sign up again",
- },
+ ),
)),
db::ValidationResult::UnknownUser => Ok((
jar,
- MessageTemplate {
- user,
- message: "Validation error. Try to sign up again",
- },
+ MessageTemplate::new_with_user("Validation error. Try to sign up again", user),
)),
}
}
None => Ok((
jar,
- MessageTemplate {
- user,
- message: "Validation error",
- },
+ MessageTemplate::new_with_user("Validation error", user),
)),
}
}
///// SIGN IN /////
-#[derive(Template)]
-#[template(path = "sign_in_form.html")]
-struct SignInFormTemplate {
- user: Option<model::User>,
- email: String,
- message: String,
-}
-
#[debug_handler]
pub async fn sign_in_get(
Extension(user): Extension<Option<model::User>>,
///// RESET PASSWORD /////
-#[derive(Template)]
-#[template(path = "ask_reset_password.html")]
-struct AskResetPasswordTemplate {
- user: Option<model::User>,
- email: String,
- message: String,
- message_email: String,
-}
-
#[debug_handler]
pub async fn ask_reset_password_get(
Extension(user): Extension<Option<model::User>>,
) -> Result<Response> {
if user.is_some() {
- Ok(MessageTemplate {
+ Ok(MessageTemplate::new_with_user(
+ "Can't ask to reset password when already logged in",
user,
- message: "Can't ask to reset password when already logged in",
- }
+ )
.into_response())
} else {
Ok(AskResetPasswordTemplate {
)
.await
{
- Ok(()) => Ok(MessageTemplate {
+ Ok(()) => Ok(MessageTemplate::new_with_user(
+ "An email has been sent, follow the link to reset your password.",
user,
- message: "An email has been sent, follow the link to reset your password.",
- }
+ )
.into_response()),
Err(_) => {
// error!("Email validation error: {}", error); // TODO: log
}
}
-#[derive(Template)]
-#[template(path = "reset_password.html")]
-struct ResetPasswordTemplate {
- user: Option<model::User>,
- reset_token: String,
- message: String,
- message_password: String,
-}
-
#[debug_handler]
pub async fn reset_password_get(
Extension(user): Extension<Option<model::User>>,
}
.into_response())
} else {
- Ok(MessageTemplate {
- user,
- message: "Reset token missing",
- }
- .into_response())
+ Ok(MessageTemplate::new_with_user("Reset token missing", user).into_response())
}
}
)
.await
{
- Ok(db::ResetPasswordResult::Ok) => Ok(MessageTemplate {
+ Ok(db::ResetPasswordResult::Ok) => Ok(MessageTemplate::new_with_user(
+ "Your password has been reset",
user,
- message: "Your password has been reset",
- }
+ )
.into_response()),
Ok(db::ResetPasswordResult::ResetTokenExpired) => {
error_response(ResetPasswordError::TokenExpired, &form_data, user)
///// EDIT PROFILE /////
-#[derive(Template)]
-#[template(path = "profile.html")]
-struct ProfileTemplate {
- user: Option<model::User>,
-}
-
#[debug_handler]
pub async fn edit_user(
State(connection): State<db::Connection>,
if user.is_some() {
ProfileTemplate { user }.into_response()
} else {
- MessageTemplate {
- user: None,
- message: "Not logged in",
- }
- .into_response()
+ MessageTemplate::new("Not logged in").into_response()
}
}
///// 404 /////
#[debug_handler]
-pub async fn not_found() -> Result<impl IntoResponse> {
- Ok(MessageWithoutUser {
- message: "404: Not found",
- })
+pub async fn not_found(Extension(user): Extension<Option<model::User>>) -> impl IntoResponse {
+ MessageTemplate::new_with_user("404: Not found", user)
}
// #[put("/ron-api/recipe/rm-step")]
// #[put("/ron-api/recipe/set-steps-order")]
-use askama_axum::IntoResponse;
use axum::{
debug_handler,
extract::{Extension, State},
http::StatusCode,
- response::ErrorResponse,
- response::Result,
+ response::{ErrorResponse, IntoResponse, Result},
};
use tracing::{event, Level};
-use crate::{
- data::db,
- model,
- ron_extractor::{ron_error, ron_response, ExtractRon},
-};
+use crate::{data::db, model, ron_extractor::ExtractRon, ron_utils::ron_error};
#[debug_handler]
pub async fn update_user(
ExtractRon(ron): ExtractRon<common::ron_api::UpdateProfile>,
) -> Result<StatusCode> {
if let Some(user) = user {
- // connection.set_user_name(user.id, &ron.name).await?;
+ connection
+ .update_user(
+ user.id,
+ ron.email.as_deref(),
+ ron.name.as_deref(),
+ ron.password.as_deref(),
+ )
+ .await?;
} else {
return Err(ErrorResponse::from(ron_error(
StatusCode::UNAUTHORIZED,
Ok(StatusCode::OK)
}
-/* Example with RON return value.
+///// 404 /////
#[debug_handler]
-pub async fn set_user_name(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetProfileName>,
-) -> Result<impl IntoResponse> {
-
-
- Ok(ron_response(common::ron_api::SetProfileName {
- name: "abc".to_string(),
- }))
+pub async fn not_found(Extension(user): Extension<Option<model::User>>) -> impl IntoResponse {
+ ron_error(StatusCode::NOT_FOUND, "Not found")
}
-*/
+++ /dev/null
-@font-face{font-family: Fira Code; font-weight:200; src:url(FiraCode-Light.woff2) format("woff2"); }
-@font-face{font-family: Fira Code; font-weight:400; src:url(FiraCode-Regular.woff2) format("woff2"); }
-@font-face{font-family: Fira Code; font-weight:600; src:url(FiraCode-SemiBold.woff2) format("woff2"); }
-@font-face{font-family: Fira Code; font-weight:700; src:url(FiraCode-Bold.woff2) format("woff2"); }
-
-$primary: #182430;
-
-$background: darken($primary, 5%);
-$background-container: lighten($primary, 10%);
-
-* {
- margin: 10px;
- padding: 0px;
-}
-
-html {
- font-size: 80%
-}
-
-a {
- color: lighten($primary, 40%);
- text-decoration: none;
- &:hover { color: lighten($primary, 60%); }
-}
-
-body {
- font-family: Fira Code, Helvetica Neue, Helvetica, Arial, sans-serif;
- text-shadow: 2px 2px 2px rgb(0, 0, 0);
- text-align: center;
- // line-height: 18px;
- color: rgb(255, 255, 255);
- background-color: $background;
- margin: 0px;
-
- .recipe-item {
- padding: 4px;
- }
-
- .recipe-item-current {
- padding: 3px;
- border: 1px solid white;
- }
-
- /*
- .header-container {
-
- }
- */
-
- .main-container {
- display: flex;
-
- .list {
- text-align: left;
- }
-
- .content {
- background-color: $background-container;
- border: 0.1em solid white;
- padding: 0.5em;
- }
- }
-
- .footer-container {
- font-size: 0.5em;
- }
-}
-
-img {
- border: 0px;
-}
\ No newline at end of file
run();
</script>
+ <div id="toast"></div>
+
{% block body_container %}{% endblock %}
+
<footer class="footer-container">gburri - 2022</footer>
</body>
</html>
\ No newline at end of file
{% endmacro %}
{% block main_container %}
- <nav class="list">
+ <nav class="recipes-list">
<ul>
{% for (id, title) in recipes %}
<li>
{% extends "base_with_header.html" %}
{% block main_container %}
- {{ message|markdown }}
+
+ <div class="message">
+ {% if as_code %}
+ <pre><code>
+ {% endif %}
+
+ {{ message|markdown }}
+
+ {% if as_code %}
+ </code></pre>
+ {% endif %}
+ </div>
+
<a href="/">Go to home</a>
{% endblock %}
\ No newline at end of file
+++ /dev/null
-{% extends "base.html" %}
-
-{% block body_container %}
- {% include "title.html" %}
- {{ message|markdown }}
-{% endblock %}
\ No newline at end of file
{% block main_container %}
-<h2>Profile</h2>
-
{% match user %}
{% when Some with (user) %}
-<div id="user-edit">
+<div class="content">
+
+ <h1>Profile</h1>
+
+ <form id="user-edit">
+
+ <label for="input-name">Name</label>
+ <input
+ id="input-name"
+ type="text"
+ name="name"
+ value="{{ user.name }}"
+ autocapitalize="none"
+ autocomplete="title"
+ autofocus="autofocus" />
- <label for="title_field">Name</label>
- <input
- id="name_field"
- type="text"
- name="name"
- value="{{ user.name }}"
- autocapitalize="none"
- autocomplete="title"
- autofocus="autofocus" />
+ <label for="input-password-1">New password (minimum 8 characters)</label>
+ <input id="input-password-1" type="password" name="password_1" />
- <label for="password_field_1">New password (minimum 8 characters)</label>
- <input id="password_field_1" type="password" name="password_1" />
+ <label for="input-password-2">Re-enter password</label>
+ <input id="input-password-2" type="password" name="password_2" />
- <label for="password_field_1">Re-enter password</label>
- <input id="password_field_2" type="password" name="password_2" />
+ <input type="button" value="Save" />
+ </form>
- <button class="button" typed="button">Save</button>
</div>
{% when None %}
{% extends "base_with_header.html" %}
{% block main_container %}
- <div class="content">
- <form action="/signin" method="post">
- <label for="email_field">Email address</label>
- <input id="email_field" type="email" name="email" value="{{ email }}" autocapitalize="none" autocomplete="email" autofocus="autofocus" />
- <label for="password_field">Password</label>
- <input id="password_field" type="password" name="password" autocomplete="current-password" />
+<div id="sign-in" class="content">
+
+ <h1>Sign in</h1>
+
+ <form action="/signin" method="post">
+ <label for="input-email">Email address</label>
+ <input id="input-email" type="email" name="email" value="{{ email }}" autocapitalize="none" autocomplete="email" autofocus="autofocus" />
+
+ <label for="input-password">Password</label>
+ <input id="input-password" type="password" name="password" autocomplete="current-password" />
+
+ <input type="submit" value="Sign in" />
+ </form>
+ {{ message }}
+</div>
- <input type="submit" name="commit" value="Sign in" />
- </form>
- {{ message }}
- </div>
{% endblock %}
{% extends "base_with_header.html" %}
{% block main_container %}
- <div class="content">
- <form action="/signup" method="post">
- <label for="email_field">Your email address</label>
- <input id="email_field" type="email" name="email" value="{{ email }}" autocapitalize="none" autocomplete="email" autofocus="autofocus" />
- {{ message_email }}
+<div class="content">
- <label for="password_field_1">Choose a password (minimum 8 characters)</label>
- <input id="password_field_1" type="password" name="password_1" />
+ <h1>Sign up</h1>
- <label for="password_field_1">Re-enter password</label>
- <input id="password_field_2" type="password" name="password_2" />
+ <form action="/signup" method="post">
+ <label for="input-email">Your email address</label>
+ <input id="input-email" type="email" name="email" value="{{ email }}" autocapitalize="none" autocomplete="email" autofocus="autofocus" />
+ {{ message_email }}
- {{ message_password }}
+ <label for="input-password-1">Choose a password (minimum 8 characters)</label>
+ <input id="input-password-1" type="password" name="password_1" />
- <input type="submit" name="commit" value="Sign up" />
- </form>
- {{ message }}
- </div>
+ <label for="input-password-2">Re-enter password</label>
+ <input id="input-password-2" type="password" name="password_2" />
+
+ {{ message_password }}
+
+ <input type="submit" name="commit" value="Sign up" />
+ </form>
+ {{ message }}
+</div>
{% endblock %}
lazy_static = "1"
ron = "0.8"
-serde = {version = "1.0", features = ["derive"]}
\ No newline at end of file
+serde = { version = "1.0", features = ["derive"] }
-use serde::{Deserialize, Serialize};
+use ron::{
+ de::from_bytes,
+ ser::{to_string_pretty, PrettyConfig},
+};
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
///// RECIPE /////
pub email: Option<String>,
pub password: Option<String>,
}
+
+pub fn to_string<T>(ron: T) -> String
+where
+ T: Serialize,
+{
+ // TODO: handle'unwrap'.
+ to_string_pretty(&ron, PrettyConfig::new()).unwrap()
+}
common = { path = "../common" }
wasm-bindgen = "0.2"
+wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = [
"console",
"Document",
"Location",
"EventTarget",
"HtmlLabelElement",
+ "HtmlInputElement",
] }
+gloo = "0.11"
+
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
+use gloo::{console::log, events::EventListener, net::http::Request};
use wasm_bindgen::prelude::*;
-use web_sys::{Document, HtmlLabelElement};
+use wasm_bindgen_futures::spawn_local;
+use web_sys::{Document, HtmlInputElement};
-pub fn recipe_edit(doc: &Document) {
+use crate::toast::{self, Level};
+
+pub fn recipe_edit(doc: Document) -> Result<(), JsValue> {
let title_input = doc.get_element_by_id("title_field").unwrap();
+ Ok(())
}
-pub fn user_edit(doc: &Document) {
- // let name_input = doc.get_element_by_id("name_field").unwrap().dyn_ref::<>()
+pub fn user_edit(doc: Document) -> Result<(), JsValue> {
+ log!("user_edit");
+
+ let button = doc
+ .query_selector("#user-edit input[type='button']")?
+ .unwrap();
+
+ let on_click_submit = EventListener::new(&button, "click", move |_event| {
+ log!("Click!");
+
+ let input_name = doc.get_element_by_id("input-name").unwrap();
+ let name = input_name.dyn_ref::<HtmlInputElement>().unwrap().value();
+
+ let update_data = common::ron_api::UpdateProfile {
+ name: Some(name),
+ email: None,
+ password: None,
+ };
+
+ let body = common::ron_api::to_string(update_data);
+
+ let doc = doc.clone();
+ spawn_local(async move {
+ match Request::put("/ron-api/user/update")
+ .header("Content-Type", "application/ron")
+ .body(body)
+ .unwrap()
+ .send()
+ .await
+ {
+ Ok(resp) => {
+ log!("Status code: {}", resp.status());
+ if resp.status() == 200 {
+ toast::show(Level::Info, "Profile saved", doc);
+ } else {
+ toast::show(
+ Level::Error,
+ &format!(
+ "Status code: {} {}",
+ resp.status(),
+ resp.text().await.unwrap()
+ ),
+ doc,
+ );
+ }
+ }
+ Err(error) => {
+ toast::show(
+ Level::Info,
+ &format!("Internal server error: {}", error),
+ doc,
+ );
+ }
+ }
+ });
+ });
+
+ on_click_submit.forget();
+
+ Ok(())
}
mod handles;
+mod toast;
mod utils;
+use gloo::{console::log, events::EventListener};
use wasm_bindgen::prelude::*;
use web_sys::console;
-#[wasm_bindgen]
-extern "C" {
- fn alert(s: &str);
-}
+// #[wasm_bindgen]
+// extern "C" {
+// fn alert(s: &str);
+// }
-#[wasm_bindgen]
-pub fn greet(name: &str) {
- alert(&format!("Hello, {}!", name));
- console::log_1(&"Hello bg".into());
-}
+// #[wasm_bindgen]
+// pub fn greet(name: &str) {
+// alert(&format!("Hello, {}!", name));
+// console::log_1(&"Hello bg".into());
+// }
#[wasm_bindgen(start)]
pub fn main() -> Result<(), JsValue> {
* - Call (as AJAR) /ron-api/set-title and set the body to a serialized RON of the type common::ron_api::SetTitle
* - Display error message if needed
*/
-
match path[..] {
["recipe", "edit", id] => {
let id = id.parse::<i64>().unwrap(); // TODO: remove unwrap.
- console_log!("recipe edit ID: {}", id);
+ log!("recipe edit ID: {}", id);
- handles::recipe_edit(&document);
+ handles::recipe_edit(document)?;
}
["user", "edit"] => {
- handles::user_edit(&document);
+ handles::user_edit(document)?;
}
_ => (),
}
--- /dev/null
+use gloo::{console::log, timers::callback::Timeout};
+use web_sys::{console, Document, HtmlInputElement};
+
+pub enum Level {
+ Success,
+ Error,
+ Info,
+ Warning,
+}
+
+pub fn show(level: Level, message: &str, doc: Document) {
+ let toast_element = doc.get_element_by_id("toast").unwrap();
+ toast_element.set_inner_html(message);
+ toast_element.set_class_name("show");
+
+ Timeout::new(4_000, move || {
+ toast_element.set_class_name("");
+ })
+ .forget();
+}
-use web_sys::console;
+// use web_sys::console;
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
console_error_panic_hook::set_once();
}
-#[macro_export]
-macro_rules! console_log {
- // Note that this is using the `log` function imported above during
- // `bare_bones`
- ($($t:tt)*) => (console::log_1(&format_args!($($t)*).to_string().into()))
-}
+// #[macro_export]
+// macro_rules! console_log {
+// // Note that this is using the `log` function imported above during
+// // `bare_bones`
+// ($($t:tt)*) => (console::log_1(&format_args!($($t)*).to_string().into()))
+// }