diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | src/chef/core.clj | 3 | ||||
-rw-r--r-- | src/chef/database/init.clj | 3 | ||||
-rw-r--r-- | src/chef/pages/admin.clj | 2 | ||||
-rw-r--r-- | src/chef/pages/admin/api.clj | 57 | ||||
-rw-r--r-- | src/chef/pages/admin/recipe_editor.clj | 14 | ||||
-rw-r--r-- | src/chef/pages/recipe.clj | 50 | ||||
-rw-r--r-- | src/chef/routes.clj | 11 | ||||
-rw-r--r-- | src/chef/utils.clj | 27 |
9 files changed, 140 insertions, 28 deletions
@@ -4,3 +4,4 @@ target/ .nrepl-port chef.iml chef.db +thumbnails/ 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))) |