From b571c0992dc1e96f821621044f052e1ec98887a0 Mon Sep 17 00:00:00 2001 From: Matthew Huebert Date: Fri, 5 Jan 2024 18:07:33 +0100 Subject: [PATCH] video fields, role granularity, nil attribute transactions, --- src/sb/app.cljc | 9 ++-- src/sb/app/board/data.cljc | 10 ++-- src/sb/app/content/data.cljc | 4 +- src/sb/app/entity/data.cljc | 38 +++++++++++--- src/sb/app/entity/ui.cljc | 12 ++--- src/sb/app/field/data.cljc | 6 +-- src/sb/app/field/ui.cljc | 96 ++++++++++++++++++----------------- src/sb/app/member/data.cljc | 3 +- src/sb/app/org/data.cljc | 4 +- src/sb/app/project/data.cljc | 2 +- src/sb/app/project/ui.cljc | 41 ++++++--------- src/sb/authorize.cljc | 50 +++++++++++------- src/sb/i18n.cljc | 3 ++ src/sb/migration/one_time.clj | 13 +++-- src/sb/validate.cljc | 19 +++---- 15 files changed, 167 insertions(+), 143 deletions(-) diff --git a/src/sb/app.cljc b/src/sb/app.cljc index 4d2fe88f..ff270f59 100644 --- a/src/sb/app.cljc +++ b/src/sb/app.cljc @@ -22,7 +22,8 @@ [sb.app.social-feed.ui] [sb.app.vote.ui] [sb.i18n :refer [t]] - [sb.transit :as t])) + [sb.transit :as t] + [sb.util :as u])) #?(:cljs (def client-endpoints (t/read (shadow.resource/inline "public/js/sparkboard.views.transit.json")))) @@ -38,8 +39,9 @@ :init init))} :prose/as-map {:view field.ui/prose-field :make-field (fn [init _props] - (io/form {:prose/format (prose/?format :init :prose.format/markdown) - :prose/string prose/?string} + (io/form (-> {:prose/format (prose/?format :init :prose.format/markdown) + :prose/string prose/?string} + (u/guard :prose/string)) :init init))} :account/email {:props {:type "email" :placeholder (t :tr/email)} @@ -52,6 +54,7 @@ :field/options {:view field.admin-ui/options-editor} :entity/domain-name {:view domain.ui/domain-field :make-field domain.ui/make-domain-field} + :entity/video {:view field.ui/video-field} :entity/fields {:view field.admin-ui/fields-editor :make-field field.admin-ui/make-field:fields} :entity/field-entries {:view field.ui/entries-field diff --git a/src/sb/app/board/data.cljc b/src/sb/app/board/data.cljc index f7f0572d..c1e168bf 100644 --- a/src/sb/app/board/data.cljc +++ b/src/sb/app/board/data.cljc @@ -137,12 +137,12 @@ (mapv (db/pull `[~@entity.data/fields])))) (defn authorize-edit! [board account-id] - (when-not (or (validate/can-edit? board account-id) - (validate/can-edit? (:entity/parent board) account-id)) + (when-not (or (validate/can-edit? account-id board) + (validate/can-edit? account-id (:entity/parent board) )) (validate/permission-denied!))) (defn authorize-create! [board account-id] - (when-not (validate/can-edit? (:entity/parent board) account-id) + (when-not (validate/can-edit? account-id (:entity/parent board)) (validate/permission-denied!))) (q/defx new! @@ -161,8 +161,8 @@ (q/defquery settings {:prepare [az/with-account-id! (az/with-roles :board-id) - (fn [_ {:as params :keys [member/roles board-id account-id]}] - (validate/assert-can-edit! board-id account-id) + (fn [_ {:as params :keys [board-id account-id]}] + (validate/assert-can-edit! account-id board-id) params)]} [{:keys [board-id member/roles]}] (some-> diff --git a/src/sb/app/content/data.cljc b/src/sb/app/content/data.cljc index 732293b6..8f7c4476 100644 --- a/src/sb/app/content/data.cljc +++ b/src/sb/app/content/data.cljc @@ -1,5 +1,5 @@ (ns sb.app.content.data - (:require [sb.schema :as sch :refer [s-]])) + (:require [sb.schema :as sch :refer [s- ?]])) (sch/register! (merge @@ -9,7 +9,7 @@ :prose/string {s- :string} :prose/as-map {s- [:map {:closed true} :prose/format - :prose/string]}} + (? :prose/string)]}} {:content/badge {s- [:map {:closed true} :badge/label diff --git a/src/sb/app/entity/data.cljc b/src/sb/app/entity/data.cljc index d7eae1d3..b6eadc3f 100644 --- a/src/sb/app/entity/data.cljc +++ b/src/sb/app/entity/data.cljc @@ -88,20 +88,46 @@ :entity/description :entity/created-at :entity/deleted-at + :entity/video {:image/avatar [:entity/id]} {:image/background [:entity/id]} {:entity/domain-name [:domain-name/name]}]) +(defn required? [parent-schema child-attr] + (-> parent-schema + (mu/find child-attr) + (mu/-required-map-entry?))) + +(defn ignore-optional-nils [parent-schema m] + (reduce-kv (fn [m k v] + (if (and (nil? v) (not (required? parent-schema k))) + (dissoc m k) + m)) + m + m)) + +(defn retract-nils + [m] + (let [nils (->> m (filter #(nil? (val %))) (map key)) + m (apply dissoc m nils) + e (:db/id m)] + (cond-> [] + (seq m) (conj m) + (seq nils) (into (for [a nils] [:db/retract e a]))))) + (q/defx save-attributes! {:prepare [az/with-account-id!]} [{:keys [account-id]} e m] (let [e (sch/wrap-id e) - _ (validate/assert-can-edit! e account-id) - {:as entity :keys [entity/id entity/kind]} (db/entity e) - parent-schema (-> (keyword (name kind) "as-map") - (@sch/!malli-registry)) - txs [(assoc m :db/id e)]] - (validate/assert m (mu/select-keys parent-schema (keys m))) + _ (validate/assert-can-edit! account-id e) + txs (-> (assoc m :db/id e) + retract-nils)] + + (let [parent-schema (-> (keyword (name (:entity/kind (db/entity e))) "as-map") + (@sch/!malli-registry)) + without-nils (ignore-optional-nils parent-schema m)] + (validate/assert without-nils (mu/select-keys parent-schema (keys without-nils)))) + (try (db/transact! txs) (catch Exception e (def E e) (throw e))) diff --git a/src/sb/app/entity/ui.cljc b/src/sb/app/entity/ui.cljc index f2b01a38..64276bd5 100644 --- a/src/sb/app/entity/ui.cljc +++ b/src/sb/app/entity/ui.cljc @@ -1,18 +1,12 @@ (ns sb.app.entity.ui - (:require [clojure.string :as str] - [inside-out.forms :as io] - [re-db.api :as db] + (:require [inside-out.forms :as io] [sb.app.asset.ui :as asset.ui] - [sb.app.domain-name.ui :as domain.ui] [sb.app.entity.data :as data] - [sb.app.field.ui :as field.ui] - [sb.app.form.ui :as form.ui] [sb.app.views.ui :as ui] + [sb.authorize :as az] [sb.icons :as icons] [sb.routing :as routing] [sb.schema :as sch] - [sb.util :as u] - [sb.validate :as validate] [yawn.hooks :as h] [yawn.view :as v])) @@ -84,7 +78,7 @@ [:div.line-clamp-2 title]]]) (ui/defview settings-button [entity] - (when-let [path (and (validate/editing-role? (:member/roles entity)) + (when-let [path (and (az/editor-role? (:member/roles entity)) (some-> (routing/entity-route entity 'admin-ui/settings) routing/path-for))] [:a.button diff --git a/src/sb/app/field/data.cljc b/src/sb/app/field/data.cljc index 730f27eb..5eab7f06 100644 --- a/src/sb/app/field/data.cljc +++ b/src/sb/app/field/data.cljc @@ -202,7 +202,7 @@ (q/defx add-field {:prepare [az/with-account-id!]} [{:keys [account-id]} e a new-field] - (validate/assert-can-edit! e account-id) + (validate/assert-can-edit! account-id e) (let [e (sch/wrap-id e) existing-fields (a (db/entity e)) field (assoc new-field :field/id (dl/new-uuid :field))] @@ -213,7 +213,7 @@ (q/defx remove-field {:prepare [az/with-account-id!]} [{:keys [account-id]} parent-id a field-id] - (validate/assert-can-edit! parent-id account-id) + (validate/assert-can-edit! account-id parent-id) (let [parent (db/entity (sch/wrap-id parent-id))] (db/transact! [[:db/add (:db/id parent) a (->> (get parent a) (remove (comp #{field-id} :field/id)) @@ -246,7 +246,7 @@ :prose/format (:prose/format entry)})) (q/defx save-entry! [{:keys [account-id]} parent-id field-id entry] - (validate/assert-can-edit! parent-id account-id) + (validate/assert-can-edit! account-id parent-id) (let [field (db/entity (sch/wrap-id field-id)) parent (db/entity (sch/wrap-id parent-id)) entries (assoc (get parent :entity/field-entries) field-id entry)] diff --git a/src/sb/app/field/ui.cljc b/src/sb/app/field/ui.cljc index 54b90719..840f79ec 100644 --- a/src/sb/app/field/ui.cljc +++ b/src/sb/app/field/ui.cljc @@ -152,13 +152,16 @@ {:disabled (not can-edit?) :class ["w-full" (:input classes)] :placeholder (:placeholder props) - :on-key-down (let [save #(when (io/ancestor-by ?field :field/persisted?) - (j/call % :preventDefault) - (entity.data/maybe-save-field ?field))] - (ui/keydown-handler (merge {:Meta-Enter save - :Escape cancel! + :on-key-down (let [save (fn [^js e] + (if (io/ancestor-by ?field :field/persisted?) + (entity.data/maybe-save-field ?field) + (some-> (j/get-in e [:target :form]) + (j/call :requestSubmit))) + (.preventDefault e))] + (ui/keydown-handler (merge {:Escape cancel! :Meta-. cancel!} - (when-not multi-line? + (if multi-line? + {:Meta-Enter save} {:Enter save}) keybindings)))}))]) (when-let [postfix (or (:field/postfix props) @@ -209,22 +212,17 @@ (ui/defview video-field {:key (fn [?field _] #?(:cljs (goog/getUid ?field)))} [?field {:as props :keys [field/can-edit?]}] - (let [!editing? (h/use-state (nil? @?field))] - [:div.field-wrapper - ;; preview shows persisted value? - [:div.flex.items-center - [:div.flex-auto (form.ui/show-label ?field (:field/label props))] - #_(when can-edit? - [:div.place-self-end [:a {:on-click #(swap! !editing? not)} - [(if @!editing? icons/chevron-up icons/chevron-down) "icon-gray"]]])] - (when-let [url (:video/url @?field)] - [show-video url]) - (when can-edit? - (text-field ?field (merge props - {:field/label false - :placeholder "YouTube or Vimeo url" - :field/wrap (partial hash-map :video/url) - :field/unwrap :video/url})))])) + [:div.field-wrapper + ;; preview shows persisted value? + [:div.flex.items-center + (when (and can-edit? (not (u/some-str @?field))) + [:div.flex-auto (form.ui/show-label ?field (:field/label props))])] + (when-let [url @?field] + [show-video url]) + (when can-edit? + (text-field ?field (merge props + {:field/label false + :placeholder "YouTube or Vimeo url"})))]) (ui/defview select-field [?field {:as props :field/keys [label options]}] [:div.field-wrapper @@ -294,28 +292,36 @@ [:button.flex.items-center {:type "submit"} [icons/checkmark "w-5 h-5 icon-gray"]]] (form.ui/show-field-messages ?badges)]]])))) -(ui/defview badges-field [?badges {:field/keys [can-edit?]}] - [:div.flex.gap-1 - (for [{:as ?badge - :syms [?label ?color]} ?badges - :let [bg (or (u/some-str @?color) "#ffffff") - color (color/contrasting-text-color bg)]] - [radix/context-menu {:trigger [:div.rounded.bg-badge.text-badge-txt.py-1.px-2.text-sm.inline-flex - {:key @?label - :style {:background-color bg :color color}} @?label] - :items [[radix/context-menu-item - {:on-select (fn [] - (io/remove-many! ?badge) - (entity.data/maybe-save-field ?badges))} - (t :tr/remove)]]}]) - (let [!open (h/use-state false)] - (when can-edit? - [:div.inline-flex.text-sm.gap-1.items-center - {:on-click #(reset! !open true)} - (when-not (seq ?badges) "Add Badge") - [icons/plus "w-4 h-4 icon-gray"] +#?(:cljs + (defn use-new-badge [?badges] + (let [!open (h/use-state false)] + [#(reset! !open true) (when @!open - [new-badge ?badges #(reset! !open false)])]))]) + [new-badge ?badges #(reset! !open false)])]))) + +(ui/defview badges-field [?badges {:keys [field/can-edit? member/roles]}] + (let [[new! new-screen] (use-new-badge ?badges) + board-admin? (:role/board-admin roles)] + (when (or (seq ?badges) board-admin?) + [:div.flex.gap-1 + (for [{:as ?badge + :syms [?label ?color]} ?badges + :let [bg (or (u/some-str @?color) "#ffffff") + color (color/contrasting-text-color bg)]] + [radix/context-menu {:trigger [:div.rounded.bg-badge.text-badge-txt.py-1.px-2.text-sm.inline-flex + {:key @?label + :style {:background-color bg :color color}} @?label] + :items [[radix/context-menu-item + {:on-select (fn [] + (io/remove-many! ?badge) + (entity.data/maybe-save-field ?badges))} + (t :tr/remove)]]}]) + (when board-admin? + [:div.inline-flex.text-sm.gap-1.items-center.rounded.hover:bg-gray-100.p-1 + {:on-click new!} + (when-not (seq ?badges) [:span.cursor-default (t :tr/add-badge)]) + [icons/plus "w-4 h-4 icon-gray"] + new-screen])]))) (ui/defview image-field [?field props] @@ -481,9 +487,7 @@ props (merge (select-keys field [:field/label :field/hint :field/options]) (select-keys props [:field/can-edit?]))] (case (:field/type field) - :field.type/video [video-field - ('video/?url ?entry) - props] + :field.type/video [video-field ('video/?url ?entry) props] :field.type/select [select-field ('select/?value ?entry) props] :field.type/link-list [link-list-field ('link-list/?links ?entry) props] :field.type/image-list [images-field ('image-list/?images ?entry) props] diff --git a/src/sb/app/member/data.cljc b/src/sb/app/member/data.cljc index b5bfaf8f..405e38de 100644 --- a/src/sb/app/member/data.cljc +++ b/src/sb/app/member/data.cljc @@ -20,8 +20,7 @@ :member/as-map]]} :member/role {s- [:enum :role/admin - :role/owner - :role/collaborate + :role/editor :role/member]} :member/roles (merge sch/keyword sch/many diff --git a/src/sb/app/org/data.cljc b/src/sb/app/org/data.cljc index acb9573f..64d5d716 100644 --- a/src/sb/app/org/data.cljc +++ b/src/sb/app/org/data.cljc @@ -95,7 +95,7 @@ (q/defx settings! {:prepare [az/with-account-id!]} [{:keys [account-id]} {:as org :keys [entity.data/id]}] - (validate/assert-can-edit! id account-id) + (validate/assert-can-edit! account-id id) (let [org (validate/conform org :org/as-map)] (db/transact! [org]) {:body org})) @@ -107,7 +107,7 @@ (validate/conform :org/as-map)) member (-> {:member/entity org :member/account account-id - :member/roles #{:role/owner}} + :member/roles #{:role/admin}} (dl/new-entity :member))] (db/transact! [member]) org)) \ No newline at end of file diff --git a/src/sb/app/project/data.cljc b/src/sb/app/project/data.cljc index 81e4a3d0..86ff26cf 100644 --- a/src/sb/app/project/data.cljc +++ b/src/sb/app/project/data.cljc @@ -102,7 +102,7 @@ ;; TODO ;; verify that user is allowed to create a new project in parent (let [project (dl/new-entity project :project :by account-id) - membership (member.data/new-entity-with-membership project account-id #{:role/owner})] + membership (member.data/new-entity-with-membership project account-id #{:role/admin})] (validate/assert project :project/as-map) (db/transact! [membership]) {:entity/id (:entity/id project)})) diff --git a/src/sb/app/project/ui.cljc b/src/sb/app/project/ui.cljc index 1b90a665..6bc10a8b 100644 --- a/src/sb/app/project/ui.cljc +++ b/src/sb/app/project/ui.cljc @@ -90,25 +90,24 @@ #?(:cljs (defn use-dev-panel [entity] - (let [m {"Current" (:member/roles entity) - "Editor" #{:role/editor} - "Viewer" #{} - "Admin" #{:role/admin}} - !roles (h/use-state "Current") - roles (m @!roles)] - [(validate/editing-role? roles) + (let [m {"Current User" (:member/roles entity) + "Project Editor" #{:role/project-editor} + "Board Admin" #{:role/board-admin} + "Visitor" #{}} + !roles (h/use-state "Current User") + roles (m @!roles)] + [(az/editor-role? roles) roles (when (ui/dev?) [radix/select-menu {:value @!roles :on-value-change #(reset! !roles %) - :field/classes {:trigger "flex items-center px-2 icon-gray text-sm self-start" + :field/classes {:trigger "flex items-center px-2 icon-gray text-sm self-start focus-visible:ring-0" :content (str radix/menu-content-classes " text-sm")} :field/can-edit? true - :field/options [{:value "Current" :text "Current"} - {:value "Editor" :text "Editor"} - {:value "Viewer" :text "Viewer"} - {:value "Admin" :text "Admin"}]}]) - ]))) + :field/options [{:value "Current User" :text "Current User"} + {:value "Project Editor" :text "Project Editor"} + {:value "Visitor" :text "Visitor"} + {:value "Board Admin" :text "Board Admin"}]}])]))) (def title-icon-classes "px-1 py-2 icon-light-gray") @@ -121,13 +120,7 @@ {:route "/p/:project-id" :view/router :router/modal} [params] - (let [{:as project - :entity/keys [title - description - video - field-entries] - :keys [project/badges - member/roles]} (data/show params) + (let [project (data/show params) [can-edit? roles dev-panel] (use-dev-panel project) field-params {:member/roles roles :field/can-edit? can-edit?}] @@ -144,8 +137,7 @@ dev-panel [:div.flex.self-start.ml-auto.px-1.rounded-bl-lg.border-b.border-l.relative - - (when (:role/admin roles) + (when (:role/board-admin roles) [radix/dropdown-menu {:trigger [:div.flex.items-center [icons/ellipsis-horizontal "rotate-90 icon-gray"]] :children [[{:on-click #()} "Add Badge"]]}]) @@ -165,6 +157,7 @@ (entity.ui/use-persisted-attr project :entity/description (merge field-params {:field/label false :placeholder "Description"})) + (entity.ui/use-persisted-attr project :entity/video field-params) (entity.ui/use-persisted-attr project :project/badges field-params) (entity.ui/use-persisted-attr project :entity/field-entries @@ -172,6 +165,4 @@ :member/roles roles :field/can-edit? can-edit?}) [:section.flex-v.gap-2.items-start - [manage-community-actions project (:project/community-actions project)]] - (when video - [field.ui/show-video video])]]])) + [manage-community-actions project (:project/community-actions project)]]]]])) diff --git a/src/sb/authorize.cljc b/src/sb/authorize.cljc index b77d2134..e8642d70 100644 --- a/src/sb/authorize.cljc +++ b/src/sb/authorize.cljc @@ -5,8 +5,11 @@ [sb.schema :as sch])) (defn editor-role? [roles] - (or (:role/admin roles) - (:role/collaborate roles))) + (or (:role/self roles) + (:role/project-admin roles) + (:role/project-editor roles) + (:role/board-admin roles) + (:role/org-admin roles))) #?(:clj (defn membership-id [account-id entity-id] @@ -29,29 +32,38 @@ (unauthorized! "User not signed in")) params)) -#?(:clj - (defn get-roles [account-id entity-id] - (let [account-id (dl/resolve-id account-id) - entity-id (dl/resolve-id entity-id)] - (if (= account-id entity-id) - #{:role/owner} - (db/get (membership-id account-id entity-id) :member/roles))))) +(defn entity-roles [account-id entity-id] + (let [account-id (dl/resolve-id account-id) + entity-id (dl/resolve-id entity-id)] + (if (= account-id entity-id) + #{:role/self} + (let [kind (:entity/kind (db/entity entity-id)) + roles (:member/roles (db/entity + ;; queries merge :member/roles onto entities, so in cljs we can read roles directly from the entity. + ;; re-db in-membery doesn't support tuple attrs that we use for memberships. + #?(:cljs entity-id + :clj (membership-id account-id entity-id)))) + scoped-roles (for [role roles] + (keyword "role" (str (name kind) "-" (name role))))] + scoped-roles)))) -#?(:clj - (defn roles-for [account-id entity-id] - (->> (db/entity entity-id) - (iterate :entity/parent) - (take-while identity) - (mapcat (partial get-roles account-id)) - (into #{})))) +(defn all-roles [account-id entity-id] + (->> (db/entity entity-id) + (iterate :entity/parent) + (take-while identity) + (mapcat (partial entity-roles account-id)) + (into #{}))) + +(def can-edit? (comp editor-role? all-roles)) (comment (let [account-id [:entity/id #uuid"b03a4669-a7ef-3e8e-bddc-8413e004c338"] - entity-id [:entity/id #uuid"a4ede6c0-22c2-3902-86ef-c1b8149d0a75"]] - (roles-for account-id entity-id))) + entity-id [:entity/id #uuid"a4ede6c0-22c2-3902-86ef-c1b8149d0a75"]] + (all-roles account-id entity-id) + (db/touch (db/entity entity-id)))) #?(:clj (defn with-roles [entity-key] (fn [req params] (when-let [account-id (some-> (-> req :account :entity/id) sch/wrap-id)] - (assoc params :member/roles (roles-for account-id (entity-key params))))))) \ No newline at end of file + (assoc params :member/roles (all-roles account-id (entity-key params))))))) \ No newline at end of file diff --git a/src/sb/i18n.cljc b/src/sb/i18n.cljc index 8cb35a72..57b14739 100644 --- a/src/sb/i18n.cljc +++ b/src/sb/i18n.cljc @@ -36,6 +36,9 @@ See https://iso639-3.sil.org/code_tables/639/data/all for list of codes" {:tr/cancel {:en "Cancel" :fr "Annuler" :es "Cancelar"} + :tr/add-badge {:en "Add badge" + :fr "Ajouter un badge" + :es "Añadir insignia"} :tr/further-instructions {:en "Further instructions" :fr "Instructions supplémentaires" :es "Instrucciones adicionales"} diff --git a/src/sb/migration/one_time.clj b/src/sb/migration/one_time.clj index 729aa723..687e97cf 100644 --- a/src/sb/migration/one_time.clj +++ b/src/sb/migration/one_time.clj @@ -27,10 +27,9 @@ (defn role-kw [role-name] (case role-name - "editor" :role/collaborate - "collaborator" :role/collaborate + "editor" :role/editor "admin" :role/admin - "owner" :role/owner + "owner" :role/admin "member" nil)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -433,9 +432,9 @@ (defn video-value [v] (when (and v (not (str/blank? v))) - (cond (re-find #"vimeo" v) {:video/url v} - (re-find #"youtube" v) {:video/url v} - :else {:video/url (str "https://www.youtube.com/watch?v=" v)}))) + (cond (re-find #"vimeo" v) v + (re-find #"youtube" v) v + :else (str "https://www.youtube.com/watch?v=" v)))) (defn parse-fields [managed-by-k to-k] (fn [m] @@ -959,7 +958,7 @@ (let [role (if (and (not (:role member)) (= account-id (some->> (:entity/created-by project) (to-uuid :account)))) - :role/owner + :role/admin (some-> (:role member) role-kw))] (merge {:entity/id (composite-uuid :member project-id account-id) :entity/kind :member diff --git a/src/sb/validate.cljc b/src/sb/validate.cljc index 3060a00c..f21b62cd 100644 --- a/src/sb/validate.cljc +++ b/src/sb/validate.cljc @@ -7,6 +7,7 @@ [sb.server.datalevin :as dl] [sb.i18n :refer [t]] [re-db.api :as db] + [sb.authorize :as az] [sb.util :as u])) (defn humanized [schema value] @@ -91,17 +92,9 @@ (assert (-> (mu/optional-keys schema) (mu/assoc :entity/domain-name (mu/optional-keys :domain-name/as-map))))))) -(defn editing-role? [roles] - (boolean (some #{:role/owner :role/admin :role/collaborate} roles))) - -(defn can-edit? [entity-id account-id] - (let [entity-id (dl/resolve-id entity-id) - account-id (dl/resolve-id account-id)] - (or (= entity-id account-id) ;; entity _is_ account - #?(:clj (->> (dl/entity [:member/entity+account [entity-id account-id]]) - :member/roles - editing-role?) - :cljs (editing-role? (db/get entity-id :member/roles)))))) +(defn can-edit? [account-id entity-id] + (-> (az/entity-roles account-id entity-id) + az/editor-role?)) (defn permission-denied! [] (ex-info "Permission denied" @@ -116,8 +109,8 @@ :inside-out.forms/messages-by-path {() [reason]}}}})) #?(:clj - (defn assert-can-edit! [entity-id account-id] - (when-not (can-edit? entity-id account-id) + (defn assert-can-edit! [account-id entity-id] + (when-not (can-edit? account-id entity-id) (permission-denied!))))