(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 "<" "<")
(string.gsub ">" ">")
(string.gsub """ "\"")
(string.gsub "&" "&"))))
(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 (.. "
" (unescape product.title) "
")]]
[: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 "₽ за 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, '', '') 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/")