summaryrefslogtreecommitdiff
path: root/main.fnl
diff options
context:
space:
mode:
authorunwox <me@unwox.com>2024-09-26 17:46:38 +0600
committerunwox <me@unwox.com>2024-09-26 17:46:38 +0600
commit9b82db238f9e2e02a76f95c793f8d6ef2387ecfd (patch)
treecdb2a16d01f09553b560ab1034d53392d07bae42 /main.fnl
init
Diffstat (limited to 'main.fnl')
-rw-r--r--main.fnl234
1 files changed, 234 insertions, 0 deletions
diff --git a/main.fnl b/main.fnl
new file mode 100644
index 0000000..9282517
--- /dev/null
+++ b/main.fnl
@@ -0,0 +1,234 @@
+(import-macros {: map : reduce} :lib.macro)
+
+(tset package :path (.. package.path ";./lib/lpeglj/?.lua"))
+
+(local io (require :io))
+(local math (require :math))
+(local fennel (require :vendor.fennel))
+(local html (require :vendor.html))
+(local json (require :vendor.json))
+(local array (require :lib.array))
+(local str (require :lib.string))
+
+(local ozchai (require :site.ozchai))
+(local ipuer (require :site.ipuer))
+(local artoftea (require :site.artoftea))
+
+(print (fennel.view (ipuer.products)))
+(os.exit 1)
+
+(when _G.unpack
+ (tset table :unpack _G.unpack))
+
+(local query-synonyms {
+ "шэн" "шен"
+ "шен" "шэн"
+ "доска" "чабань"
+ "чабань" "доска"
+})
+
+(local db (luna.db.open "file:db.sqlite?_journal=WAL&_sync=NORMAL"))
+(luna.db.exec db "
+ PRAGMA foreign_keys=ON;
+ PRAGMA journal_mode=WAL;
+ PRAGMA synchronous=NORMAL;
+
+ CREATE VIRTUAL TABLE IF NOT EXISTS search USING fts5(name, fid, `table`);
+
+ CREATE TABLE IF NOT EXISTS products (
+ id TEXT NOT NULL PRIMARY KEY,
+ site TEXT NOT NULL,
+ category TEXT NOT NULL,
+ title TEXT NOT NULL,
+ description TEXT NOT NULL,
+ year INT NOT NULL,
+ image TEXT NOT NULL,
+ url TEXT NOT NULL,
+ price REAL NOT NULL,
+ weight REAL NOT NULL,
+ price_per REAL NOT NULL,
+ misc TEXT NOT NULL,
+ creation_time DATETIME NOT NULL
+ );" [])
+
+(fn now []
+ (os.date "%Y-%m-%d %H:%M:%S"))
+
+(fn unescape [s]
+ (assert (= (type s) :string))
+ (pick-values 1
+ (-> s
+ (string.gsub "&lt;" "<")
+ (string.gsub "&gt;" ">")
+ (string.gsub "&quot;" "\"")
+ (string.gsub "&amp;" "&"))))
+
+(fn site-name-template [name]
+ (if
+ (= name "ipuer.ru")
+ [:a {:class "site-icon" :href "https://ipuer.ru"}
+ [:img {:src "/static/ipuer.jpg"}]
+ "Институт чая пуэр"]
+ ""))
+
+(fn item-template [product]
+ [:div {:class "tile"}
+ [:a {:href product.url :style "display: block;"}
+ [:img {:src product.image} ""]]
+ (site-name-template product.site)
+ [:a {:href product.url :style "text-decoration: none;"}
+ [:NO-ESCAPE (.. "<h2>" (unescape product.title) "</h2>")]]
+ [:div {:class "price"}
+ (if product.price (.. product.price "₽") "")
+ (if product.quantity (.. " за " product.quantity "г") "")
+ (if (and product.price-per
+ (< 0 product.price-per))
+ [:NO-ESCAPE (.. " (" product.price-per "₽ за&nbsp;1г)")]
+ "")]
+ [:small {} (or product.description "")]])
+
+(fn paginator-template [query page limit total]
+ (local last-page (math.ceil (/ total limit)))
+
+ (if (< limit total)
+ [:div {:class "paginator"}
+ [:div {:class "paginator-numbers"}
+ (if (< 1 page)
+ [:a {:href (.. "?page=" (- page 1) "&query=" query)} "<"]
+ "")
+ (faccumulate [res [:span {}] i 1 last-page]
+ (do
+ (table.insert
+ res [:a {:href (.. "?page=" i "&query=" query)
+ :class (if (= page i) "paginator-active" "")}
+ (tostring i)])
+ res))
+ (if (< page last-page)
+ [:a {:href (.. "?page=" (+ page 1) "&query=" query)} ">"]
+ "")]
+ [:div {} "Всего результатов: " [:strong {} (string.format "%d" total)]]]
+ ""))
+
+(fn base-template [query sort page total ...]
+ (local paginator (paginator-template query page 32 total))
+
+ [:html {:lang "en"}
+ [:head {}
+ [:meta {:charset "UTF-8"}]
+ [:link {:rel :stylesheet :href "static/style.css"}]
+ [:title {} "A new cool web server for lua"]]
+ [:body {}
+ [:div {:class "container"}
+ [:div {:class "content"}
+ [:aside {:class "aside"}
+ [:div {:class "aside-content"}
+ [:a {:href "/" :style "display: block;"}
+ [:img {:class "logo" :src "static/logo.svg" :alt "Логотип meicha.ru"}]]
+ [:form {:class "form"}
+ [:input {:type :search :name :query :value query
+ :autofocus true :placeholder "enter search query"}]
+ [:button {:type :submit} "Искать"]]
+ paginator]]
+ [:section {}
+ [:div {:class "list"} ...]
+ [:footer {} paginator]]]]]])
+
+(fn query-products [page query sorters]
+ (local query
+ (table.concat
+ (map (fn [_ q]
+ (if (. query-synonyms q)
+ (.. "(" q "* OR " (. query-synonyms q) "*)")
+ (.. q "*")))
+ (str.split query))
+ " "))
+ (local total
+ (luna.db.query
+ db
+ "SELECT count(*)
+ FROM search
+ WHERE search.`table` = 'products'
+ AND search.name MATCH ?"
+ [query]))
+
+ {:results
+ (luna.db.query*
+ db
+ "SELECT products.id,
+ 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
+ FROM search
+ INNER JOIN products ON search.fid = products.id
+ WHERE search.`table` = 'products'
+ AND search.name MATCH ?
+ ORDER BY rank
+ LIMIT 32 OFFSET ?"
+ [query (* (- page 1) 32)])
+ :total (if (< 0 (# total))
+ (. total 1 1)
+ 0)})
+
+(fn store-products [products]
+ (local sql
+ (.. "INSERT OR REPLACE INTO products VALUES "
+ (table.concat
+ (map (fn [_ _]
+ "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
+ products)
+ ",")))
+ (local vars
+ (reduce
+ (fn [_ product rest]
+ (array.concat rest
+ [product.id
+ product.site
+ product.category
+ product.title
+ (or product.description "")
+ (or product.year 0)
+ (or product.image "")
+ (or product.url "")
+ (or product.price 0)
+ (or product.weight 0)
+ (or product.price-per 0)
+ (or product.misc "")
+ (now)]))
+ products []))
+ (luna.db.exec db sql vars))
+
+(fn populate-search-table []
+ (local tx (luna.db.begin db))
+ (luna.db.exec-tx tx "DELETE FROM search" [])
+ (luna.db.exec-tx tx "INSERT INTO search
+ SELECT title, id, 'products' FROM products;" [])
+ (luna.db.commit tx))
+
+; (store-products (ipuer.products))
+; (store-products (ozchai.products))
+; (populate-search-table)
+
+(fn root-handler [{: path : query}]
+ (if (= path "/")
+ (let [headers {:content-type "text/html"}
+ page (or (tonumber query.page) 1)
+ search (or query.query "")
+ sort "ASC"
+ {: results : total} (query-products page search sort)]
+ (values
+ 200 headers
+ (html.render
+ (base-template
+ search sort page total
+ (table.unpack (map #(item-template $2) results)))
+ true)))
+ (values 404 {} "not found")))
+
+(luna.router.route "GET /" root-handler)
+(luna.router.static "GET /static/" "static/")