Searcher
This commit is contained in:
parent
2849d9f339
commit
6d487dd5c7
376
Cargo.lock
generated
376
Cargo.lock
generated
@ -793,6 +793,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitpacking"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c1d3e2bfd8d06048a179f7b17afc3188effa10385e7b00dc65af6aae732ea92"
|
||||||
|
dependencies = [
|
||||||
|
"crunchy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitvec"
|
name = "bitvec"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -913,9 +922,17 @@ version = "1.1.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
|
checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"jobserver",
|
||||||
|
"libc",
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "census"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -1122,6 +1139,8 @@ dependencies = [
|
|||||||
"sea-orm",
|
"sea-orm",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"tantivy",
|
||||||
|
"tempfile",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"uuid",
|
"uuid",
|
||||||
@ -1185,6 +1204,15 @@ version = "2.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.13"
|
version = "0.5.13"
|
||||||
@ -1228,6 +1256,12 @@ version = "0.8.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
@ -1370,6 +1404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1436,6 +1471,12 @@ version = "0.15.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "downcast-rs"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dtoa"
|
name = "dtoa"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
@ -1547,6 +1588,12 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastdivide"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59668941c55e5c186b8b58c391629af56774ec768f73c08bbcd56f09348eb00b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
@ -1591,6 +1638,12 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@ -1600,6 +1653,16 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fs4"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7e180ac76c23b45e767bd7ae9579bc0bb458618c4bc71835926e098e61d15f8"
|
||||||
|
dependencies = [
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsevent-sys"
|
name = "fsevent-sys"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
@ -1848,6 +1911,11 @@ name = "hashbrown"
|
|||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
|
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"equivalent",
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashlink"
|
name = "hashlink"
|
||||||
@ -1915,6 +1983,12 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "htmlescape"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
@ -2064,6 +2138,18 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "instant"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "io-uring"
|
name = "io-uring"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
@ -2104,6 +2190,15 @@ version = "1.0.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobserver"
|
||||||
|
version = "0.1.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.72"
|
version = "0.3.72"
|
||||||
@ -2168,6 +2263,12 @@ dependencies = [
|
|||||||
"spin 0.9.8",
|
"spin 0.9.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "levenshtein_automata"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.161"
|
version = "0.2.161"
|
||||||
@ -2299,6 +2400,21 @@ dependencies = [
|
|||||||
"value-bag",
|
"value-bag",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.15.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lz4_flex"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -2324,12 +2440,31 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "measure_time"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbefd235b0aadd181626f281e1d684e116972988c14c264e42069d5e8a5775cc"
|
||||||
|
dependencies = [
|
||||||
|
"instant",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.4"
|
version = "2.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memmap2"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "migration"
|
name = "migration"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -2396,6 +2531,12 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "murmurhash32"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "napi"
|
name = "napi"
|
||||||
version = "2.16.13"
|
version = "2.16.13"
|
||||||
@ -2563,6 +2704,16 @@ dependencies = [
|
|||||||
"libm",
|
"libm",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi 0.3.9",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.5"
|
version = "0.36.5"
|
||||||
@ -2578,6 +2729,12 @@ version = "1.20.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oneshot"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e296cf87e61c9cfc1a61c3c63a0f7f286ed4554e0e22be84e8a38e1d264a2a29"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -2646,6 +2803,15 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ownedbytes"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3a059efb063b8f425b948e042e6b9bd85edfe60e913630ed727b23e2dfcc558"
|
||||||
|
dependencies = [
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parcel_selectors"
|
name = "parcel_selectors"
|
||||||
version = "0.27.0"
|
version = "0.27.0"
|
||||||
@ -3085,6 +3251,16 @@ dependencies = [
|
|||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_distr"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rayon"
|
name = "rayon"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
@ -3452,6 +3628,16 @@ dependencies = [
|
|||||||
"ordered-multimap",
|
"ordered-multimap",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-stemmers"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
@ -3964,6 +4150,15 @@ version = "0.3.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sketches-ddsketch"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@ -4257,6 +4452,12 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static-self"
|
name = "static-self"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -4335,6 +4536,147 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tantivy"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8d0582f186c0a6d55655d24543f15e43607299425c5ad8352c242b914b31856"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"arc-swap",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"bitpacking",
|
||||||
|
"byteorder",
|
||||||
|
"census",
|
||||||
|
"crc32fast",
|
||||||
|
"crossbeam-channel",
|
||||||
|
"downcast-rs",
|
||||||
|
"fastdivide",
|
||||||
|
"fnv",
|
||||||
|
"fs4",
|
||||||
|
"htmlescape",
|
||||||
|
"itertools 0.12.1",
|
||||||
|
"levenshtein_automata",
|
||||||
|
"log",
|
||||||
|
"lru",
|
||||||
|
"lz4_flex",
|
||||||
|
"measure_time",
|
||||||
|
"memmap2",
|
||||||
|
"num_cpus",
|
||||||
|
"once_cell",
|
||||||
|
"oneshot",
|
||||||
|
"rayon",
|
||||||
|
"regex",
|
||||||
|
"rust-stemmers",
|
||||||
|
"rustc-hash",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sketches-ddsketch",
|
||||||
|
"smallvec",
|
||||||
|
"tantivy-bitpacker",
|
||||||
|
"tantivy-columnar",
|
||||||
|
"tantivy-common",
|
||||||
|
"tantivy-fst",
|
||||||
|
"tantivy-query-grammar",
|
||||||
|
"tantivy-stacker",
|
||||||
|
"tantivy-tokenizer-api",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
|
"uuid",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tantivy-bitpacker"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "284899c2325d6832203ac6ff5891b297fc5239c3dc754c5bc1977855b23c10df"
|
||||||
|
dependencies = [
|
||||||
|
"bitpacking",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tantivy-columnar"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12722224ffbe346c7fec3275c699e508fd0d4710e629e933d5736ec524a1f44e"
|
||||||
|
dependencies = [
|
||||||
|
"downcast-rs",
|
||||||
|
"fastdivide",
|
||||||
|
"itertools 0.12.1",
|
||||||
|
"serde",
|
||||||
|
"tantivy-bitpacker",
|
||||||
|
"tantivy-common",
|
||||||
|
"tantivy-sstable",
|
||||||
|
"tantivy-stacker",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tantivy-common"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8019e3cabcfd20a1380b491e13ff42f57bb38bf97c3d5fa5c07e50816e0621f4"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"byteorder",
|
||||||
|
"ownedbytes",
|
||||||
|
"serde",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tantivy-fst"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"regex-syntax 0.8.5",
|
||||||
|
"utf8-ranges",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tantivy-query-grammar"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "847434d4af57b32e309f4ab1b4f1707a6c566656264caa427ff4285c4d9d0b82"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tantivy-sstable"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c69578242e8e9fc989119f522ba5b49a38ac20f576fc778035b96cc94f41f98e"
|
||||||
|
dependencies = [
|
||||||
|
"tantivy-bitpacker",
|
||||||
|
"tantivy-common",
|
||||||
|
"tantivy-fst",
|
||||||
|
"zstd",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tantivy-stacker"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c56d6ff5591fc332739b3ce7035b57995a3ce29a93ffd6012660e0949c956ea8"
|
||||||
|
dependencies = [
|
||||||
|
"murmurhash32",
|
||||||
|
"rand_distr",
|
||||||
|
"tantivy-common",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tantivy-tokenizer-api"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a0dcade25819a89cfe6f17d932c9cedff11989936bf6dd4f336d50392053b04"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tap"
|
name = "tap"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -4703,6 +5045,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8-ranges"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@ -5132,3 +5480,31 @@ name = "zeroize"
|
|||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-safe",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-safe"
|
||||||
|
version = "7.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
|
||||||
|
dependencies = [
|
||||||
|
"zstd-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zstd-sys"
|
||||||
|
version = "2.0.13+zstd.1.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
@ -25,5 +25,7 @@ humantime = "2.1.0"
|
|||||||
humantime-serde = "1.1.1"
|
humantime-serde = "1.1.1"
|
||||||
actix-session = { version = "0.10.1", features = ["redis-session-rustls"] }
|
actix-session = { version = "0.10.1", features = ["redis-session-rustls"] }
|
||||||
actix-identity = "0.8.0"
|
actix-identity = "0.8.0"
|
||||||
|
tantivy = "0.22.0"
|
||||||
|
tempfile = "3.13.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
@ -1744,10 +1744,6 @@ html{
|
|||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mt-6{
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block{
|
.block{
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@ -1888,30 +1884,16 @@ html{
|
|||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-r-4{
|
|
||||||
border-right-width: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-gray-100{
|
.border-gray-100{
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
border-color: rgb(243 244 246 / var(--tw-border-opacity));
|
border-color: rgb(243 244 246 / var(--tw-border-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-primary{
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-color: rgb(255 99 99 / var(--tw-border-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gray-300{
|
.bg-gray-300{
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-secondary-100{
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(226 226 213 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.stroke-current{
|
.stroke-current{
|
||||||
stroke: currentColor;
|
stroke: currentColor;
|
||||||
}
|
}
|
||||||
@ -1993,11 +1975,6 @@ html{
|
|||||||
color: rgb(55 65 81 / var(--tw-text-opacity));
|
color: rgb(55 65 81 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-secondary-200{
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(136 136 131 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-stone-500{
|
.text-stone-500{
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(120 113 108 / var(--tw-text-opacity));
|
color: rgb(120 113 108 / var(--tw-text-opacity));
|
||||||
@ -2108,32 +2085,16 @@ html{
|
|||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:scale-110:hover{
|
|
||||||
--tw-scale-x: 1.1;
|
|
||||||
--tw-scale-y: 1.1;
|
|
||||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:bg-gray-400:hover{
|
.hover\:bg-gray-400:hover{
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
|
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:bg-opacity-60:hover{
|
|
||||||
--tw-bg-opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:text-gray-600:hover{
|
.hover\:text-gray-600:hover{
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(75 85 99 / var(--tw-text-opacity));
|
color: rgb(75 85 99 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.hover\:shadow-inner:hover{
|
|
||||||
--tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
|
|
||||||
--tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);
|
|
||||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hover\:shadow-lg:hover{
|
.hover\:shadow-lg:hover{
|
||||||
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||||
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
||||||
@ -2175,10 +2136,6 @@ html{
|
|||||||
grid-column: span 3 / span 3;
|
grid-column: span 3 / span 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md\:block{
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.md\:flex{
|
.md\:flex{
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
1
src/actors/mod.rs
Normal file
1
src/actors/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod search;
|
129
src/actors/search.rs
Normal file
129
src/actors/search.rs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use actix::prelude::*;
|
||||||
|
use tantivy::collector::TopDocs;
|
||||||
|
use tantivy::query::QueryParser;
|
||||||
|
use tantivy::{doc, Index, IndexWriter, ReloadPolicy, Searcher};
|
||||||
|
use tantivy::{schema::*, TantivyError};
|
||||||
|
|
||||||
|
pub struct Inner {
|
||||||
|
writer: IndexWriter,
|
||||||
|
schema: Schema,
|
||||||
|
searcher: Searcher,
|
||||||
|
index: Index,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SearchEngine(Arc<Mutex<Inner>>);
|
||||||
|
|
||||||
|
impl SearchEngine {
|
||||||
|
pub fn build() -> Result<Self, tantivy::TantivyError> {
|
||||||
|
let index_path = std::path::Path::new("./indices");
|
||||||
|
let mut schema_builder = Schema::builder();
|
||||||
|
schema_builder.add_u64_field("id", INDEXED);
|
||||||
|
schema_builder.add_text_field("title", TEXT);
|
||||||
|
schema_builder.add_text_field("summary", TEXT);
|
||||||
|
|
||||||
|
let schema = schema_builder.build();
|
||||||
|
let index = Index::create_in_dir(&index_path, schema.clone())?;
|
||||||
|
let index_writer: IndexWriter = index.writer(50_000_000)?;
|
||||||
|
|
||||||
|
let reader = index
|
||||||
|
.reader_builder()
|
||||||
|
.reload_policy(ReloadPolicy::OnCommitWithDelay)
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let searcher = reader.searcher();
|
||||||
|
|
||||||
|
Ok(Self(Arc::new(Mutex::new(Inner {
|
||||||
|
writer: index_writer,
|
||||||
|
schema,
|
||||||
|
searcher,
|
||||||
|
index,
|
||||||
|
}))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl actix::Actor for SearchEngine {
|
||||||
|
type Context = actix::Context<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Message)]
|
||||||
|
#[rtype(result = "Result<u64,TantivyError>")]
|
||||||
|
pub struct CreateRecipe {
|
||||||
|
id: u64,
|
||||||
|
title: String,
|
||||||
|
summary: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<CreateRecipe> for SearchEngine {
|
||||||
|
type Result = actix::ResponseActFuture<Self, Result<u64, TantivyError>>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: CreateRecipe, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
let inner = self.0.clone();
|
||||||
|
Box::pin(
|
||||||
|
async move {
|
||||||
|
let mut shared = inner.lock().unwrap();
|
||||||
|
|
||||||
|
let id = shared.schema.get_field("id").unwrap();
|
||||||
|
let title = shared.schema.get_field("summary").unwrap();
|
||||||
|
let summary = shared.schema.get_field("summary").unwrap();
|
||||||
|
|
||||||
|
let n = shared.writer.add_document(doc! {
|
||||||
|
id => msg.id,
|
||||||
|
title => msg.title,
|
||||||
|
summary => msg.summary,
|
||||||
|
})?;
|
||||||
|
shared.writer.commit()?;
|
||||||
|
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
.into_actor(self),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Message)]
|
||||||
|
#[rtype(result = "Result<Vec<u64>,TantivyError>")]
|
||||||
|
pub struct Find {
|
||||||
|
query: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<Find> for SearchEngine {
|
||||||
|
type Result = actix::ResponseActFuture<Self, Result<Vec<u64>, TantivyError>>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: Find, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
let inner = self.0.clone();
|
||||||
|
Box::pin(
|
||||||
|
async move {
|
||||||
|
let shared = inner.lock().unwrap();
|
||||||
|
|
||||||
|
let id = shared.schema.get_field("id").unwrap();
|
||||||
|
let title = shared.schema.get_field("summary").unwrap();
|
||||||
|
let summary = shared.schema.get_field("summary").unwrap();
|
||||||
|
|
||||||
|
let query_parser = QueryParser::for_index(&shared.index, vec![title, summary]);
|
||||||
|
let query = query_parser.parse_query(&msg.query)?;
|
||||||
|
|
||||||
|
let rows = shared.searcher.search(&query, &TopDocs::with_limit(100))?;
|
||||||
|
|
||||||
|
let ids = rows
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|row| {
|
||||||
|
let doc: Option<TantivyDocument> = shared.searcher.doc(row.1).ok();
|
||||||
|
doc
|
||||||
|
})
|
||||||
|
.fold(Vec::with_capacity(1_000), |agg, doc| {
|
||||||
|
doc.get_all(id)
|
||||||
|
.filter_map(|id| id.as_u64())
|
||||||
|
.fold(agg, |mut agg, id| {
|
||||||
|
agg.push(id);
|
||||||
|
agg
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(ids)
|
||||||
|
}
|
||||||
|
.into_actor(self),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
17
src/filters.rs
Normal file
17
src/filters.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
use crate::types::Page;
|
||||||
|
|
||||||
|
pub fn duration(sec: &&i32) -> ::askama::Result<String> {
|
||||||
|
use humantime::format_duration;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
Ok(format_duration(Duration::from_secs(**sec as u64)).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn page_class(pair: &(Page, Page)) -> ::askama::Result<String> {
|
||||||
|
tracing::info!("page_class: {pair:?}");
|
||||||
|
Ok(match pair {
|
||||||
|
(current, expected) if current == expected => " border-r-4 border-primary ",
|
||||||
|
_ => "",
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
}
|
277
src/main.rs
277
src/main.rs
@ -1,266 +1,20 @@
|
|||||||
use actix_files::Files;
|
use actix_files::Files;
|
||||||
use actix_identity::{Identity, IdentityMiddleware};
|
use actix_identity::IdentityMiddleware;
|
||||||
use actix_session::{storage::RedisSessionStore, SessionMiddleware};
|
use actix_session::{storage::RedisSessionStore, SessionMiddleware};
|
||||||
use actix_web::cookie::Key;
|
use actix_web::cookie::Key;
|
||||||
use actix_web::{
|
use actix_web::{web::Data, App, HttpServer};
|
||||||
get, post,
|
use sea_orm::Database;
|
||||||
web::{Data, Form, Json, Path},
|
|
||||||
App, HttpResponse, HttpServer, Responder,
|
|
||||||
};
|
|
||||||
use actix_web::{HttpMessage, HttpRequest};
|
|
||||||
use askama::Template;
|
|
||||||
use sea_orm::{prelude::*, Database, QuerySelect};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use types::Admins;
|
||||||
|
|
||||||
mod entities;
|
pub mod actors;
|
||||||
mod filters {
|
pub mod entities;
|
||||||
pub fn duration(sec: &&i32) -> ::askama::Result<String> {
|
pub mod filters;
|
||||||
use humantime::format_duration;
|
pub mod routes;
|
||||||
use std::time::Duration;
|
pub mod types;
|
||||||
|
|
||||||
Ok(format_duration(Duration::from_secs(**sec as u64)).to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const SESSION_KEY: &'static str = "session";
|
const SESSION_KEY: &'static str = "session";
|
||||||
|
|
||||||
type User = String;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
|
||||||
enum Page {
|
|
||||||
Index,
|
|
||||||
Recipe,
|
|
||||||
Search,
|
|
||||||
SignIn,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Template, derive_more::Deref)]
|
|
||||||
#[template(path = "recipe_card.html")]
|
|
||||||
struct RecipeCard(entities::recipies::Model);
|
|
||||||
|
|
||||||
#[derive(Debug, Template)]
|
|
||||||
#[template(path = "index.html")]
|
|
||||||
struct IndexTemplate {
|
|
||||||
recipies: Vec<RecipeCard>,
|
|
||||||
count: u64,
|
|
||||||
session: Option<User>,
|
|
||||||
page: Page,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Template)]
|
|
||||||
#[template(path = "top_bar.html")]
|
|
||||||
struct TopBar<'s> {
|
|
||||||
session: &'s Option<User>,
|
|
||||||
}
|
|
||||||
impl<'s> TopBar<'s> {
|
|
||||||
pub fn new(session: &'s Option<User>) -> Self {
|
|
||||||
tracing::info!("top bar session: {session:?}");
|
|
||||||
Self { session }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Admin {
|
|
||||||
pub email: String,
|
|
||||||
pub pass: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Admin {
|
|
||||||
type Err = ();
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let mut it = s.split(':');
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
email: it.next().expect("Admin login is required").into(),
|
|
||||||
pass: it.next().expect("Admin password is required").into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Admins(Vec<Admin>);
|
|
||||||
|
|
||||||
impl FromStr for Admins {
|
|
||||||
type Err = ();
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Ok(Self(
|
|
||||||
s.trim().split(',').filter_map(|s| s.parse().ok()).collect(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
|
||||||
struct CreateRecipe {
|
|
||||||
// #[serde(default)]
|
|
||||||
// #[serde(with = "humantime_serde")]
|
|
||||||
// time: Option<chrono::Duration>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
|
||||||
struct SignIn {
|
|
||||||
email: String,
|
|
||||||
password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Template)]
|
|
||||||
#[template(path = "sign_in/form.html")]
|
|
||||||
struct SignInForm {
|
|
||||||
not_found: bool,
|
|
||||||
email: String,
|
|
||||||
session: Option<User>,
|
|
||||||
page: Page,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/sign-in")]
|
|
||||||
async fn render_sign_in() -> SignInForm {
|
|
||||||
SignInForm {
|
|
||||||
not_found: false,
|
|
||||||
email: "".into(),
|
|
||||||
session: None,
|
|
||||||
page: Page::SignIn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/sign-in")]
|
|
||||||
async fn sign_in(
|
|
||||||
req: HttpRequest,
|
|
||||||
admins: Data<Admins>,
|
|
||||||
payload: Form<SignIn>,
|
|
||||||
admin: Option<Identity>,
|
|
||||||
) -> HttpResponse {
|
|
||||||
let payload = payload.into_inner();
|
|
||||||
let is_admin = admins
|
|
||||||
.as_ref()
|
|
||||||
.0
|
|
||||||
.iter()
|
|
||||||
.any(|admin| admin.email == payload.email && admin.pass == payload.password);
|
|
||||||
if is_admin {
|
|
||||||
tracing::info!("Valid credentials");
|
|
||||||
// let res = session
|
|
||||||
// .insert(SESSION_KEY, payload.email.clone())
|
|
||||||
// .inspect_err(|e| tracing::error!("Failed to save session: {e}"));
|
|
||||||
// tracing::debug!("Saving session res: {res:?}");
|
|
||||||
let _s =
|
|
||||||
Identity::login(&req.extensions(), payload.email).expect("Failed to store session");
|
|
||||||
|
|
||||||
HttpResponse::SeeOther()
|
|
||||||
.append_header(("location", "/"))
|
|
||||||
.finish()
|
|
||||||
} else {
|
|
||||||
tracing::warn!("Invalid credentials");
|
|
||||||
HttpResponse::BadRequest()
|
|
||||||
.append_header(("Content-Type", "text/html"))
|
|
||||||
.body(
|
|
||||||
SignInForm {
|
|
||||||
email: payload.email,
|
|
||||||
not_found: true,
|
|
||||||
session: admin.and_then(|s| s.id().ok()),
|
|
||||||
page: Page::SignIn,
|
|
||||||
}
|
|
||||||
.render()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
struct Padding {
|
|
||||||
page: Option<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/")]
|
|
||||||
async fn index_html(
|
|
||||||
db: Data<DatabaseConnection>,
|
|
||||||
q: actix_web::web::Query<Padding>,
|
|
||||||
admin: Option<Identity>,
|
|
||||||
) -> IndexTemplate {
|
|
||||||
let count = (entities::prelude::Recipies::find()
|
|
||||||
.count(&**db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default() as f64
|
|
||||||
/ 20.0)
|
|
||||||
.ceil() as u64;
|
|
||||||
let recipies = entities::prelude::Recipies::find()
|
|
||||||
.limit(20)
|
|
||||||
.offset(q.page.unwrap_or_default() as u64)
|
|
||||||
.all(&**db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
IndexTemplate {
|
|
||||||
recipies: recipies.into_iter().map(RecipeCard).collect(),
|
|
||||||
count,
|
|
||||||
session: admin.and_then(|s| s.id().ok()),
|
|
||||||
page: Page::Index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Template)]
|
|
||||||
#[template(path = "recipies/show.html")]
|
|
||||||
struct RecipeDetailTemplate {
|
|
||||||
recipe: entities::recipies::Model,
|
|
||||||
tags: Vec<entities::recipe_tags::Model>,
|
|
||||||
steps: Vec<entities::recipe_steps::Model>,
|
|
||||||
ingeredients: Vec<entities::recipe_ingeredients::Model>,
|
|
||||||
session: Option<User>,
|
|
||||||
page: Page,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/recipe/{id}")]
|
|
||||||
async fn show(
|
|
||||||
id: Path<i64>,
|
|
||||||
db: Data<DatabaseConnection>,
|
|
||||||
admin: Option<Identity>,
|
|
||||||
) -> impl Responder {
|
|
||||||
let id = id.into_inner();
|
|
||||||
let db = &**db;
|
|
||||||
let Ok(Some(recipe)) = entities::prelude::Recipies::find()
|
|
||||||
.filter(entities::recipies::Column::Id.eq(id))
|
|
||||||
.one(db)
|
|
||||||
.await
|
|
||||||
else {
|
|
||||||
return HttpResponse::SeeOther()
|
|
||||||
.append_header(("location", "/"))
|
|
||||||
.finish();
|
|
||||||
};
|
|
||||||
let tags = entities::prelude::RecipeTags::find()
|
|
||||||
.filter(entities::recipe_tags::Column::RecipeId.eq(id))
|
|
||||||
.all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default();
|
|
||||||
let steps = entities::prelude::RecipeSteps::find()
|
|
||||||
.filter(entities::recipe_steps::Column::RecipeId.eq(id))
|
|
||||||
.all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default();
|
|
||||||
let ingeredients = entities::prelude::RecipeIngeredients::find()
|
|
||||||
.filter(entities::recipe_ingeredients::Column::RecipeId.eq(id))
|
|
||||||
.all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
HttpResponse::Ok().body(
|
|
||||||
RecipeDetailTemplate {
|
|
||||||
steps,
|
|
||||||
tags,
|
|
||||||
recipe,
|
|
||||||
ingeredients,
|
|
||||||
session: admin.and_then(|s| s.id().ok()),
|
|
||||||
page: Page::Recipe,
|
|
||||||
}
|
|
||||||
.render()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/styles.css")]
|
|
||||||
async fn styles_css() -> HttpResponse {
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.append_header(("Content-Type", "text/css"))
|
|
||||||
.body(include_str!("../assets/styles.css"))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let _ = tracing_subscriber::fmt::init();
|
let _ = tracing_subscriber::fmt::init();
|
||||||
@ -280,7 +34,7 @@ async fn main() {
|
|||||||
std::env::var("PSQL").expect("PSQL is required. Please provide postgresql connection url");
|
std::env::var("PSQL").expect("PSQL is required. Please provide postgresql connection url");
|
||||||
let redis_url =
|
let redis_url =
|
||||||
std::env::var("REDIS").expect("REDIS is required. Pleasde provide redis connection url");
|
std::env::var("REDIS").expect("REDIS is required. Pleasde provide redis connection url");
|
||||||
let secret =
|
let mut secret =
|
||||||
std::env::var("SECRET").expect("SECRET is required. Please provider encryption key");
|
std::env::var("SECRET").expect("SECRET is required. Please provider encryption key");
|
||||||
|
|
||||||
// Build structs
|
// Build structs
|
||||||
@ -295,6 +49,11 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
let redis = redis::Client::open(redis_url.as_str()).expect("Failed to connect to redis");
|
let redis = redis::Client::open(redis_url.as_str()).expect("Failed to connect to redis");
|
||||||
let secret_key = Key::from(secret.as_bytes());
|
let secret_key = Key::from(secret.as_bytes());
|
||||||
|
unsafe {
|
||||||
|
for c in secret.as_bytes_mut() {
|
||||||
|
*c = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
drop(secret);
|
drop(secret);
|
||||||
tracing::info!("{:?}", secret_key.master());
|
tracing::info!("{:?}", secret_key.master());
|
||||||
let redis_store = RedisSessionStore::new(redis_url.as_str()).await.unwrap();
|
let redis_store = RedisSessionStore::new(redis_url.as_str()).await.unwrap();
|
||||||
@ -321,12 +80,8 @@ async fn main() {
|
|||||||
.app_data(admins.clone())
|
.app_data(admins.clone())
|
||||||
.app_data(db.clone())
|
.app_data(db.clone())
|
||||||
.app_data(redis.clone())
|
.app_data(redis.clone())
|
||||||
.service(styles_css)
|
|
||||||
.service(Files::new("/pages", "./pages"))
|
.service(Files::new("/pages", "./pages"))
|
||||||
.service(render_sign_in)
|
.configure(routes::configure)
|
||||||
.service(sign_in)
|
|
||||||
.service(index_html)
|
|
||||||
.service(show)
|
|
||||||
})
|
})
|
||||||
.bind(&bind)
|
.bind(&bind)
|
||||||
.expect("Failed to start http server")
|
.expect("Failed to start http server")
|
||||||
|
256
src/routes.rs
Normal file
256
src/routes.rs
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
use crate::types::*;
|
||||||
|
use crate::{entities, filters};
|
||||||
|
use actix_identity::Identity;
|
||||||
|
use actix_web::web::{Data, Form, Path};
|
||||||
|
use actix_web::HttpMessage;
|
||||||
|
use actix_web::{get, post, HttpRequest, HttpResponse, Responder};
|
||||||
|
use askama::Template;
|
||||||
|
use sea_orm::{prelude::*, QuerySelect};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Template, derive_more::Deref)]
|
||||||
|
#[template(path = "recipe_card.html")]
|
||||||
|
struct RecipeCard(entities::recipies::Model);
|
||||||
|
|
||||||
|
#[derive(Debug, Template)]
|
||||||
|
#[template(path = "index.html")]
|
||||||
|
struct IndexTemplate {
|
||||||
|
recipies: Vec<RecipeCard>,
|
||||||
|
count: u64,
|
||||||
|
session: Option<User>,
|
||||||
|
page: Page,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Template)]
|
||||||
|
#[template(path = "search.html")]
|
||||||
|
struct SearchTemplate {
|
||||||
|
recipies: Vec<RecipeCard>,
|
||||||
|
count: u64,
|
||||||
|
query: String,
|
||||||
|
session: Option<User>,
|
||||||
|
page: Page,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Template)]
|
||||||
|
#[template(path = "top_bar.html")]
|
||||||
|
struct TopBar<'s> {
|
||||||
|
session: &'s Option<User>,
|
||||||
|
}
|
||||||
|
impl<'s> TopBar<'s> {
|
||||||
|
pub fn new(session: &'s Option<User>) -> Self {
|
||||||
|
tracing::info!("top bar session: {session:?}");
|
||||||
|
Self { session }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Template)]
|
||||||
|
#[template(path = "sign_in/form.html")]
|
||||||
|
struct SignInForm {
|
||||||
|
not_found: bool,
|
||||||
|
email: String,
|
||||||
|
session: Option<User>,
|
||||||
|
page: Page,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/sign-in")]
|
||||||
|
async fn render_sign_in() -> SignInForm {
|
||||||
|
SignInForm {
|
||||||
|
not_found: false,
|
||||||
|
email: "".into(),
|
||||||
|
session: None,
|
||||||
|
page: Page::SignIn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/sign-in")]
|
||||||
|
async fn sign_in(
|
||||||
|
req: HttpRequest,
|
||||||
|
admins: Data<Admins>,
|
||||||
|
payload: Form<SignIn>,
|
||||||
|
admin: Option<Identity>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let payload = payload.into_inner();
|
||||||
|
let is_admin = admins
|
||||||
|
.as_ref()
|
||||||
|
.iter()
|
||||||
|
.any(|admin| admin.email == payload.email && admin.pass == payload.password);
|
||||||
|
if is_admin {
|
||||||
|
tracing::info!("Valid credentials");
|
||||||
|
// let res = session
|
||||||
|
// .insert(SESSION_KEY, payload.email.clone())
|
||||||
|
// .inspect_err(|e| tracing::error!("Failed to save session: {e}"));
|
||||||
|
// tracing::debug!("Saving session res: {res:?}");
|
||||||
|
let _s =
|
||||||
|
Identity::login(&req.extensions(), payload.email).expect("Failed to store session");
|
||||||
|
|
||||||
|
HttpResponse::SeeOther()
|
||||||
|
.append_header(("location", "/"))
|
||||||
|
.finish()
|
||||||
|
} else {
|
||||||
|
tracing::warn!("Invalid credentials");
|
||||||
|
HttpResponse::BadRequest()
|
||||||
|
.append_header(("Content-Type", "text/html"))
|
||||||
|
.body(
|
||||||
|
SignInForm {
|
||||||
|
email: payload.email,
|
||||||
|
not_found: true,
|
||||||
|
session: admin.and_then(|s| s.id().ok()),
|
||||||
|
page: Page::SignIn,
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct CreateRecipe {
|
||||||
|
// #[serde(default)]
|
||||||
|
// #[serde(with = "humantime_serde")]
|
||||||
|
// time: Option<chrono::Duration>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
struct SignIn {
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Padding {
|
||||||
|
page: Option<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/search")]
|
||||||
|
async fn search_page(admin: Option<Identity>) -> SearchTemplate {
|
||||||
|
SearchTemplate {
|
||||||
|
query: "".into(),
|
||||||
|
recipies: Vec::new(),
|
||||||
|
count: 0,
|
||||||
|
session: admin.and_then(|a| a.id().ok()),
|
||||||
|
page: Page::Search,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct SearchQuery {
|
||||||
|
q: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/search")]
|
||||||
|
async fn search_results(
|
||||||
|
q: actix_web::web::Query<SearchQuery>,
|
||||||
|
admin: Option<Identity>,
|
||||||
|
) -> SearchTemplate {
|
||||||
|
let query = q.into_inner().q;
|
||||||
|
SearchTemplate {
|
||||||
|
query: "".into(),
|
||||||
|
recipies: Vec::new(),
|
||||||
|
count: 0,
|
||||||
|
session: admin.and_then(|a| a.id().ok()),
|
||||||
|
page: Page::Search,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn index_html(
|
||||||
|
db: Data<DatabaseConnection>,
|
||||||
|
q: actix_web::web::Query<Padding>,
|
||||||
|
admin: Option<Identity>,
|
||||||
|
) -> IndexTemplate {
|
||||||
|
let count = (entities::prelude::Recipies::find()
|
||||||
|
.count(&**db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default() as f64
|
||||||
|
/ 20.0)
|
||||||
|
.ceil() as u64;
|
||||||
|
let recipies = entities::prelude::Recipies::find()
|
||||||
|
.limit(20)
|
||||||
|
.offset(q.page.unwrap_or_default() as u64)
|
||||||
|
.all(&**db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
IndexTemplate {
|
||||||
|
recipies: recipies.into_iter().map(RecipeCard).collect(),
|
||||||
|
count,
|
||||||
|
session: admin.and_then(|s| s.id().ok()),
|
||||||
|
page: Page::Index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Template)]
|
||||||
|
#[template(path = "recipies/show.html")]
|
||||||
|
struct RecipeDetailTemplate {
|
||||||
|
recipe: entities::recipies::Model,
|
||||||
|
tags: Vec<entities::recipe_tags::Model>,
|
||||||
|
steps: Vec<entities::recipe_steps::Model>,
|
||||||
|
ingeredients: Vec<entities::recipe_ingeredients::Model>,
|
||||||
|
session: Option<User>,
|
||||||
|
page: Page,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/recipe/{id}")]
|
||||||
|
async fn show(
|
||||||
|
id: Path<i64>,
|
||||||
|
db: Data<DatabaseConnection>,
|
||||||
|
admin: Option<Identity>,
|
||||||
|
) -> impl Responder {
|
||||||
|
let id = id.into_inner();
|
||||||
|
let db = &**db;
|
||||||
|
let Ok(Some(recipe)) = entities::prelude::Recipies::find()
|
||||||
|
.filter(entities::recipies::Column::Id.eq(id))
|
||||||
|
.one(db)
|
||||||
|
.await
|
||||||
|
else {
|
||||||
|
return HttpResponse::SeeOther()
|
||||||
|
.append_header(("location", "/"))
|
||||||
|
.finish();
|
||||||
|
};
|
||||||
|
let tags = entities::prelude::RecipeTags::find()
|
||||||
|
.filter(entities::recipe_tags::Column::RecipeId.eq(id))
|
||||||
|
.all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
let steps = entities::prelude::RecipeSteps::find()
|
||||||
|
.filter(entities::recipe_steps::Column::RecipeId.eq(id))
|
||||||
|
.all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
let ingeredients = entities::prelude::RecipeIngeredients::find()
|
||||||
|
.filter(entities::recipe_ingeredients::Column::RecipeId.eq(id))
|
||||||
|
.all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
HttpResponse::Ok().body(
|
||||||
|
RecipeDetailTemplate {
|
||||||
|
steps,
|
||||||
|
tags,
|
||||||
|
recipe,
|
||||||
|
ingeredients,
|
||||||
|
session: admin.and_then(|s| s.id().ok()),
|
||||||
|
page: Page::Recipe,
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/styles.css")]
|
||||||
|
async fn styles_css() -> HttpResponse {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.append_header(("Content-Type", "text/css"))
|
||||||
|
.body(include_str!("../assets/styles.css"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure(config: &mut actix_web::web::ServiceConfig) {
|
||||||
|
config
|
||||||
|
.service(styles_css)
|
||||||
|
.service(render_sign_in)
|
||||||
|
.service(sign_in)
|
||||||
|
.service(index_html)
|
||||||
|
.service(show)
|
||||||
|
.service(search_page)
|
||||||
|
.service(search_results);
|
||||||
|
}
|
42
src/types.rs
Normal file
42
src/types.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
pub type User = String;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
pub enum Page {
|
||||||
|
Index,
|
||||||
|
Recipe,
|
||||||
|
Search,
|
||||||
|
SignIn,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Admin {
|
||||||
|
pub email: String,
|
||||||
|
pub pass: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Admin {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut it = s.split(':');
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
email: it.next().expect("Admin login is required").into(),
|
||||||
|
pass: it.next().expect("Admin password is required").into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, derive_more::Deref)]
|
||||||
|
pub struct Admins(Vec<Admin>);
|
||||||
|
|
||||||
|
impl FromStr for Admins {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Self(
|
||||||
|
s.trim().split(',').filter_map(|s| s.parse().ok()).collect(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
@ -23,10 +23,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endmatch %}
|
{% endmatch %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<div class="btn bg-secondary-100 text-secondary-200 hover:shadow-inner transform hover:scale-110 hover:bg-opacity-60 transition ease-out duration-300">Load More</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{% block navigation %}
|
{% block navigation %}
|
||||||
<!-- navigation -->
|
<!-- navigation -->
|
||||||
<div class="md:col-span-1 md:flex md:justify-end">
|
<div class="md:col-span-1 md:flex md:justify-end">
|
||||||
<nav class="text-right">
|
<nav class="text-right flex flex-col gap-8">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<h1 class="font-bold uppercase p-4 border-b border-gray-100">
|
<h1 class="font-bold uppercase p-4 border-b border-gray-100">
|
||||||
<a href="/" class="hover:text-gray-600">Cooked</a>
|
<a href="/" class="hover:text-gray-600">Cooked</a>
|
||||||
@ -12,9 +12,9 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="text-sm mt-6 hidden md:block" id="menu">
|
<ul class="text-sm hidden md:flex flex-col gap-8" id="menu">
|
||||||
<li class="text-gray-700 font-bold py-2">
|
<li>
|
||||||
<a href="/" class="px-4 flex justify-end border-r-4 border-primary hover:shadow-md">
|
<a href="/" class="px-4 flex justify-end hover:shadow-md {{(page, Page::Index)|page_class}}">
|
||||||
<span>Home</span>
|
<span>Home</span>
|
||||||
<svg class="w-5 ml-2" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
<svg class="w-5 ml-2" xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path
|
<path
|
||||||
@ -23,7 +23,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/search" class="px-4 flex justify-end border-r-4 border-primary hover:shadow-md">
|
<a href="/search" class="px-4 flex justify-end hover:shadow-md {{(page, Page::Search)|page_class}}">
|
||||||
<span>Search</span>
|
<span>Search</span>
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-5 ml-2">
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="w-5 ml-2">
|
||||||
<rect width="24" height="24" fill="white"/>
|
<rect width="24" height="24" fill="white"/>
|
||||||
|
35
templates/search.html
Normal file
35
templates/search.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<main class="md:col-span-3 flex flex-col gap-4 m-4">
|
||||||
|
{{ TopBar::new(session)|safe }}
|
||||||
|
|
||||||
|
<header class="flex flex-col gap-2">
|
||||||
|
<h2 class="text-gray-600 text-6xl font-semibold text-center">Search</h2>
|
||||||
|
</header>
|
||||||
|
<div class="flex flex-col gap-8">
|
||||||
|
<div class="flex flex-wrap gap-8 justify-center">
|
||||||
|
<form action="/search" method="post">
|
||||||
|
<input name="q" class="input input-search" type="search" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-8">
|
||||||
|
<div class="flex flex-wrap gap-8 justify-center">
|
||||||
|
{% for recipe in recipies %}
|
||||||
|
{{ recipe|safe }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-4 flex-wrap">
|
||||||
|
{% match count %}
|
||||||
|
{% when 0 %}
|
||||||
|
{% when _ %}
|
||||||
|
{% for page in 0..count %}
|
||||||
|
<a class="btn" href="/?page={{page}}">{{page}}</a>
|
||||||
|
{% endfor %}
|
||||||
|
{% endmatch %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue
Block a user