summaryrefslogtreecommitdiff
path: root/bin/serve.fnl
diff options
context:
space:
mode:
authorunwox <me@unwox.com>2024-10-15 14:48:16 +0600
committerunwox <me@unwox.com>2024-10-15 14:49:36 +0600
commit1204496efa2fcd495bd74ba8ca249b7f082f3ba5 (patch)
tree57f908c7bae9ff1194ccb68328ec59ef5be00872 /bin/serve.fnl
parent1520d83acccdcad2d7f87aec073b48d5f4995bf6 (diff)
WIP try to fix spelling mistakes in search querie
currently works very slowly and uses a lot of CPU
Diffstat (limited to 'bin/serve.fnl')
-rw-r--r--bin/serve.fnl237
1 files changed, 155 insertions, 82 deletions
diff --git a/bin/serve.fnl b/bin/serve.fnl
index 4d982c2..4cbb905 100644
--- a/bin/serve.fnl
+++ b/bin/serve.fnl
@@ -9,6 +9,9 @@
(local json (require :vendor.json))
(local array (require :lib.array))
(local str (require :lib.string))
+(local texts (require :texts))
+(local spellfix (require :spellfix))
+(local {: must} (require :lib.utils))
(local ozchai (require :parser.ozchai))
(local ipuer (require :parser.ipuer))
@@ -17,7 +20,7 @@
(when _G.unpack
(tset table :unpack _G.unpack))
-(local db (luna.db.open "file:var/db.sqlite?_journal=WAL&_sync=NORMAL"))
+(local db (must (luna.db.open "file:var/db.sqlite?_journal=WAL&_sync=NORMAL")))
(local query-synonyms {
"шэн" "шен"
@@ -36,9 +39,66 @@
(string.gsub "&quot;" "\"")
(string.gsub "&amp;" "&"))))
+(fn get-query-string [query key]
+ (if (and query
+ (. query key)
+ (. query key 1)
+ (< 0 (# (. query key 1))))
+ (. query key 1)
+ nil))
+
+(fn get-query-number [query key]
+ (if (and query
+ (. query key)
+ (. query key 1)
+ (< 0 (# (. query key 1))))
+ (tonumber (. query key 1))
+ nil))
+
+(fn collect-form [params]
+ {:query (str.trim (or (get-query-string params "query") ""))
+ :tags (filter #(~= "" $2) (or params.tags []))
+ :min-price (get-query-number params "min-price")
+ :max-price (get-query-number params "max-price")
+ :price-per (= "on" (get-query-string params "price-per"))})
+
+(fn form-empty? [form]
+ (and
+ (= "" form.query)
+ (= (# form.tags) 0)
+ (not form.min-price)
+ (not form.max-price)
+ ;; price-per is intentionally left out since it must not trigger search
+ ;; by itself
+ ))
+
+(fn form->path [page form]
+ (.. "?page=" (tostring page)
+ (if (not (str.empty? form.query))
+ (.. "&query=" form.query)
+ "")
+ (if (< 0 (# form.tags))
+ (.. "&"
+ (array.join
+ (array.flatten
+ (map (fn [_ tag] (.. "tags=" tag))
+ form.tags))
+ "&"))
+ "")
+ (if (and form.min-price (< 0 form.min-price))
+ (.. "&min-price=" (tostring form.min-price))
+ "")
+ (if (and form.max-price (< 0 form.max-price))
+ (.. "&max-price=" (tostring form.max-price))
+ "")
+ (if form.price-per
+ "&price-per=on"
+ "")))
+
(fn random-products [limit]
(assert (< 0 limit) "limit must be > 0")
- (luna.db.query*
+ (must
+ (luna.db.query*
db
"SELECT site,
title,
@@ -52,17 +112,18 @@
FROM products
ORDER BY RANDOM()
LIMIT ?"
- [limit]))
+ [limit])))
(fn all-tags []
(map
(fn [_ v] (. v 1))
- (luna.db.query
+ (must
+ (luna.db.query
db
"SELECT title FROM tags ORDER BY creation_time"
- [])))
+ []))))
-(fn query-products [page query tags]
+(fn query-products [{: query : tags : min-price : max-price : price-per} page]
(local tags (or tags []))
(var where-conds [])
@@ -81,42 +142,59 @@
(.. q "*")))
(str.split query))
" AND ")))
- (local where-sql (array.join where-conds "\nAND "))
+ (when (and min-price (< 0 min-price))
+ (if price-per
+ (table.insert where-conds "products.price_per >= ?")
+ (table.insert where-conds "products.price >= ?"))
+ (table.insert where-vars min-price))
+ (when (and max-price (< 0 max-price))
+ (if price-per
+ (table.insert where-conds "products.price_per <= ?")
+ (table.insert where-conds "products.price <= ?"))
+ (table.insert where-vars max-price))
+
+ (local where-sql
+ (if (< 0 (# where-conds))
+ (.. "AND " (array.join where-conds "\nAND "))
+ ""))
(local total
- (luna.db.query
- db
- (string.format
- "SELECT count(*)
- FROM search
- LEFT JOIN product_tags ON product_tags.product = search.fid
- WHERE search.`table` = 'products'
- AND %s" where-sql)
- where-vars))
+ (must
+ (luna.db.query
+ db
+ (string.format
+ "SELECT count(*)
+ FROM search
+ INNER JOIN products ON search.fid = products.url
+ LEFT JOIN product_tags ON product_tags.product = search.fid
+ WHERE search.`table` = 'products'
+ %s" where-sql)
+ where-vars)))
{:results
+ (must
(luna.db.query*
- db
- (string.format
- "SELECT highlight(search, 0, '<i>', '</i>') AS \"title\",
- products.site,
- products.description,
- products.image,
- products.url,
- products.price,
- products.weight,
- products.price_per AS \"price-per\",
- products.year,
- products.archived,
- products.creation_time AS \"creation-time\"
- FROM search
- INNER JOIN products ON search.fid = products.url
- LEFT JOIN product_tags ON product_tags.product = products.url
- WHERE search.`table` = 'products'
- AND %s
- ORDER BY rank
- LIMIT 48 OFFSET ?" where-sql)
- (array.concat where-vars [(* (- page 1) 48)]))
+ db
+ (string.format
+ "SELECT highlight(search, 0, '<i>', '</i>') AS \"title\",
+ products.site,
+ products.description,
+ products.image,
+ products.url,
+ products.price,
+ products.weight,
+ products.price_per AS \"price-per\",
+ products.year,
+ products.archived,
+ products.creation_time AS \"creation-time\"
+ FROM search
+ INNER JOIN products ON search.fid = products.url
+ LEFT JOIN product_tags ON product_tags.product = products.url
+ WHERE search.`table` = 'products'
+ %s
+ ORDER BY rank
+ LIMIT 48 OFFSET ?" where-sql)
+ (array.concat where-vars [(* (- page 1) 48)])))
:total (if (< 0 (# total))
(. total 1 1)
0)})
@@ -170,48 +248,39 @@
;; FIXME: security issue
[:small {} [:NO-ESCAPE (unescape (str.truncate product.description 200))]]])
-(fn paginator-template [query tags page limit total]
+(fn paginator-template [form page limit total]
(local last-page (math.ceil (/ total limit)))
- (fn make-url [page query tags]
- (.. "?page=" (tostring page)
- "&query=" query
- "&"
- (array.join
- (array.flatten
- (map (fn [_ tag] (.. "tags=" tag))
- tags))
- "&")))
(if (< limit total)
[:div {:class "paginator"}
[:div {:class "paginator-numbers"}
(if (< 1 page)
- [:a {:href (make-url (- page 1) query tags)} "←"]
+ [:a {:href (form->path (- page 1) form)} "←"]
"")
(faccumulate [res [:span {}] i 1 last-page]
(do
(table.insert
- res [:a {:href (make-url i query tags)
+ res [:a {:href (form->path i form)
:class (if (= page i) "paginator-active" "")}
(tostring i)])
res))
(if (< page last-page)
- [:a {:href (make-url (+ page 1) query tags)} "→"]
+ [:a {:href (form->path (+ page 1) form)} "→"]
"")]
[:div {} "Всего результатов: " [:strong {} (string.format "%d" total)]]]
""))
-(fn aside-template [query tags paginator]
+(fn aside-template [form paginator]
[:aside {:class "aside"}
[:div {:class "aside-content"}
- (if (or (~= query "") (< 0 (# tags)))
+ (if (not (form-empty? form))
[:a {:href "/" :style "display: block;"}
[:img {:class "logo" :src "static/logo.svg"
:alt "Логотип meicha.ru" :title "Логотип meicha.ru"}]]
[:img {:class "logo" :src "static/logo.svg"
:alt "Логотип meicha.ru" :title "Логотип meicha.ru"}])
[:form {:class "form"}
- [:input {:type :search :name :query :value query
+ [:input {:type :search :name "query" :value form.query
:autofocus true :placeholder "Поисковый запрос"}]
[:div {:class "form-tags"}
[:select {:class "form-tag" :name "tags"}
@@ -220,14 +289,28 @@
(map
(fn [_ tag]
[:option {:value tag
- :selected (if (array.contains tags tag) "selected" nil)}
+ :selected (if (array.contains form.tags tag)
+ "selected" nil)}
tag])
(all-tags)))]]
+ [:div {:class "form-price"}
+ [:input {:type :number :name :min-price :min "1"
+ :placeholder "От ₽" :value (tostring form.min-price)}]
+ [:input {:type :number :name :max-price :min "1"
+ :placeholder "До ₽" :value (tostring form.max-price)}]]
+ [:div {:class "form-price-per"}
+ [:input {:type :checkbox :id "price-per" :name "price-per"
+ :checked (if form.price-per "checked" nil)}]
+ [:label {:for "price-per"} "цена за грамм"]]
[:button {:type :submit} "Искать"]]
paginator]])
-(fn base-template [query tags page total ...]
- (local paginator (paginator-template query tags page 48 total))
+(fn base-template [form page total items]
+ (local paginator (paginator-template form page 48 total))
+ (local spellfix-suggestion
+ (if (and (not (str.empty? form.query)) items (< 0 (# items)))
+ nil
+ (spellfix.guess form.query)))
[:html {:lang "en"}
[:head {}
[:meta {:charset "UTF-8"}]
@@ -236,44 +319,34 @@
[:body {}
[:div {:class "container"}
[:div {:class "content"}
- (aside-template query tags paginator)
+ (aside-template form paginator)
[:section {}
- [:div {:class "list"} ...]
+ (if (< 0 (# items))
+ [:div {:class "list"}
+ (table.unpack (map #(item-template $2) items))]
+ (if spellfix-suggestion
+ [:NO-ESCAPE
+ (string.format texts.no-results-with-suggestion
+ spellfix-suggestion
+ spellfix-suggestion)]
+ texts.no-results))
[:footer {} paginator]]]]]])
-(fn get-query-string [query key]
- (if (and query
- (. query key)
- (. query key 1)
- (< 0 (# (. query key 1))))
- (. query key 1)
- nil))
-
-(fn get-query-number [query key]
- (if (and query
- (. query key)
- (. query key 1))
- (tonumber (. query key 1))
- nil))
-
(fn root-handler [{: path : query}]
(if (= path "/")
(let [headers {:content-type "text/html"}
page (or (get-query-number query "page") 1)
- search (str.trim (or (get-query-string query "query") ""))
- tags (filter #(~= "" $2) (or query.tags []))
+ form (collect-form query)
{: results : total}
- (if (or (~= "" search) (< 0 (# tags)))
- (query-products page search tags)
+ (if (not (form-empty? form))
+ (query-products form page)
{:total 48 :results (random-products 48)})]
(values
200 headers
(html.render
- (base-template
- search tags page total
- (table.unpack (map #(item-template $2) results)))
+ (base-template form page total results)
true)))
(values 404 {} "not found")))
-(luna.router.route "GET /" root-handler)
-(luna.router.static "GET /static/" "static/")
+(must (luna.router.route "GET /" root-handler))
+(must (luna.router.static "GET /static/" "static/"))