Skip to content

Commit

Permalink
recent-ids, tag validation
Browse files Browse the repository at this point in the history
  • Loading branch information
mhuebert committed Jan 12, 2024
1 parent 72078db commit 8211dd5
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 90 deletions.
12 changes: 1 addition & 11 deletions src/sb/app/account/data.cljc
Expand Up @@ -47,26 +47,16 @@
[{:keys [account-id]}]
(u/timed `all
(->> (q/pull '[{:member/_account [:member/roles
:member/last-visited
{:member/entity [:entity/id
:entity/kind
:entity/title
:entity/created-at
{:image/avatar [:entity/id]}
{:image/background [:entity/id]}]}]}]
account-id)
:member/_account
(map #(u/lift-key % :member/entity)))))

(q/defquery recent-ids
{:endpoint {:query true}
:prepare az/with-account-id!}
[params]
(->> (all params)
(filter :member/last-visited)
(sort-by :member/last-visited #(compare %2 %1))
(into #{} (comp (take 8)
(map :entity/id)))))

#?(:clj
(defn login!
{:endpoint {:post "/login"}
Expand Down
3 changes: 1 addition & 2 deletions src/sb/app/board/data.cljc
Expand Up @@ -102,8 +102,7 @@
)

(q/defquery show
{:prepare [(member.data/member:log-visit! :board-id)
(az/with-roles :board-id)]}
{:prepare [(az/with-roles :board-id)]}
[{:keys [board-id member/roles]}]
(u/timed `show
(if-let [board (db/pull `[~@entity.data/entity-keys
Expand Down
39 changes: 33 additions & 6 deletions src/sb/app/entity/data.cljc
Expand Up @@ -6,7 +6,9 @@
[sb.server.datalevin :as dl]
[sb.schema :as sch :refer [? s- unique-uuid]]
[sb.validate :as validate]
[inside-out.forms :as io]))
[inside-out.forms :as io]
[clojure.set :as set]
[sb.util :as u]))

(sch/register!
(merge
Expand Down Expand Up @@ -119,14 +121,39 @@
(seq m) (conj m)
(seq nils) (into (for [a nils] [:db/retract e a])))))

(def rules
{:entity/tags (fn validate-changed-tags [roles k entity m]
(when-not (= :member (:entity/kind entity))
(validate/validation-failed! "Only members may have tags"))
(let [tags-before (into #{} (map :tag/id) (k entity))
tags-after (into #{} (map :tag/id) (k m))
tags-changed (concat
(set/difference tags-before tags-after) ;; removed
(set/difference tags-after tags-before)) ;; added
admin? (:role/board-admin roles)]
(when (seq tags-changed)
(let [tag-defs (-> entity :member/entity :entity/member-tags (u/index-by :tag/id))]
(doseq [tag-id tags-changed ;; added
:let [tag (get tag-defs tag-id)]
:when (:tag/restricted? tag)]
(when (and (not admin?) (:tag/restricted? tag))
(validate/permission-denied! "Only admins may modify restricted tags"))
(when (not tag)
(validate/validation-failed! (str "Tag " tag-id " does not exist"))))))))})

(q/defx save-attributes!
{:prepare [az/with-account-id!]}
[{:keys [account-id]} e m]
(let [e (sch/wrap-id e)
(let [e (sch/wrap-id e)
entity (dl/entity e)
_ (validate/assert-can-edit! account-id entity)
txs (-> (assoc m :db/id e)
retract-nils)]
roles (az/all-roles account-id entity)
_ (validate/assert-can-edit! roles)
txs (-> (assoc m :db/id e)
retract-nils)]
(doseq [k (keys m)
:let [rule (get rules k)]
:when rule]
(rule roles k entity m))

(let [parent-schema (-> (keyword (name (:entity/kind entity)) "as-map")
(@sch/!malli-registry))
Expand Down Expand Up @@ -190,4 +217,4 @@
txs (map-indexed (fn [i x]
[:db/add [:entity/id (db/get x :entity/id)] order-by i]) siblings)]
(db/transact! txs)
txs))
txs))
50 changes: 31 additions & 19 deletions src/sb/app/member/data.cljc
@@ -1,17 +1,15 @@
(ns sb.app.member.data
(:require #?(:clj [java-time.api :as time])
[re-db.api :as db]
[sb.app.entity.data :as entity.data]
(:require [sb.app.entity.data :as entity.data]
[sb.authorize :as az]
[sb.query :as q]
[sb.schema :as sch :refer [? s-]]
[sb.server.datalevin :as dl])
[sb.server.datalevin :as dl]
[sb.util :as u]
[re-db.api :as db])
#?(:clj (:import [java.util Date])))

(sch/register!
{:roles/as-map {s- :member/as-map}
:member/last-visited (merge sch/instant
{s- 'inst?})
:member/entity+account (merge {:db/tupleAttrs [:member/entity :member/account]}
sch/unique-value)
:member/_entity {s- [:or
Expand Down Expand Up @@ -63,7 +61,6 @@
(? :member/newsletter-subscription?)
(? :entity/tags)
(? :member/roles)
(? :member/last-visited)

;; TODO, backfill?
(? :entity/created-at)
Expand Down Expand Up @@ -96,6 +93,33 @@
(when-not (membership-id account-id entity-id)
(throw (ex-info "Not a member" {:status 403})))))

(defn member-active? [member]
(and (not (:member/inactive? member))
(not (:entity/deleted-at member))))

#?(:clj
(defn can-view? [account-id entity]
(let [visibility-entity (case (:entity/kind entity)
(:board :org) entity
:project (:entity/parent entity)
:member (:member/entity entity))]
(or (:entity/public? visibility-entity)
(some-> (membership-id account-id entity)
db/entity
member-active?)))))

(q/defquery descriptions
{:endpoint {:query true}
:prepare az/with-account-id!}
[{:as params :keys [account-id ids]}]
(u/timed `descriptions
(into []
(comp (map (comp db/entity sch/wrap-id))
(filter member-active?)
(map (db/pull `[~@entity.data/entity-keys
:entity/public?])))
ids)))

(q/defquery search
{:prepare [az/with-account-id!]}
[{:as params :keys [account-id entity-id search-term]}]
Expand Down Expand Up @@ -132,18 +156,6 @@
;:entity-id [:entity/id #uuid "a1630339-64b3-3604-8110-0f22355e12be"]
:search-term "matt"}))

#?(:clj
(defn member:log-visit! [entity-key]
(fn [req params]
(when-let [id (some-> (-> req :account :entity/id)
(membership-id (entity-key params)))]
(re-db.reactive/silently
(db/transact! [[:db/add id :member/last-visited
(-> (time/offset-date-time)
time/instant
Date/from)]])))
params)))

(defn new-entity-with-membership [entity account-id roles]
{:entity/id (random-uuid)
:entity/kind :member
Expand Down
3 changes: 1 addition & 2 deletions src/sb/app/org/data.cljc
Expand Up @@ -59,8 +59,7 @@
org-id))

(q/defquery show
{:prepare [az/with-account-id!
(member.data/member:log-visit! :org-id)]}
{:prepare [az/with-account-id!]}
[{:keys [org-id]}]
(q/pull `[~@entity.data/entity-keys
{:entity/_parent ~entity.data/entity-keys}]
Expand Down
43 changes: 24 additions & 19 deletions src/sb/app/views/header.cljc
Expand Up @@ -5,9 +5,8 @@
[sb.app.asset.ui :as asset.ui]
[sb.app.chat.data :as chat.data]
[sb.app.chat.ui :as chat.ui]
[sb.app.account.data :as account.data]
[sb.app.entity.ui :as entity.ui]
[sb.authorize :as az]
[sb.app.member.data :as member.data]
[sb.i18n :as i :refer [t]]
[sb.routing :as routes]
[sb.app.views.ui :as ui]
Expand All @@ -16,7 +15,8 @@
[sb.util :as u]
[yawn.hooks :as h]
[yawn.util :as yu]
[sb.routing :as routing]))
[sb.routing :as routing]
[sb.query :as q]))

#?(:cljs
(defn lang-menu-content []
Expand All @@ -25,12 +25,12 @@
(p/do
(i/set-locale! {:i18n/locale v})
(js/window.location.reload)))]
(map (fn [lang]
(let [selected (= lang current-locale)]
[{:selected selected
:on-click (when-not selected #(on-select lang))}
(get-in i/dict [lang :meta/lect])]))
(keys i/dict)))))
(mapv (fn [lang]
(let [selected (= lang current-locale)]
[{:selected selected
:on-click (when-not selected #(on-select lang))}
(get-in i/dict [lang :meta/lect])]))
(keys i/dict)))))

(ui/defview lang [classes]
[:div.inline-flex.flex-row.items-center {:class ["hover:text-txt-faded"
Expand Down Expand Up @@ -84,12 +84,25 @@
[{:on-click #(routes/nav! 'sb.app.account-ui/logout!)} (t :tr/logout)]
[{:sub? true
:trigger [icons/languages "w-5 h-5"]
:children (lang-menu-content)}]]})]
:items (lang-menu-content)}]]})]
[:a.btn.btn-transp.px-3.py-1.h-7
{:href (routes/path-for ['sb.app.account-ui/sign-in])} (t :tr/continue-with-email)]))

(def down-arrow (icons/chevron-down:mini "ml-1 -mr-1 w-4 h-4"))

(ui/defview recents []
(when-let [entities (-> (:value (q/use [`member.data/descriptions {:ids @routing/!recent-ids}]))
ui/use-last-some)]
(when (seq entities)
(radix/dropdown-menu
{:id :show-recents
:trigger [:button (t :tr/recent) down-arrow]
:items (into []
(map (fn [entity]
[{:on-select #(routes/nav! (routes/entity-route entity 'ui/show) entity)}
(:entity/title entity)]))
entities)}))))

(ui/defview entity [{:as entity
:keys [entity/title
member/roles
Expand All @@ -106,15 +119,7 @@
(into [:div.flex.gap-1]
(concat children
[(entity.ui/settings-button entity)
(radix/dropdown-menu
{:id :show-recents
:trigger [:button (t :tr/recent) down-arrow]
:items (map (fn [entity]
[{:on-select #(routes/nav! (routes/entity-route entity 'ui/show) entity)}
(:entity/title entity)])
(when-let [recent-ids (account.data/recent-ids nil)]
(filterv (comp recent-ids :entity/id)
(account.data/all nil))))})
(recents)
(radix/dropdown-menu
{:id :new
:trigger [:button (t :tr/new) down-arrow]
Expand Down
2 changes: 1 addition & 1 deletion src/sb/app/views/radix.cljc
Expand Up @@ -32,7 +32,7 @@
:sideOffset 0}))

(defn menu-item-classes [selected?]
(str "block px-3 rounded mx-1 relative hover:outline-0 data-[highlighted]:bg-gray-100 cursor-default "
(str "flex items-center px-3 h-8 rounded mx-1 relative hover:outline-0 data-[highlighted]:bg-gray-100 cursor-default "
(if selected?
"text-txt/50 "
(str "hover:bg-primary/5 "
Expand Down
9 changes: 9 additions & 0 deletions src/sb/app/views/ui.cljs
Expand Up @@ -143,6 +143,15 @@
[image-url (@!loaded image-url)])
[@!last-loaded (not= @!last-loaded image-url)]))

(defn use-last-some [value]
(let [!last-value (h/use-state value)]
(h/use-effect (fn []
(when (and (some? value) (not= value @!last-value))
(reset! !last-value value))
nil)
(h/use-deps value))
@!last-value))

(def email-schema [:re #"^[^@]+@[^@]+$"])

(comment
Expand Down
38 changes: 26 additions & 12 deletions src/sb/client/local_storage.cljc
@@ -1,21 +1,35 @@
(ns sb.client.local-storage
(:require [re-db.memo :as memo]
(:require [applied-science.js-interop :as j]
[re-db.memo :as memo]
[re-db.reactive :as r]
[re-db.sync.transit :as transit]))

#?(:cljs
(defn- set-item [k v]
{:pre [(string? k)]}
(j/call-in js/window [:localStorage :setItem] k (transit/pack v))))

#?(:cljs
(defn- get-item [k]
{:pre [(string? k)]}
(transit/unpack (j/call-in js/window [:localStorage :getItem] k))))

(comment
(get-item "foo")
(set-item "foo" 1)
(.. js/window -localStorage (getItem "foo"))
(.. js/window -localStorage (setItem "foo" "bar"))

)

(memo/defn-memo $local-storage
"Returns a 2-way syncing local-storage atom identified by `k` with default value"
[k default]
"Returns a 2-way syncing local-storage atom identified by `k` with default value"
[k default]
#?(:cljs
(let [k (str k)]
(doto (r/atom (or (-> (.-localStorage js/window)
(.getItem k)
transit/unpack)
default))
(doto (r/atom (or (get-item k)
(doto default (->> (set-item k)))))
(add-watch ::update-local-storage
(fn [_k _atom _old new]
(.setItem (.-localStorage js/window)
k
(transit/pack new))))))
(fn [_k _atom _old v] (set-item k v)))))
:clj (r/atom default)))

15 changes: 15 additions & 0 deletions src/sb/routing.cljc
Expand Up @@ -9,6 +9,7 @@
[re-db.reactive :as r]
[re-db.xform :as xf]
[reitit.core :as reit]
[sb.client.local-storage :as local]
[sb.http :as http]
[sb.query-params :as query-params]
[sb.schema :as sch]
Expand Down Expand Up @@ -69,6 +70,20 @@
(defn tag->endpoint [tag method]
(get-in @!tag->endpoints [tag method]))

(defonce !recent-ids (local/$local-storage ::recently-viewed-ids ()))

(r/redef !track-recents
(r/reaction!
(swap! !recent-ids
(fn [ids]
(->> (concat (->> @!location vals
(mapcat :match/params)
(filter (comp #{:org-id :board-id :project-id} key))
(map (comp sch/unwrap-id val)))
ids)
distinct
(take 6))))))

(defn aux:parse-path
"Given a `path` string, returns map of {<route-name>, <path>}
Expand Down

0 comments on commit 8211dd5

Please sign in to comment.