(import-macros {:compile-html HTML} :macros) (local lib (require :lib)) (local dicts (require :dicts)) (local templates (require :templates)) (fn all-products [db authenticated?] (local where (if (not authenticated?) "WHERE products.published = true" "")) (_G.must (luna.db.query-assoc db (.. "SELECT products.name, products.title, products.published, products.short_description as \"short-description\", products.price_per AS \"price-per\", products.type, products.image1, products.image2, products.image3, products.image4, products.image5 FROM products " where " ORDER BY products.position") []))) (fn quantity-steps [stock step] (assert (< 0 step) "step must be greater than 0") (var result []) (var first (math.min stock step)) (while (<= first stock) (table.insert result first) (set first (+ first step))) result) (fn item-template [product] (local item-url (.. "/shop/" product.name)) ;; (var quantity-options []) ;; (if (< 0 product.stock) ;; (each [_ q (ipairs (quantity-steps product.stock 50))] ;; (table.insert quantity-options ;; (HTML ;; [:option {:value (tostring q)} ;; (.. q " грамм за " (* product.price-per q) "₽")]))) ;; (table.insert quantity-options (HTML [:option {:value "0"} "Товар закончился"]))) (local images []) (for [i 2 5] (table.insert images (. product (.. "image" i)))) (HTML [:section {:class (.. "shop-item" (if (not product.published) " shop-item-not-published" ""))} [:a {:href item-url} [:div {:class "shop-item-imgs"} [:img {:class "shop-item-img" :src (.. "/static/files/" (. product.image1) "-thumbnail.jpg")}] (table.concat (icollect [idx image (ipairs images)] (HTML [:img {:class "shop-item-img" :src (.. "/static/files/" image "-thumbnail.jpg") :loading "lazy" :style (.. "z-index: " (+ idx 2) ";" "width: calc(100% / " (# images) ");" "left: calc(100% / " (# images) " * " (- idx 1) ")")}])))]] [:a {:href item-url} [:h3 {:class "shop-item-title"} product.title]] [:div {:style "font-style: italic; margin-bottom: 0.25rem;"} (or (dicts.label dicts.tea-type product.type) product.type) ", " [:strong {} (* 50 product.price-per) "₽ за 50 гр. "]] ;; [:div {:class "shop-item-price"} ;; [:form {:method "POST"} ;; [:input {:type "hidden" :name "name" :value product.name}] ;; [:select {:name "quantity"} (table.concat quantity-options)] ;; [:button {:type "submit"} "Добавить"]]] [:div {} product.short-description]])) (fn content [db basket basket-total authenticated?] [(HTML [:div {:class "side"} (templates.header "/shop" authenticated?) (if (< 0 (# basket)) (HTML [:article {:class "article"} [:h2 {} "Корзина"] [:div {} (table.concat (icollect [_ item (ipairs basket)] (templates.basket-item item "/shop")))] [:div {} "~~~"] [:div {:class "basket-total"} (.. "Итого: " basket-total "₽")] [:a {:href "/shop/order"} "Оформить заказ"]]) "")]) (HTML [:div {:class "content"} [:div {:class "mb-1"} [:a {:href "/"} "⟵ Обратно на главную"]] [:h2 {:class "mb-1 product-page-title"} "Магазин" (if authenticated? (HTML [:a {:style "font-size: 1rem; margin-left: 0.75rem;" :href (.. "/shop/add")} "+ Добавить"]) "")] [:div {:class "shop-items"} (let [products (all-products db authenticated?)] (if (< 0 (# products)) (table.concat (icollect [_ v (ipairs products)] (item-template v))) (HTML [:em {} "Пока что здесь ничего нет!"])))]])]) (fn create-order [db] (let [id (_G.must (luna.crypto.random-string 64))] (_G.must (luna.db.exec db "INSERT INTO orders (id, creation_time) VALUES (?, ?)" [id (lib.now)])) id)) (fn create-order-line [db order-id name quantity] (_G.must (luna.db.exec db "INSERT INTO order_lines (order_id, product_name, quantity) VALUES (?, ?, ?)" [order-id name quantity]))) (fn render [request db authenticated?] (let [order-id (lib.order-id request) basket (if order-id (lib.basket db order-id) []) basket-total (accumulate [sum 0 _ v (ipairs basket)] (+ sum (* v.quantity v.price-per)))] (if (= request.method "POST") (do (var order-id (lib.order-id request)) (var headers (if (not order-id) (do (set order-id (create-order db)) {:Set-Cookie (.. "order= " order-id "; HttpOnly; SameSite=strict" (if luna.debug? "" "; Secure"))}) {})) (if (and order-id request.body) (let [body-values (lib.parse-values request.body)] (create-order-line db order-id body-values.name body-values.quantity) (tset headers :Location "/shop") (values 302 headers "")) (values 400 {} "bad body"))) (values 200 {} (templates.base (content db basket basket-total authenticated?)))))) {: render}