diff options
| -rw-r--r-- | bin/serve.fnl | 3 | ||||
| -rw-r--r-- | dicts.fnl | 2 | ||||
| -rw-r--r-- | pages/shop/_product/edit.fnl | 7 | ||||
| -rw-r--r-- | pages/shop/add.fnl | 5 | ||||
| -rw-r--r-- | pages/shop/index.fnl | 92 | ||||
| -rwxr-xr-x | run.sh | 4 | ||||
| -rw-r--r-- | shop.fnl | 11 | ||||
| -rw-r--r-- | static/style.css | 4 |
8 files changed, 98 insertions, 30 deletions
diff --git a/bin/serve.fnl b/bin/serve.fnl index 0948a83..a1d1737 100644 --- a/bin/serve.fnl +++ b/bin/serve.fnl @@ -43,6 +43,9 @@ expires_at TEXT NOT NULL ); + CREATE VIRTUAL TABLE IF NOT EXISTS products_search + USING fts5(title, short_description, name); + CREATE TABLE IF NOT EXISTS products( name TEXT PRIMARY KEY, creation_time TEXT NOT NULL, @@ -10,7 +10,7 @@ {:value "liu-bao" :label "Лю бао"} {:value "teapot" :label "Чайник"} {:value "gaiwan" :label "Гайвань"} - {:value "pourer" :label "Чахай"}])]) + {:value "pourer" :label "Чахай"}]) (local tea-season [{:value "spring" :label "Весна"} diff --git a/pages/shop/_product/edit.fnl b/pages/shop/_product/edit.fnl index 3e4f2f0..3995096 100644 --- a/pages/shop/_product/edit.fnl +++ b/pages/shop/_product/edit.fnl @@ -2,6 +2,7 @@ (local templates (require :templates)) (local {: product-form} (require :pages.shop.add)) (local forms (require :forms)) +(local shop (require :shop)) (local lib (require :lib)) (fn find-product [db name] @@ -69,8 +70,10 @@ authenticated?))) (do (lib.with-tx db - (fn [tx] (update-product tx product-form data - {:name request.params._product}))) + (fn [tx] + (update-product tx product-form data + {:name request.params._product}) + (shop.update-search-index tx))) (values 302 {:Location (.. "/shop/" data.name)} "")))) (values 200 {} (templates.base diff --git a/pages/shop/add.fnl b/pages/shop/add.fnl index 01b83d6..460cc81 100644 --- a/pages/shop/add.fnl +++ b/pages/shop/add.fnl @@ -1,6 +1,7 @@ (import-macros {:compile-html HTML} :macros) (local templates (require :templates)) (local dicts (require :dicts)) +(local shop (require :shop)) (local forms (require :forms)) (local lib (require :lib)) @@ -88,7 +89,9 @@ authenticated?))) (do (lib.with-tx db - (fn [tx] (insert-product tx product-form request.form))) + (fn [tx] + (insert-product tx product-form request.form) + (shop.update-search-index tx))) (values 302 {:Location "/shop"} "")))) (values 200 {} (templates.base (content product-form {} {} authenticated?)))))) diff --git a/pages/shop/index.fnl b/pages/shop/index.fnl index da83114..29d93b6 100644 --- a/pages/shop/index.fnl +++ b/pages/shop/index.fnl @@ -4,11 +4,33 @@ (local dicts (require :dicts)) (local templates (require :templates)) -(fn all-products [db authenticated?] +(fn all-products [db filters authenticated?] + (local where-stmts []) + (local where-args []) + (var has-search-query? false) + + (when (not authenticated?) + (table.insert where-stmts "products.published = true")) + + (when filters.type + (table.insert where-stmts "products.type = ?") + (table.insert where-args filters.type)) + + (when filters.search + (table.insert where-stmts "products_search MATCH ?") + (table.insert where-args filters.search) + (set has-search-query? true)) + (local where - (if (not authenticated?) - "WHERE products.published = true" + (if (< 0 (# where-stmts)) + (.. "WHERE " (table.concat where-stmts " AND\n")) "")) + + (local from-sql + (if has-search-query? + "products_search INNER JOIN products ON products_search.name = products.name" + "products")) + (_G.must (luna.db.query-assoc db (.. @@ -27,9 +49,12 @@ products.image3, products.image4, products.image5 - FROM products " + FROM " from-sql " " where - " ORDER BY products.position") []))) + " ORDER BY products.position ASC, + products.creation_time DESC" + (if has-search-query? ", rank" "")) + where-args))) (fn item-template [product basket] (local item-url (.. "/shop/" product.name)) @@ -65,7 +90,7 @@ (templates.product-overview product "mb-0-25 font-size-0-875") [:div {} product.short-description]])) -(fn content [db basket authenticated?] +(fn content [db products filters basket authenticated?] [(HTML [:aside {} (templates.header "/shop" authenticated?) @@ -77,25 +102,46 @@ [:div {:class "content"} [:div {:class "back"} [:a {:href "/"} "⟵ Обратно на главную"]] [:h2 {:class "product-page-title"} "Магазин"] - (if authenticated? - (HTML - [:div {:class "mb-1" :style "margin-top: -0.5rem"} - [:a {:style "white-space: nowrap" - :href (.. "/shop/add")} "+ Добавить"] - [:a {:style "white-space: nowrap; margin-left: 1rem;" - :href (.. "/shop/order/list")} "☰ Список заказов"]]) - "") - [:div {:class "shop-items"} - (let [products (all-products db authenticated?)] - (if (< 0 (# products)) - (table.concat - (icollect [_ v (ipairs products)] - (item-template v basket))) - (HTML [:em {} "Пока что здесь ничего нет!"])))]])]) + (if authenticated? + (HTML + [:div {:class "mb-1" :style "margin-top: -0.5rem"} + [:a {:style "white-space: nowrap" + :href (.. "/shop/add")} "+ Добавить"] + [:a {:style "white-space: nowrap; margin-left: 1rem;" + :href (.. "/shop/order/list")} "☰ Список заказов"]]) + "") + [:form {:class "d-flex gap-0-5 mb-1"} + [:input {:name "search" :type "search" + :placeholder "Поиск" :value (or filters.search "")}] + [:select {:name "type"} + [:option {:value ""} "Все товары"] + (table.concat + (icollect [_ v (ipairs dicts.product-type)] + (HTML [:option (fn [] {:value v.value + :selected (= filters.type v.value)}) + v.label])))] + [:button {:type "submit"} "Применить"]] + [:div {:class "shop-items"} + (if (< 0 (# products)) + (table.concat + (icollect [_ v (ipairs products)] + (item-template v basket))) + (HTML [:em {} "Пока что здесь ничего нет!"]))]])]) (fn render [request db authenticated?] (let [order-id (shop.order-id request) - basket (if order-id (shop.basket db order-id) [])] - (values 200 {} (templates.base (content db basket authenticated?))))) + basket (if order-id (shop.basket db order-id) []) + type (if (and request.query.type + (not (lib.empty? (. request.query.type 1)))) + (. request.query.type 1) + nil) + search (if (and request.query.search + (not (lib.empty? (. request.query.search 1)))) + (. request.query.search 1) + nil) + filters { : type : search } + products (all-products db filters authenticated?)] + (values 200 {} (templates.base + (content db products filters basket authenticated?))))) {: render} @@ -9,12 +9,12 @@ usage () { serve () { echo "running in jit" - go run -tags jit ../. -n ${1:-1} bin/serve.fnl + go run -tags jit,fts5 ../. -n ${1:-1} bin/serve.fnl } debug () { echo "running debug version in jit" - go run -tags jit ../. -n ${1:-1} -D bin/serve.fnl + go run -tags jit,fts5 ../. -n ${1:-1} -D bin/serve.fnl } deploy () { @@ -124,6 +124,14 @@ LIMIT 1" [order-id product-name]))))) +(fn update-search-index [tx] + (_G.must + (luna.db.exec-tx tx "DELETE FROM products_search" [])) + (_G.must + (luna.db.exec-tx tx "INSERT INTO products_search + SELECT title, short_description, name + FROM products;" []))) + {: create-order : place-order : finish-order @@ -132,4 +140,5 @@ : create-order-line : delete-order-line : basket - : in-basket?} + : in-basket? + : update-search-index} diff --git a/static/style.css b/static/style.css index ea8243f..17b1d26 100644 --- a/static/style.css +++ b/static/style.css @@ -560,6 +560,10 @@ p { gap: 0.25rem !important; } +.gap-0-5 { + gap: 0.5rem !important; +} + .gap-1 { gap: 1rem !important; } |
