From 91f7e79aed465d380e977b3a3ff5b482c359e6b7 Mon Sep 17 00:00:00 2001 From: Bryce Date: Tue, 24 Oct 2023 11:12:31 -0700 Subject: [PATCH] a lot of streamlining for validation --- src/clj/auto_ap/ssr/admin/accounts.clj | 73 +++++------ .../auto_ap/ssr/admin/transaction_rules.clj | 117 ++++++++---------- src/clj/auto_ap/ssr/components.clj | 1 + src/clj/auto_ap/ssr/components/dialog.clj | 12 +- src/clj/auto_ap/ssr/components/inputs.clj | 6 + src/clj/auto_ap/ssr/users.clj | 94 +++++++------- src/clj/auto_ap/ssr/utils.clj | 26 ++-- 7 files changed, 161 insertions(+), 168 deletions(-) diff --git a/src/clj/auto_ap/ssr/admin/accounts.clj b/src/clj/auto_ap/ssr/admin/accounts.clj index ef48ae82..755b5460 100644 --- a/src/clj/auto_ap/ssr/admin/accounts.clj +++ b/src/clj/auto_ap/ssr/admin/accounts.clj @@ -31,12 +31,12 @@ ref->enum-schema ref->select-options temp-id + wrap-entity wrap-form-4xx-2 wrap-schema-decode]] [bidi.bidi :as bidi] [clojure.string :as str] [datomic.api :as dc] - [hiccup2.core :as hiccup] [malli.core :as mc])) (defn filters [request] @@ -291,16 +291,21 @@ ;; ensure that adding a new one results in a new row -(defn dialog* [& {:keys [entity form-params form-errors]}] - (fc/start-form entity form-errors - [:div {:x-data (hx/json {"accountName" (:account/name entity) - "accountCode" (:account/numeric-code entity)})} +(defn dialog* [{:keys [entity form-params form-errors]}] + (fc/start-form form-params form-errors + [:div {:x-data (hx/json {"accountName" (or (:account/name form-params) (:account/numeric-code entity)) + "accountCode" (or (:account/numeric-code form-params) (:account/numeric-code entity) )}) + :hx-target "this"} (com/modal {} - [:form#edit-form (merge {:hx-ext "response-targets" - :hx-swap "outerHTML swap:300ms" - :hx-target-400 "#form-errors .error-content"} - form-params) + [:form#edit-form (-> {:hx-ext "response-targets" + :hx-swap "outerHTML swap:300ms" + :hx-target-400 "#form-errors .error-content"} + (assoc (if (:db/id entity) + :hx-put + :hx-post) + (str (bidi/path-for ssr-routes/only-routes + :admin-transaction-rule-edit-save)))) [:fieldset {:class "hx-disable"} (com/modal-card {} @@ -318,6 +323,7 @@ (com/validated-field {:label "Numeric code" :errors (fc/field-errors)} (com/text-input {:name (fc/field-name) + :value (fc/field-value) :x-model "accountCode" :autofocus true :class "w-32"})) @@ -388,9 +394,7 @@ ] [:div - [:div [:div#form-errors (when (:errors fc/*form-errors*) - [:span.error-content - (com/errors {:errors (:errors fc/*form-errors*)})])]] + (com/form-errors {:errors (:errors fc/*form-errors*)}) (com/validated-save-button {:errors (seq form-errors)} "Save account")])]])])) @@ -403,34 +407,15 @@ [] (client-override* fc/*current*)))) -(defn account-edit-dialog [request] - (let [account (some-> request :route-params :db/id (#(dc/pull (dc/db conn) default-read %)))] - (html-response (dialog* :entity account - :form-params {:hx-put (str (bidi/path-for ssr-routes/only-routes - :admin-account-edit-save))}) - :headers {"hx-trigger" "modalopen"}))) - - -(defn account-new-dialog [_] - (html-response (dialog* :entity {} - :form-errors {} - :form-params {:hx-post (str (bidi/path-for ssr-routes/only-routes - :admin-account-new-save))}) +(defn account-dialog [{:keys [entity form-params form-errors]}] + (html-response (dialog* {:entity entity + :form-params (or (when (seq form-params) + form-params) + entity + {}) + :form-errors form-errors}) :headers {"hx-trigger" "modalopen"})) -(defn account-save-error [request] - ;; TODO hydration - ;; TODO consistency of error handling and passing, on all form examples - (let [entity (some-> request :last-form)] - (html-response (dialog* :entity entity - :form-errors (:form-errors request) - :form-params (if (:db/id entity) - {:hx-put (str (bidi/path-for ssr-routes/only-routes - :admin-transaction-rule-edit-save))} - {:hx-post (str (bidi/path-for ssr-routes/only-routes - :admin-transaction-rule-edit-save))})) - :headers {"hx-retarget" "#edit-form fieldset" - "hx-reselect" "#edit-form fieldset"}))) (def account-schema (mc/schema [:map @@ -439,14 +424,14 @@ [:account/name [:string {:min 1}]] [:account/location [:maybe :string]] [:account/type (ref->enum-schema "account-type")] - [:account/applicability (ref->enum-schema "account-applicability")] + [:account/applicability (ref->enum-schema "account-applicability")] ; [:account/invoice-allowance (ref->enum-schema "allowance")] [:account/vendor-allowance (ref->enum-schema "allowance")] [:account/client-overrides {:optional true} [:maybe (many-entity {} [:db/id [:or entity-id temp-id]] - [:account-client-override/client [:or entity-id :string]] + [:account-client-override/client entity-id] [:account-client-override/name [:string {:min 2}]])]]])) (def key->handler @@ -460,12 +445,14 @@ :default 0} [nat-int? {:default 0}]]]) wrap-admin wrap-client-redirect-unauthenticated) :admin-account-save (-> account-save + (wrap-entity [:form-params :db/id] default-read) (wrap-schema-decode :form-schema account-schema) (wrap-nested-form-params) - (wrap-form-4xx-2 account-save-error)) - :admin-account-edit-dialog (-> account-edit-dialog + (wrap-form-4xx-2 (wrap-entity account-dialog [:form-params :db/id] default-read))) + :admin-account-edit-dialog (-> account-dialog + (wrap-entity [:route-params :db/id] default-read) (wrap-schema-decode :route-schema [:map [:db/id entity-id]])) - :admin-account-new-dialog account-new-dialog}) + :admin-account-new-dialog account-dialog}) (fn [h] (-> h (wrap-admin) diff --git a/src/clj/auto_ap/ssr/admin/transaction_rules.clj b/src/clj/auto_ap/ssr/admin/transaction_rules.clj index 0b0060e9..841c2160 100644 --- a/src/clj/auto_ap/ssr/admin/transaction_rules.clj +++ b/src/clj/auto_ap/ssr/admin/transaction_rules.clj @@ -18,9 +18,9 @@ [auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr.company :refer [bank-account-typeahead*]] [auto-ap.ssr.components :as com] + [auto-ap.ssr.form-cursor :as fc] [auto-ap.ssr.grid-page-helper :as helper] [auto-ap.ssr.hx :as hx] - [auto-ap.ssr.form-cursor :as fc] [auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]] [auto-ap.ssr.svg :as svg] [auto-ap.ssr.utils @@ -31,24 +31,20 @@ html-response many-entity money - path->name2 percentage ref->enum-schema ref->radio-options regex temp-id + wrap-entity wrap-form-4xx-2 wrap-schema-decode]] [auto-ap.utils :refer [dollars=]] [bidi.bidi :as bidi] - [cheshire.core :as cheshire] [clojure.string :as str] [datomic.api :as dc] - [hiccup2.core :as hiccup] [iol-ion.query :refer [ident]] - [malli.core :as mc] - [auto-ap.cursor :as cursor] - [auto-ap.ssr.hiccup-helper :as hh])) + [malli.core :as mc])) ;; TODO with dependencies, I really don't like that you have to be ultra specific in what ;; you want to include, and generating the routes and interconnection is weird too. @@ -401,25 +397,29 @@ (com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x)))) ;; TODO dialog is no longer closeable -(defn dialog* [& {:keys [entity form-params form-errors]}] - (fc/start-form entity form-errors +;; TODO might not need #edit-form id +(defn dialog* [{:keys [entity form-params form-errors]}] + (fc/start-form form-params form-errors (com/modal - {:modal-class "max-w-2xl"} + {:modal-class "max-w-2xl" + :hx-target "this"} - [:form#edit-form (merge {:hx-ext "response-targets" - :hx-swap "outerHTML swap:300ms" - :hx-target "#modal-holder" ;; TODO sort - :hx-target-400 "#form-errors .error-content" - :x-trap "true" - :class "group/form"} - form-params) + [:form#edit-form {:hx-ext "response-targets" + :hx-swap "outerHTML swap:300ms" + :hx-target-400 "#form-errors .error-content" + :x-trap "true" + :class "group/form" + (if (:db/id entity) + :hx-put + :hx-post) (str (bidi/path-for ssr-routes/only-routes :admin-transaction-rule-edit-save))} (com/modal-card {} [:div.flex [:div.p-2 "Transaction Rule"]] [:fieldset {:class "hx-disable" :hx-disinherit "hx-target" ;; TODO why disinherit - :x-data (hx/json {:clientId (or (:db/id (:transaction-rule/client entity)) - (:transaction-rule/client entity))})} + :x-data (hx/json {:clientId (or (:db/id (:transaction-rule/client form-params)) + (:transaction-rule/client form-params) + (:db/id (:transaction-rule/client entity)))})} [:div.space-y-1 (when-let [id (:db/id entity)] @@ -486,7 +486,7 @@ :hx-vals (format "js:{name: '%s', 'client-id': event.detail.clientId}" (fc/field-name)) :x-init "$watch('clientId', cid => $dispatch('changed', $data))"}] - (bank-account-typeahead* {:client-id ((some-fn :db/id identity) (:transaction-rule/client entity)) + (bank-account-typeahead* {:client-id ((some-fn :db/id identity) (:transaction-rule/client form-params)) :name (fc/field-name) :value (fc/field-value)})])) @@ -543,18 +543,20 @@ :content-fn (some-fn :vendor/name #(pull-attr (dc/db conn) :vendor/name %))})])) (fc/with-field :transaction-rule/accounts - (com/data-grid {:headers [(com/data-grid-header {} "Account") - (com/data-grid-header {:class "w-32"} "Location") - (com/data-grid-header {:class "w-16"} "%") - (com/data-grid-header {:class "w-16"})] - :id "transaction-rule-account-table"} - (fc/cursor-map #(transaction-rule-account-row* entity %)) - (com/data-grid-new-row {:colspan 4 - :hx-get (bidi/path-for ssr-routes/only-routes - :admin-transaction-rule-new-account) - :index (count (fc/field-value)) - :tr-params (hx/bind-alpine-vals {} {:client-id "clientId"})} - "New account"))) + (com/validated-field + {:errors (fc/field-errors)} + (com/data-grid {:headers [(com/data-grid-header {} "Account") + (com/data-grid-header {:class "w-32"} "Location") + (com/data-grid-header {:class "w-16"} "%") + (com/data-grid-header {:class "w-16"})] + :id "transaction-rule-account-table"} + (fc/cursor-map #(transaction-rule-account-row* form-params %)) + (com/data-grid-new-row {:colspan 4 + :hx-get (bidi/path-for ssr-routes/only-routes + :admin-transaction-rule-new-account) + :index (count (fc/field-value)) + :tr-params (hx/bind-alpine-vals {} {:client-id "clientId"})} + "New account")))) (fc/with-field :transaction-rule/transaction-approval-status (com/validated-field {:label "Approval status" @@ -567,10 +569,8 @@ ]] [:div - [:div#form-errors (when (:errors fc/*form-errors*) - [:span.error-content - (com/errors {:errors (:errors fc/*form-errors*)})])] - (com/validated-save-button {:errors form-errors} "Save rule")])]))) + (com/form-errors {:errors (:errors fc/*form-errors*)}) + (com/validated-save-button {:errors (:errors form-errors)} "Save rule")])]))) ;; TODO Should forms have some kind of helper to render an individual field? saving @@ -619,34 +619,14 @@ :value account :client-id client-id})))) -(defn transaction-rule-edit-dialog [request] - (let [entity (or - (some-> request :last-form) - (some-> request :route-params :db/id (#(dc/pull (dc/db conn) default-read %))))] - (html-response (dialog* :entity entity - :form-params {:hx-put (str (bidi/path-for ssr-routes/only-routes - :admin-transaction-rule-edit-save))}) - :headers {"hx-trigger-after-settle" "modalopen"}))) - -(defn transaction-rule-error [request] - (let [entity (some-> request :last-form)] - (html-response (dialog* :entity entity - :form-errors (:form-errors request) - :form-params (if (:db/id entity) - {:hx-put (str (bidi/path-for ssr-routes/only-routes - :admin-transaction-rule-edit-save))} - {:hx-post (str (bidi/path-for ssr-routes/only-routes - :admin-transaction-rule-edit-save))})) - :headers {"hx-retarget" "#edit-form fieldset" - "hx-reselect" "#edit-form fieldset"}))) - - -(defn transaction-rule-new-dialog [_] - (html-response (dialog* :entity {} - :form-errors {} - :form-params {:hx-post (str (bidi/path-for ssr-routes/only-routes - :admin-transaction-rule-edit-save))}) - :headers {"hx-trigger-after-settle" "modalopen"})) +(defn transaction-dialog [{:keys [entity form-params form-errors]}] + (html-response (dialog* {:entity entity + :form-params (or (when (seq form-params) + form-params) + entity ;; TODO coerce into form params + {}) + :form-errors form-errors}) + :headers {"hx-trigger" "modalopen"})) (def transaction-rule-schema (mc/schema [:map @@ -695,12 +675,15 @@ [:value {:optional true} [:maybe entity-id]]])) :admin-transaction-rule-save (-> transaction-rule-save + (wrap-entity [:form-params :db/id] default-read) (wrap-schema-decode :form-schema transaction-rule-schema) (wrap-nested-form-params) - (wrap-form-4xx-2 transaction-rule-error)) - :admin-transaction-rule-edit-dialog (-> transaction-rule-edit-dialog + (wrap-form-4xx-2 (-> transaction-dialog + (wrap-entity [:form-params :db/id] default-read)))) + :admin-transaction-rule-edit-dialog (-> transaction-dialog + (wrap-entity [:route-params :db/id] default-read) (wrap-schema-decode :route-schema [:map [:db/id entity-id]])) - :admin-transaction-rule-new-dialog transaction-rule-new-dialog}) + :admin-transaction-rule-new-dialog transaction-dialog}) (fn [h] (-> h (wrap-admin) diff --git a/src/clj/auto_ap/ssr/components.clj b/src/clj/auto_ap/ssr/components.clj index 56c410c0..305afc49 100644 --- a/src/clj/auto_ap/ssr/components.clj +++ b/src/clj/auto_ap/ssr/components.clj @@ -37,6 +37,7 @@ (def field inputs/field-) (def validated-field inputs/validated-field-) (def errors inputs/errors-) +(def form-errors inputs/form-errors-) (def left-aside aside/left-aside-) (def company-aside-nav aside/company-aside-nav-) diff --git a/src/clj/auto_ap/ssr/components/dialog.clj b/src/clj/auto_ap/ssr/components/dialog.clj index d2109e35..3ee49af6 100644 --- a/src/clj/auto_ap/ssr/components/dialog.clj +++ b/src/clj/auto_ap/ssr/components/dialog.clj @@ -4,11 +4,13 @@ [auto-ap.ssr.hiccup-helper :as hh])) (defn modal- [params & children] - [:div {:class (-> (:class params) - (or "max-w-4xl w-1/4 overflow-visible") - (hh/add-class "h-min")) - "@click.outside" "open=false" - } children]) + [:div (-> params + (update :class #(-> % + (or "max-w-4xl w-1/4 overflow-visible") + (hh/add-class "h-min"))) + + (assoc "@click.outside" "open=false")) + children]) (defn modal-card- [params header content footer] [:div#modal-card (update params diff --git a/src/clj/auto_ap/ssr/components/inputs.clj b/src/clj/auto_ap/ssr/components/inputs.clj index 2704c2b6..02251640 100644 --- a/src/clj/auto_ap/ssr/components/inputs.clj +++ b/src/clj/auto_ap/ssr/components/inputs.clj @@ -152,6 +152,7 @@ c.clearChoices(); [:input (-> params (dissoc :error?) + (assoc :type "text") (update :class (fnil hh/add-class "") default-input-classes) (update :class #(str % (use-size size))))]) @@ -210,6 +211,11 @@ c.clearChoices(); (when (sequential? errors) (str/join ", " (filter string? errors)))]) +(defn form-errors- [{:keys [errors]}] + [:div#form-errors (when errors + [:span.error-content + (errors- {:errors errors})])]) + (defn validated-field- [params & rest] (field- (cond-> params true (dissoc :errors) diff --git a/src/clj/auto_ap/ssr/users.clj b/src/clj/auto_ap/ssr/users.clj index e1d90816..26f90161 100644 --- a/src/clj/auto_ap/ssr/users.clj +++ b/src/clj/auto_ap/ssr/users.clj @@ -26,6 +26,7 @@ many-entity ref->enum-schema ref->select-options + wrap-entity wrap-form-4xx-2 wrap-schema-decode]] [auto-ap.time :as atime] @@ -34,9 +35,7 @@ [clojure.string :as str] [config.core :refer [env]] [datomic.api :as dc] - [hiccup2.core :as hiccup] - [malli.core :as mc] - [clj-time.format :as f])) + [malli.core :as mc])) (defn filters [request] [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" @@ -257,14 +256,17 @@ (def table* (partial helper/table* grid-page)) (defn impersonate [request] - (let [user (some-> request :params :db/id (#(dc/pull (dc/db conn) default-read %))) ] + (println (:entity request)) + (if (:entity request) {:status 200 - :headers {"hx-redirect" (str "/?jwt=" (jwt/sign (auth/user->jwt user "FAKE_TOKEN") - (:jwt-secret env) - {:alg :hs512})) + :headers {"hx-redirect" (str "/?jwt=" (jwt/sign (auth/user->jwt (:entity request) "FAKE_TOKEN") + (:jwt-secret env) + {:alg :hs512})) } - :session {:identity (dissoc (auth/user->jwt user "FAKE_TOKEN") - :exp)}})) + :session {:identity (dissoc (auth/user->jwt (:entity request) "FAKE_TOKEN") + :exp)}} + {:status 404})) + (defn client-row* [client] (com/data-grid-row (-> {:x-ref "p" :data-key "show" @@ -288,19 +290,20 @@ (com/data-grid-cell {:class "align-top"} (com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x)))) -(defn dialog* [{:keys [entity form-params form-errors]}] + +;; TODO hydrate user name +(defn dialog* [{:keys [form-params form-errors entity]}] (fc/start-form - entity form-errors + form-params form-errors (com/modal - {} - [:form#edit-form (merge {:hx-ext "response-targets" - :hx-put (str (bidi/path-for ssr-routes/only-routes - :user-edit-save - :request-method :put)) - :hx-swap "outerHTML swap:300ms" - :hx-target-400 "#form-errors .error-content" - :class "w-full"} - form-params) + {:hx-target "this"} + [:form#edit-form {:hx-ext "response-targets" + :hx-put (str (bidi/path-for ssr-routes/only-routes + :user-edit-save + :request-method :put)) + :hx-swap "outerHTML swap:300ms" + :hx-target-400 "#form-errors .error-content" + :class "w-full"} [:fieldset {:class "hx-disable"} (com/modal-card {} @@ -329,9 +332,7 @@ :user-client-new)} "Assign new client"))))] [:div - [:div [:div#form-errors (when (:errors fc/*form-errors*) - [:span.error-content - (com/errors {:errors (:errors fc/*form-errors*)})])]] + (com/form-errors {:errors (:errors fc/*form-errors*)}) (com/validated-save-button {:errors (seq form-errors)} "Save user")])]]))) @@ -348,22 +349,17 @@ (defn user-save-error [request] ;; TODO hydration ;; TODO consistency of error handling and passing, on all form examples - (let [entity (some-> request :last-form)] - (html-response (dialog* {:entity entity - :form-errors (:form-errors request)}) - :headers {"hx-retarget" "#edit-form fieldset" - "hx-reselect" "#edit-form fieldset"}))) + (html-response (dialog* {:form-params (:form-params request) + :entity (:entity request) + :form-errors (:form-errors request)}))) (defn user-edit-dialog [request] - (let [user (some-> request - :route-params - :db/id - (#(dc/pull (dc/db conn) default-read %)))] - (html-response - (dialog* {:entity user - :form-errors {}}) - - :headers {"hx-trigger" "modalopen"}))) + (html-response + (dialog* {:form-params (:entity request) + :entity (:entity request) + :form-errors {}}) + + :headers {"hx-trigger" "modalopen"})) (defn new-client [{ {:keys [index]} :query-params}] (html-response @@ -371,28 +367,34 @@ :new? true} [] (client-row* fc/*current*)))) +(def user-form-schema + (mc/schema + [:map + [:db/id entity-id] + [:user/clients {:optional true} + [:maybe + (many-entity {} [:db/id entity-id])]] + [:user/role (ref->enum-schema "user-role")]])) + (def key->handler (apply-middleware-to-all-handlers {:users (helper/page-route grid-page) :user-table (helper/table-route grid-page) :user-edit-save (-> user-edit-save - (wrap-schema-decode :form-schema (mc/schema - [:map - [:db/id entity-id] - [:user/clients {:optional true} - [:maybe - (many-entity {} [:db/id entity-id])]] - [:user/role (ref->enum-schema "user-role")]])) + (wrap-entity [:form-params :db/id] default-read) + (wrap-schema-decode :form-schema user-form-schema) (wrap-nested-form-params) - (wrap-form-4xx-2 user-save-error)) - :user-client-new (-> new-client + (wrap-form-4xx-2 (wrap-entity user-save-error [:form-params :db/id] default-read))) + :user-client-new (-> new-client (wrap-schema-decode :query-schema [:map [:index {:optional true :default 0} [nat-int? {:default 0}]]])) :user-edit-dialog (-> user-edit-dialog + (wrap-entity [:route-params :db/id] default-read) (wrap-schema-decode :route-schema (mc/schema [:map [:db/id entity-id]]))) :user-impersonate (-> impersonate + (wrap-entity [:params :db/id] default-read) (wrap-schema-decode :params-schema (mc/schema [:map [:db/id entity-id]])))} (fn [h] diff --git a/src/clj/auto_ap/ssr/utils.clj b/src/clj/auto_ap/ssr/utils.clj index b80c08bd..6cbdfa0f 100644 --- a/src/clj/auto_ap/ssr/utils.clj +++ b/src/clj/auto_ap/ssr/utils.clj @@ -1,15 +1,15 @@ (ns auto-ap.ssr.utils (:require - [auto-ap.datomic :refer [all-schema]] + [auto-ap.datomic :refer [all-schema conn]] [auto-ap.logging :as alog] [clojure.string :as str] [config.core :refer [env]] + [datomic.api :as dc] [hiccup2.core :as hiccup] [malli.core :as mc] [malli.error :as me] [malli.transform :as mt2] - [slingshot.slingshot :refer [throw+ try+]] - [manifold.time :as mt])) + [slingshot.slingshot :refer [throw+ try+]])) (defn html-response [hiccup & {:keys [status headers oob] :or {status 200 headers {} oob []}}] {:status status @@ -99,7 +99,7 @@ (def entity-id (mc/schema [nat-int? {:error/message "required"} ])) -(def temp-id (mc/schema :string)) +(def temp-id (mc/schema [:string {:min 1}])) (def money (mc/schema [:double])) (def percentage (mc/schema [:double {:decode/arbitrary (fn [x] (some-> x (* 0.01))) :max 1.0 @@ -292,18 +292,18 @@ (:errors (:explain (:error e))))] (alog/warn ::form-4xx :errors errors) (form-handler (assoc request - :last-form (:decoded e) + :form-params (:decoded e) :field-validation-errors errors :form-errors humanized))) #_(html-response [:span.error-content.text-red-500 (:message &throw-context)] :status 400)) (catch [:type :field-validation] e (form-handler (assoc request - :last-form (:form e) + :form-params (:form e) :form-errors (:form-errors e)))) (catch [:type :form-validation] e (form-handler (assoc request - :last-form (:form e) + :form-params (:form e) :form-validation-errors (:form-validation-errors e) :form-errors {:errors (:form-validation-errors e)})))))) @@ -326,3 +326,15 @@ (map (fn [k] (str "[" (k->n k) "]")) rest))))) + + +(defn wrap-entity [handler path read] + (fn wrap-entity-request [request] + (let [entity (some->> + (get-in request path) + (#(if (string? %) (Long/parseLong %) %)) + (dc/pull (dc/db conn) read ))] + (handler (if entity + (assoc request + :entity entity) + request)))))