summaryrefslogtreecommitdiff
path: root/src/chef
diff options
context:
space:
mode:
Diffstat (limited to 'src/chef')
-rw-r--r--src/chef/core.clj3
-rw-r--r--src/chef/database/init.clj3
-rw-r--r--src/chef/pages/admin.clj2
-rw-r--r--src/chef/pages/admin/api.clj57
-rw-r--r--src/chef/pages/admin/recipe_editor.clj14
-rw-r--r--src/chef/pages/recipe.clj50
-rw-r--r--src/chef/routes.clj11
-rw-r--r--src/chef/utils.clj27
8 files changed, 139 insertions, 28 deletions
diff --git a/src/chef/core.clj b/src/chef/core.clj
index f3422d5..cfe72f9 100644
--- a/src/chef/core.clj
+++ b/src/chef/core.clj
@@ -2,9 +2,12 @@
(:require [org.httpkit.server :as http-server]
[chef.routes :as croutes]
[chef.database :as cdb])
+ (:import java.io.File)
(:gen-class))
(defn -main [& args]
+ (println "Creating thumbnails folder...")
+ (.mkdirs (File. "./thumbnails/"))
(println "Running db patches...")
(cdb/run-patches!)
(println "Starting http server...")
diff --git a/src/chef/database/init.clj b/src/chef/database/init.clj
index ef4bd2f..192b775 100644
--- a/src/chef/database/init.clj
+++ b/src/chef/database/init.clj
@@ -14,5 +14,4 @@
[:title :text]
[:unit :integer]
[:ingredients :text]
- [:preparation :text]
- [:thumbnail :text]]})))
+ [:preparation :text]]})))
diff --git a/src/chef/pages/admin.clj b/src/chef/pages/admin.clj
index 8ffadcc..ea19cc3 100644
--- a/src/chef/pages/admin.clj
+++ b/src/chef/pages/admin.clj
@@ -69,7 +69,7 @@
[:button {:class ["button" "primary"]
:onclick (str "window.open(\"/admin/recipe-editor/"
(:recipes/id recipe)
- "\", \"\", \"width=500,height=500\")")}
+ "\", \"\", \"width=900,height=900\")")}
"Bearbeiten"]
[:button {:class ["button error"]
:hx-trigger "click"
diff --git a/src/chef/pages/admin/api.clj b/src/chef/pages/admin/api.clj
index f1b7226..1119607 100644
--- a/src/chef/pages/admin/api.clj
+++ b/src/chef/pages/admin/api.clj
@@ -1,9 +1,12 @@
(ns chef.pages.admin.api
(:require [chef.utils :as cutils]
[chef.database :as cdb]
+ [clojure.string :as cstr]
[next.jdbc :as jdbc]
[honey.sql :as sql]
- [ring.util.response :as ruresp]))
+ [ring.util.response :as ruresp]
+ [clojure.java.io :as cjio])
+ (:import java.io.File))
(defn create-category [req]
(cutils/auth-only req
@@ -74,18 +77,50 @@
(ruresp/header "HX-Refresh" "true")))
(ruresp/bad-request "Bad request."))))
-;;TODO: validate request
(defn edit-recipe [req]
(cutils/auth-only req
+ (let [id (try (Integer/parseInt (get-in req [:path-params :id]))
+ (catch Exception _ nil))
+ ingredients (get-in req [:params "ingredients"])]
+ (if (and (some? id)
+ (cutils/valid-ingredients? ingredients))
+ (do (when-let [thumbnail (get-in req [:params "thumbnail"])]
+ (when-let [existing-thumbnail-file (->> {:select [:*]
+ :from [:recipes]
+ :where [:= :id id]}
+ sql/format
+ (jdbc/execute! @cdb/db)
+ first
+ cutils/get-thumbnail-file)]
+ (.delete ^File existing-thumbnail-file))
+ (cjio/copy (:tempfile thumbnail)
+ (File. (str "./thumbnails/" id "."
+ (-> thumbnail
+ :filename
+ (cstr/split #"\.")
+ last)))))
+ (jdbc/execute! @cdb/db
+ (sql/format {:update :recipes
+ :set {:title (get-in req [:params "title"])
+ :category (get-in req [:params "category"])
+ :unit (get-in req [:params "ingredients-unit"])
+ :ingredients ingredients
+ :preparation (get-in req [:params "preparation"])}
+ :where [:= :id id]}))
+ (ruresp/response "Saved."))
+ (ruresp/bad-request "Bad request.")))))
+
+(defn delete-thumbnail [req]
+ (cutils/auth-only req
(if-let [id (try (Integer/parseInt (get-in req [:path-params :id]))
(catch Exception _ nil))]
- (do (jdbc/execute! @cdb/db
- (sql/format {:update :recipes
- :set {:title (get-in req [:params "title"])
- :category (get-in req [:params "category"])
- :unit (get-in req [:params "ingredients-unit"])
- :ingredients (get-in req [:params "ingredients"])
- :preparation (get-in req [:params "preparation"])}
- :where [:= :id id]}))
- (ruresp/response "Saved."))
+ (when-let [thumbnail-file (->> {:select [:*]
+ :from [:recipes]
+ :where [:= :id id]}
+ sql/format
+ (jdbc/execute! @cdb/db)
+ first
+ cutils/get-thumbnail-file)]
+ (.delete ^File thumbnail-file)
+ (ruresp/response "Done."))
(ruresp/bad-request "Bad request."))))
diff --git a/src/chef/pages/admin/recipe_editor.clj b/src/chef/pages/admin/recipe_editor.clj
index 6afd591..cc30942 100644
--- a/src/chef/pages/admin/recipe_editor.clj
+++ b/src/chef/pages/admin/recipe_editor.clj
@@ -13,9 +13,20 @@
[:h1 "Rezept bearbeiten"]
[:form {:style {:width "50%"}
:hx-post (str "/api/admin/edit-recipe/" (:recipes/id recipe))
- :hx-swap "none"}
+ :hx-swap "none"
+ :enctype "multipart/form-data"}
[:input {:type :text :name "title" :placeholder "Titel"
:value (:recipes/title recipe)}]
+ [:div {:style {:display :flex}}
+ [:p {:style {:margin-right "0.5em"}} "Thumbnail: "]
+ [:input {:type :file :name "thumbnail"
+ :style {:height :fit-content
+ :padding "0.3em"}}]]
+ [:button {:class ["button" "error"]
+ :hx-trigger "click"
+ :hx-delete (str "/api/admin/delete-thumbnail/" (:recipes/id recipe))
+ :hx-swap :none}
+ "Thumbnail entfernen"]
[:h2 "Kategorie"]
[:select {:name "category"}
(for [category (->> (sql/format {:select [:*]
@@ -36,7 +47,6 @@
[:p ":"]]
[:textarea {:name "ingredients"}
(:recipes/ingredients recipe)]
- ;; Regex: ([A-z0-9 ]*)=([0-9]*) ?([A-z]*)
[:p "(Je Zeile eine Zutat, in dem Format " [:code "[Beschreibung der Zutat]=[Menge als Zahl][Einheit der Menge]"] ".)"]
[:h2 "Zubereitung"]
[:textarea {:name "preparation"}
diff --git a/src/chef/pages/recipe.clj b/src/chef/pages/recipe.clj
index 71dd4b2..84f999c 100644
--- a/src/chef/pages/recipe.clj
+++ b/src/chef/pages/recipe.clj
@@ -1,10 +1,12 @@
(ns chef.pages.recipe
(:require [chef.database :as cdb]
[chef.utils :as cutils]
+ [clojure.string :as cstr]
[hiccup2.core :as html]
[honey.sql :as sql]
[next.jdbc :as jdbc]
- [ring.util.response :as ruresp]))
+ [ring.util.response :as ruresp])
+ (:import java.io.File))
(defn- render [recipe]
(cutils/gen-page (str "chef - " (:recipes/title recipe))
@@ -13,13 +15,33 @@
[:h1 {:style {:display :inline-block
:margin-right "0.5em"}}
(:recipes/title recipe)]
- [:i (cutils/category-path (->> {:select [:*]
- :from [:categories]
- :where [:= :id (:recipes/category recipe)]}
- sql/format
- (jdbc/execute! @cdb/db)
- first))]]
- [:b "TODO"]]))
+ [:i (str "(" (cutils/category-path (->> {:select [:*]
+ :from [:categories]
+ :where [:= :id (:recipes/category recipe)]}
+ sql/format
+ (jdbc/execute! @cdb/db)
+ first)) ")")]]
+ (when (some? (cutils/get-thumbnail-file recipe))
+ [:img {:src (str "/recipes/" (:recipes/id recipe) "/thumbnail")
+ :width "50%"}])
+ [:h2 (str "Zutaten"
+ (condp = (:recipes/unit recipe)
+ 0 " pro Portion"
+ 1 " pro Person"
+ "")
+ ":")]
+ [:ul (for [ingredient (-> recipe
+ :recipes/ingredients
+ cutils/parse-ingredients)]
+ [:li
+ [:b (:description ingredient)] ": "
+ (:amount ingredient) (:unit ingredient)])]
+ [:h2 "Zubereitung"]
+ (->> (:recipes/preparation recipe)
+ cstr/split-lines
+ (map #(if (cstr/blank? %)
+ [:br]
+ [:p %])))]))
(defn handler [req]
(->> {:select [:*]
@@ -32,3 +54,15 @@
html/html
str
ruresp/response))
+
+(defn thumbnail-handler [req]
+ (if-let [id (get-in req [:path-params :id])]
+ (when-let [thumbnail-file (->> {:select [:*]
+ :from [:recipes]
+ :where [:= :id id]}
+ sql/format
+ (jdbc/execute! @cdb/db)
+ first
+ cutils/get-thumbnail-file)]
+ (ruresp/file-response (.getPath ^File thumbnail-file)))
+ (ruresp/bad-request "Bad request.")))
diff --git a/src/chef/routes.clj b/src/chef/routes.clj
index 6b57f4f..d21cb0d 100644
--- a/src/chef/routes.clj
+++ b/src/chef/routes.clj
@@ -2,7 +2,9 @@
(:require [reitit.ring :as rring]
[ring.middleware.oauth2 :as rmoauth2]
[ring.middleware.params :as rmparams]
+ [ring.middleware.multipart-params :as rmmultiparams]
[ring.middleware.session :as rmsession]
+ [ring.middleware.reload :as rmreload]
[dotenv :as env]
[clojure.string :as cstr]
@@ -16,7 +18,9 @@
[chef.pages.admin.recipe-editor :as cparecipe-editor]))
(def router [["/" {:get {:handler cphome/handler}}]
- ["/recipes/:id" {:get cprecipe/handler}]
+ ["/recipes/:id"
+ ["/" {:get cprecipe/handler}]
+ ["/thumbnail" {:get cprecipe/thumbnail-handler}]]
["/static/*" (rring/create-resource-handler)]
["/admin"
["/" {:get {:handler cpadmin/handler}}]
@@ -33,7 +37,9 @@
["/create-recipe" {:post {:handler cpaapi/create-recipe}}]
["/delete-recipe/:id" {:delete {:handler cpaapi/delete-recipe}}]
- ["/edit-recipe/:id" {:post {:handler cpaapi/edit-recipe}}]]]])
+ ["/edit-recipe/:id" {:post {:handler cpaapi/edit-recipe}}]
+
+ ["/delete-thumbnail/:id" {:delete {:handler cpaapi/delete-thumbnail}}]]]])
(def ring-handler (delay (-> router
rring/router
@@ -48,4 +54,5 @@
:landing-uri "/admin"
:pkce? true}})
rmparams/wrap-params
+ rmmultiparams/wrap-multipart-params
rmsession/wrap-session)))
diff --git a/src/chef/utils.clj b/src/chef/utils.clj
index c2c6bc1..e2725ce 100644
--- a/src/chef/utils.clj
+++ b/src/chef/utils.clj
@@ -3,7 +3,8 @@
[honey.sql :as sql]
[next.jdbc :as jdbc]
[ring.util.response :as ruresp]
- [clojure.string :as cstr]))
+ [clojure.string :as cstr])
+ (:import java.io.File))
(defn gen-page [title & content]
[:html
@@ -11,7 +12,8 @@
[:meta {:name "viewport" :content "width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"}]
[:title title]
[:link {:rel :stylesheet :href "/static/style.css"}]
- [:meta {:http-equiv "content-type" :content "text/html; charset=utf-8"}]]
+ [:meta {:http-equiv "content-type" :content "text/html; charset=utf-8"}]
+ [:meta {:name "robots" :content "noindex,nofollow"}]]
(apply conj [:body] content [[:script {:src "/static/htmx.js"}]])])
(defmacro auth-only [request & body]
@@ -40,3 +42,24 @@
category-parents
(map #(:categories/name %))
(cstr/join " > ")))
+
+(defn parse-ingredients [s]
+ (->> s
+ (re-seq #"([A-z0-9 ]*)=([0-9]*) ?([A-z]*)")
+ (map #(hash-map :description (nth % 1)
+ :amount (Integer/parseInt (nth % 2))
+ :unit (nth % 3)))))
+
+(defn valid-ingredients? [s]
+ (and (string? s)
+ (->> s
+ (re-matches #"(([A-z0-9 ]*)=([0-9]*) ?([A-z]*)\n?)*")
+ some?)))
+
+(defn get-thumbnail-file [recipe]
+ (let [thumbnails-folder (File. "./thumbnails/")]
+ (->> thumbnails-folder
+ .listFiles
+ (filter #(cstr/starts-with? (.getName ^File %)
+ (str (:recipes/id recipe) ".")))
+ first)))