From 9ffd00d8218bc13320661370bdd14a38d4661471 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 3 Aug 2022 12:48:48 +0200 Subject: [PATCH 01/11] :bug: Fix clipped elements affect artboards centering --- CHANGES.md | 1 + common/src/app/common/geom/shapes/bounds.cljc | 31 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ca06d87d7..50f914595 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -34,6 +34,7 @@ ### :bug: Bugs fixed +- Fix clipped elements affect boards and centering [Taiga #3666](https://tree.taiga.io/project/penpot/issue/3666) - Fix intro action in multi input [Taiga #3541](https://tree.taiga.io/project/penpot/issue/3541) - Fix team default image [Taiga #3919](https://tree.taiga.io/project/penpot/issue/3919) - Fix problem with group coordinates [#2008](https://github.com/penpot/penpot/issues/2008) diff --git a/common/src/app/common/geom/shapes/bounds.cljc b/common/src/app/common/geom/shapes/bounds.cljc index 83a1b25f5..367935298 100644 --- a/common/src/app/common/geom/shapes/bounds.cljc +++ b/common/src/app/common/geom/shapes/bounds.cljc @@ -129,26 +129,27 @@ (-> (get-shape-filter-bounds shape) (add-padding (calculate-padding shape true)))) - bounds - (cph/reduce-objects - objects - (fn [shape] - (and (d/not-empty? (:shapes shape)) - (or (not (cph/frame-shape? shape)) - (:show-content shape)) + bounds (if (cph/frame-shape? shape) + [(calculate-base-bounds shape)] + (cph/reduce-objects + objects + (fn [shape] + (and (d/not-empty? (:shapes shape)) + (or (not (cph/frame-shape? shape)) + (:show-content shape)) - (or (not (cph/group-shape? shape)) - (not (:masked-group? shape))))) + (or (not (cph/group-shape? shape)) + (not (:masked-group? shape))))) - (:id shape) + (:id shape) - (fn [result shape] - (conj result (get-object-bounds objects shape))) + (fn [result shape] + (conj result (get-object-bounds objects shape))) - [(calculate-base-bounds shape)]) + [(calculate-base-bounds shape)])) - - children-bounds (or (:children-bounds shape) (gsr/join-selrects bounds)) + children-bounds (cond->> (gsr/join-selrects bounds) + (not (cph/frame-shape? shape)) (or (:children-bounds shape))) filters (shape->filters shape) blur-value (or (-> shape :blur :value) 0)] From efa382c9069e04edeb37a91c548545459e23e357 Mon Sep 17 00:00:00 2001 From: Eva Date: Fri, 5 Aug 2022 11:24:34 +0200 Subject: [PATCH 02/11] :lipstick: Improve team icon --- .../resources/styles/main/partials/dashboard-team.scss | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/resources/styles/main/partials/dashboard-team.scss b/frontend/resources/styles/main/partials/dashboard-team.scss index 6bab8a4c3..acef161a4 100644 --- a/frontend/resources/styles/main/partials/dashboard-team.scss +++ b/frontend/resources/styles/main/partials/dashboard-team.scss @@ -289,8 +289,8 @@ display: flex; justify-content: center; align-items: center; - width: 70px; - height: 70px; + width: 71px; + height: 71px; border-radius: 50%; color: $color-white; background: $color-primary-dark; @@ -304,6 +304,10 @@ &:hover { .update-overlay { opacity: 1; + width: 72px; + height: 72px; + top: 14px; + left: 14px; } } } From adbadc874378b2a54000ff53862a833845d9c06a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 4 Aug 2022 22:50:02 +0200 Subject: [PATCH 03/11] :recycle: Refactor session management --- backend/deps.edn | 2 +- backend/src/app/config.clj | 14 +- backend/src/app/http/session.clj | 305 ++++++++++++++++--------------- backend/src/app/main.clj | 10 +- 4 files changed, 168 insertions(+), 163 deletions(-) diff --git a/backend/deps.edn b/backend/deps.edn index aabbb28a2..b57ee55ec 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -20,7 +20,7 @@ io.lettuce/lettuce-core {:mvn/version "6.1.8.RELEASE"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} - funcool/yetti {:git/tag "v9.3" :git/sha "c6e2d0d" + funcool/yetti {:git/tag "v9.8" :git/sha "fbe1d7d" :git/url "https://github.com/funcool/yetti.git" :exclusions [org.slf4j/slf4j-api]} diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index e61d7dd49..aa5e65d2f 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -42,8 +42,7 @@ data)) (def defaults - { - :database-uri "postgresql://postgres/penpot" + {:database-uri "postgresql://postgres/penpot" :database-username "penpot" :database-password "penpot" @@ -101,10 +100,14 @@ (s/def ::blocking-executor-parallelism ::us/integer) (s/def ::worker-executor-parallelism ::us/integer) +(s/def ::authenticated-cookie-domain ::us/string) +(s/def ::authenticated-cookie-name ::us/string) +(s/def ::auth-token-cookie-name ::us/string) +(s/def ::auth-token-cookie-max-age ::dt/duration) + (s/def ::secret-key ::us/string) (s/def ::allow-demo-users ::us/boolean) (s/def ::assets-path ::us/string) -(s/def ::authenticated-cookie-domain ::us/string) (s/def ::database-password (s/nilable ::us/string)) (s/def ::database-uri ::us/string) (s/def ::database-username (s/nilable ::us/string)) @@ -140,7 +143,6 @@ (s/def ::http-server-max-multipart-body-size ::us/integer) (s/def ::http-server-io-threads ::us/integer) (s/def ::http-server-worker-threads ::us/integer) -(s/def ::http-session-idle-max-age ::dt/duration) (s/def ::http-session-updater-batch-max-age ::dt/duration) (s/def ::http-session-updater-batch-max-size ::us/integer) (s/def ::initial-project-skey ::us/string) @@ -206,6 +208,9 @@ ::allow-demo-users ::audit-log-archive-uri ::audit-log-gc-max-age + ::auth-token-cookie-name + ::auth-token-cookie-max-age + ::authenticated-cookie-name ::authenticated-cookie-domain ::database-password ::database-uri @@ -246,7 +251,6 @@ ::http-server-max-multipart-body-size ::http-server-io-threads ::http-server-worker-threads - ::http-session-idle-max-age ::http-session-updater-batch-max-age ::http-session-updater-batch-max-size ::initial-project-skey diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index e4e52772a..f3e401641 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -7,33 +7,35 @@ (ns app.http.session (:require [app.common.data :as d] - [app.common.exceptions :as ex] [app.common.logging :as l] - [app.config :as cfg] + [app.config :as cf] [app.db :as db] [app.db.sql :as sql] - [app.metrics :as mtx] - [app.util.async :as aa] [app.util.time :as dt] [app.worker :as wrk] - [clojure.core.async :as a] [clojure.spec.alpha :as s] [integrant.core :as ig] [promesa.core :as p] [promesa.exec :as px] [yetti.request :as yrq])) -;; A default cookie name for storing the session. We don't allow to configure it. -(def token-cookie-name "auth-token") +;; A default cookie name for storing the session. +(def default-auth-token-cookie-name "auth-token") -;; A cookie that we can use to check from other sites of the same domain if a user -;; is registered. Is not intended for on premise installations, although nothing -;; prevents using it if some one wants to. -(def authenticated-cookie-name "authenticated") +;; A cookie that we can use to check from other sites of the same +;; domain if a user is authenticated. +(def default-authenticated-cookie-name "authenticated") + +;; Default value for cookie max-age +(def default-cookie-max-age (dt/duration {:days 7})) + +;; Default age for automatic session renewal +(def default-renewal-max-age (dt/duration {:hours 6})) (defprotocol ISessionStore (read-session [store key]) (write-session [store key data]) + (update-session [store data]) (delete-session [store key])) (defn- make-database-store @@ -47,18 +49,25 @@ (px/with-dispatch executor (let [profile-id (:profile-id data) user-agent (:user-agent data) - now (dt/now) - + created-at (or (:created-at data) (dt/now)) token (tokens :generate {:iss "authentication" - :iat now + :iat created-at :uid profile-id}) params {:user-agent user-agent :profile-id profile-id - :created-at now - :updated-at now + :created-at created-at + :updated-at created-at :id token}] - (db/insert! pool :http-session params) - token))) + (db/insert! pool :http-session params)))) + + (update-session [_ data] + (let [updated-at (dt/now)] + (px/with-dispatch executor + (db/update! pool :http-session + {:updated-at updated-at} + {:id (:id data)}) + (assoc data :updated-at updated-at)))) + (delete-session [_ token] (px/with-dispatch executor @@ -76,15 +85,23 @@ (p/do (let [profile-id (:profile-id data) user-agent (:user-agent data) + created-at (or (:created-at data) (dt/now)) token (tokens :generate {:iss "authentication" - :iat (dt/now) + :iat created-at :uid profile-id}) params {:user-agent user-agent + :created-at created-at + :updated-at created-at :profile-id profile-id :id token}] (swap! cache assoc token params) - token))) + params))) + + (update-session [_ data] + (let [updated-at (dt/now)] + (swap! cache update (:id data) assoc :updated-at updated-at) + (assoc data :updated-at updated-at))) (delete-session [_ token] (p/do @@ -107,77 +124,123 @@ ;; --- IMPL (defn- create-session! - [store request profile-id] - (let [params {:user-agent (yrq/get-header request "user-agent") + [store profile-id user-agent] + (let [params {:user-agent user-agent :profile-id profile-id}] (write-session store nil params))) +(defn- update-session! + [store session] + (update-session store session)) + (defn- delete-session! [store {:keys [cookies] :as request}] - (when-let [token (get-in cookies [token-cookie-name :value])] - (delete-session store token))) + (let [name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)] + (when-let [token (get-in cookies [name :value])] + (delete-session store token)))) (defn- retrieve-session [store request] - (when-let [cookie (yrq/get-cookie request token-cookie-name)] - (-> (read-session store (:value cookie)) - (p/then (fn [session] - (when session - {:session-id (:id session) - :profile-id (:profile-id session)})))))) + (let [cookie-name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)] + (when-let [cookie (yrq/get-cookie request cookie-name)] + (read-session store (:value cookie))))) -(defn- add-cookies - [response token] - (let [cors? (contains? cfg/flags :cors) - secure? (contains? cfg/flags :secure-session-cookies) - authenticated-cookie-domain (cfg/get :authenticated-cookie-domain)] - (update response :cookies - (fn [cookies] - (cond-> cookies - :always - (assoc token-cookie-name {:path "/" - :http-only true - :value token - :same-site (if cors? :none :lax) - :secure secure?}) +(defn assign-auth-token-cookie + [response {token :id updated-at :updated-at}] + (let [max-age (cf/get :auth-token-cookie-max-age default-cookie-max-age) + created-at (or updated-at (dt/now)) + renewal (dt/plus created-at default-renewal-max-age) + expires (dt/plus created-at max-age) + secure? (contains? cf/flags :secure-session-cookies) + cors? (contains? cf/flags :cors) + name (cf/get :auth-token-cookie-name default-auth-token-cookie-name) + comment (str "Renewal at: " (dt/format-instant renewal :rfc1123)) + cookie {:path "/" + :http-only true + :expires expires + :value token + :comment comment + :same-site (if cors? :none :lax) + :secure secure?}] + (update response :cookies assoc name cookie))) - (some? authenticated-cookie-domain) - (assoc authenticated-cookie-name {:domain authenticated-cookie-domain - :path "/" - :value true - :same-site :strict - :secure secure?})))))) +(defn assign-authenticated-cookie + [response {updated-at :updated-at}] + (let [max-age (cf/get :auth-token-cookie-max-age default-cookie-max-age) + created-at (or updated-at (dt/now)) + renewal (dt/plus created-at default-renewal-max-age) + expires (dt/plus created-at max-age) + comment (str "Renewal at: " (dt/format-instant renewal :rfc1123)) + secure? (contains? cf/flags :secure-session-cookies) + domain (cf/get :authenticated-cookie-domain) + name (cf/get :authenticated-cookie-name "authenticated") + cookie {:domain domain + :expires expires + :path "/" + :comment comment + :value true + :same-site :strict + :secure secure?}] + (cond-> response + (string? domain) + (update :cookies assoc name cookie)))) -(defn- clear-cookies +(defn clear-auth-token-cookie [response] - (let [authenticated-cookie-domain (cfg/get :authenticated-cookie-domain)] - (assoc response :cookies - {token-cookie-name {:path "/" - :value "" - :max-age -1} - authenticated-cookie-name {:domain authenticated-cookie-domain - :path "/" - :value "" - :max-age -1}}))) + (let [name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)] + (update response :cookies assoc name {:path "/" :value "" :max-age -1}))) + +(defn- clear-authenticated-cookie + [response] + (let [name (cf/get :authenticated-cookie-name default-authenticated-cookie-name) + domain (cf/get :authenticated-cookie-domain)] + (cond-> response + (string? domain) + (update :cookies assoc name {:domain domain :path "/" :value "" :max-age -1})))) (defn- make-middleware - [{:keys [::events-ch store] :as cfg}] - {:name :session - :compile (fn [& _] - (fn [handler] - (fn [request respond raise] - (try - (-> (retrieve-session store request) - (p/then' #(merge request %)) - (p/finally (fn [request cause] - (if cause - (raise cause) - (do - (when-let [session-id (:session-id request)] - (a/offer! events-ch session-id)) - (handler request respond raise)))))) - (catch Throwable cause - (raise cause))))))}) + [{:keys [store] :as cfg}] + (letfn [;; Check if time reached for automatic session renewal + (renew-session? [{:keys [updated-at] :as session}] + (and (dt/instant? updated-at) + (let [elapsed (dt/diff updated-at (dt/now))] + (neg? (compare default-renewal-max-age elapsed))))) + + ;; Wrap respond with session renewal code + (wrap-respond [respond session] + (fn [response] + (p/let [session (update-session! store session)] + (-> response + (assign-auth-token-cookie session) + (assign-authenticated-cookie session) + (respond)))))] + + {:name :session + :compile (fn [& _] + (fn [handler] + (fn [request respond raise] + (try + (-> (retrieve-session store request) + (p/finally (fn [session cause] + (cond + (some? cause) + (raise cause) + + (nil? session) + (handler request respond raise) + + :else + (let [request (-> request + (assoc :profile-id (:profile-id session)) + (assoc :session-id (:id session))) + respond (cond-> respond + (renew-session? session) + (wrap-respond session))] + + (handler request respond raise)))))) + + (catch Throwable cause + (raise cause))))))})) ;; --- STATE INIT: SESSION @@ -194,77 +257,23 @@ (defmethod ig/init-key :app.http/session [_ {:keys [store] :as cfg}] - (let [events-ch (a/chan (a/dropping-buffer (:buffer-size cfg))) - cfg (assoc cfg ::events-ch events-ch)] - - (-> cfg - (assoc :middleware (make-middleware cfg)) - (assoc :create (fn [profile-id] - (fn [request response] - (p/let [token (create-session! store request profile-id)] - (add-cookies response token))))) - (assoc :delete (fn [request response] - (p/do - (delete-session! store request) + (-> cfg + (assoc :middleware (make-middleware cfg)) + (assoc :create (fn [profile-id] + (fn [request response] + (p/let [uagent (yrq/get-header request "user-agent") + session (create-session! store profile-id uagent)] (-> response - (assoc :status 204) - (assoc :body nil) - (clear-cookies)))))))) - -(defmethod ig/halt-key! :app.http/session - [_ data] - (a/close! (::events-ch data))) - -;; --- STATE INIT: SESSION UPDATER - -(declare update-sessions) - -(s/def ::session map?) -(s/def ::max-batch-age ::cfg/http-session-updater-batch-max-age) -(s/def ::max-batch-size ::cfg/http-session-updater-batch-max-size) - -(defmethod ig/pre-init-spec ::updater [_] - (s/keys :req-un [::db/pool ::wrk/executor ::mtx/metrics ::session] - :opt-un [::max-batch-age ::max-batch-size])) - -(defmethod ig/prep-key ::updater - [_ cfg] - (merge {:max-batch-age (dt/duration {:minutes 5}) - :max-batch-size 200} - (d/without-nils cfg))) - -(defmethod ig/init-key ::updater - [_ {:keys [session metrics] :as cfg}] - (l/info :action "initialize session updater" - :max-batch-age (str (:max-batch-age cfg)) - :max-batch-size (str (:max-batch-size cfg))) - (let [input (aa/batch (::events-ch session) - {:max-batch-size (:max-batch-size cfg) - :max-batch-age (inst-ms (:max-batch-age cfg))})] - (a/go-loop [] - (when-let [[reason batch] (a/ response + (assoc :status 204) + (assoc :body nil) + (clear-auth-token-cookie) + (clear-authenticated-cookie))))))) ;; --- STATE INIT: SESSION GC @@ -278,7 +287,7 @@ (defmethod ig/prep-key ::gc-task [_ cfg] - (merge {:max-age (dt/duration {:days 15})} + (merge {:max-age default-cookie-max-age} (d/without-nils cfg))) (defmethod ig/init-key ::gc-task diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index cc20cc7e5..ad0fb139f 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -98,15 +98,7 @@ :app.http.session/gc-task {:pool (ig/ref :app.db/pool) - :max-age (cf/get :http-session-idle-max-age)} - - :app.http.session/updater - {:pool (ig/ref :app.db/pool) - :metrics (ig/ref :app.metrics/metrics) - :executor (ig/ref [::worker :app.worker/executor]) - :session (ig/ref :app.http/session) - :max-batch-age (cf/get :http-session-updater-batch-max-age) - :max-batch-size (cf/get :http-session-updater-batch-max-size)} + :max-age (cf/get :auth-token-cookie-max-age)} :app.http.awsns/handler {:tokens (ig/ref :app.tokens/tokens) From 61f2799e496beaf31aa74253c98f095b0e45d76d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 8 Aug 2022 09:00:00 +0200 Subject: [PATCH 04/11] :bug: Fix unexpected response truncation on viewer --- backend/src/app/rpc/queries/share_link.clj | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/app/rpc/queries/share_link.clj b/backend/src/app/rpc/queries/share_link.clj index 96ec658c8..6e9e00a79 100644 --- a/backend/src/app/rpc/queries/share_link.clj +++ b/backend/src/app/rpc/queries/share_link.clj @@ -11,6 +11,7 @@ (defn decode-share-link-row [row] (-> row + (dissoc :flags) (update :pages db/decode-pgarray #{}))) (defn retrieve-share-link From 173f0d68bb286f01455f622f3df65717c62522b0 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 8 Aug 2022 09:42:45 +0200 Subject: [PATCH 05/11] :paperclip: Properly deprecate comments related queries --- backend/src/app/rpc/commands/comments.clj | 27 ++++++++----- backend/src/app/rpc/queries/comments.clj | 49 ++++++----------------- backend/src/app/rpc/queries/viewer.clj | 2 +- 3 files changed, 31 insertions(+), 47 deletions(-) diff --git a/backend/src/app/rpc/commands/comments.clj b/backend/src/app/rpc/commands/comments.clj index 0a3ef1941..0ccd222a8 100644 --- a/backend/src/app/rpc/commands/comments.clj +++ b/backend/src/app/rpc/commands/comments.clj @@ -130,9 +130,16 @@ (-> (db/exec-one! conn [sql profile-id file-id id]) (decode-row))))) -;; --- COMMAND: Comments +(defn get-comment-thread + [conn {:keys [profile-id file-id id share-id] :as params}] + (let [sql (str "with threads as (" sql:comment-threads ")" + "select * from threads where id = ?")] + (-> (db/exec-one! conn [sql profile-id file-id id]) + (decode-row)))) -(declare retrieve-comments) +;; --- COMMAND: Retrieve Comments + +(declare get-comments) (s/def ::file-id ::us/uuid) (s/def ::share-id (s/nilable ::us/uuid)) @@ -145,22 +152,24 @@ [{:keys [pool] :as cfg} {:keys [profile-id thread-id share-id] :as params}] (with-open [conn (db/open pool)] (let [thread (db/get-by-id conn :comment-thread thread-id)] - (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) - (retrieve-comments conn thread-id)))) + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id)) + (get-comments conn thread-id))) (def sql:comments "select c.* from comment as c where c.thread_id = ? order by c.created_at asc") -(defn retrieve-comments +(defn get-comments [conn thread-id] - (->> (db/exec! conn [sql:comments thread-id]) + (->> (db/query conn :comment + {:thread-id thread-id} + {:order-by [[:created-at :asc]]}) (into [] (map decode-row)))) ;; --- COMMAND: Get file comments users -(declare retrieve-file-comments-users) +(declare get-file-comments-users) (s/def ::file-id ::us/uuid) (s/def ::share-id (s/nilable ::us/uuid)) @@ -177,7 +186,7 @@ [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id]}] (with-open [conn (db/open pool)] (files/check-comment-permissions! conn profile-id file-id share-id) - (retrieve-file-comments-users conn file-id profile-id))) + (get-file-comments-users conn file-id profile-id))) ;; All the profiles that had comment the file, plus the current ;; profile. @@ -197,6 +206,6 @@ FROM profile AS p WHERE p.id IN (SELECT id FROM available_profiles) OR p.id=?") -(defn retrieve-file-comments-users +(defn get-file-comments-users [conn file-id profile-id] (db/exec! conn [sql:file-comment-users file-id profile-id])) diff --git a/backend/src/app/rpc/queries/comments.clj b/backend/src/app/rpc/queries/comments.clj index aa57d9e8d..4a05a3924 100644 --- a/backend/src/app/rpc/queries/comments.clj +++ b/backend/src/app/rpc/queries/comments.clj @@ -23,27 +23,18 @@ ;; --- QUERY: Comment Threads -(s/def ::team-id ::us/uuid) -(s/def ::file-id ::us/uuid) -(s/def ::share-id (s/nilable ::us/uuid)) - -(s/def ::comment-threads - (s/and (s/keys :req-un [::profile-id] - :opt-un [::file-id ::share-id ::team-id]) - #(or (:file-id %) (:team-id %)))) +(s/def ::comment-threads ::cmd.comments/get-comment-threads) (sv/defmethod ::comment-threads - {::doc/deprecated "1.15"} + {::doc/added "1.0" + ::doc/deprecated "1.15"} [{:keys [pool] :as cfg} params] (with-open [conn (db/open pool)] (cmd.comments/retrieve-comment-threads conn params))) - ;; --- QUERY: Unread Comment Threads -(s/def ::team-id ::us/uuid) -(s/def ::unread-comment-threads - (s/keys :req-un [::profile-id ::team-id])) +(s/def ::unread-comment-threads ::cmd.comments/get-unread-comment-threads) (sv/defmethod ::unread-comment-threads {::doc/added "1.0" @@ -55,11 +46,7 @@ ;; --- QUERY: Single Comment Thread -(s/def ::id ::us/uuid) -(s/def ::share-id (s/nilable ::us/uuid)) -(s/def ::comment-thread - (s/keys :req-un [::profile-id ::file-id ::id] - :opt-un [::share-id])) +(s/def ::comment-thread ::cmd.comments/get-comment-thread) (sv/defmethod ::comment-thread {::doc/added "1.0" @@ -67,19 +54,11 @@ [{:keys [pool] :as cfg} {:keys [profile-id file-id id share-id] :as params}] (with-open [conn (db/open pool)] (files/check-comment-permissions! conn profile-id file-id share-id) - (let [sql (str "with threads as (" cmd.comments/sql:comment-threads ")" - "select * from threads where id = ?")] - (-> (db/exec-one! conn [sql profile-id file-id id]) - (decode-row))))) + (cmd.comments/get-comment-thread conn params))) ;; --- QUERY: Comments -(s/def ::file-id ::us/uuid) -(s/def ::share-id (s/nilable ::us/uuid)) -(s/def ::thread-id ::us/uuid) -(s/def ::comments - (s/keys :req-un [::profile-id ::thread-id] - :opt-un [::share-id])) +(s/def ::comments ::cmd.comments/get-comments) (sv/defmethod ::comments {::doc/added "1.0" @@ -87,17 +66,13 @@ [{:keys [pool] :as cfg} {:keys [profile-id thread-id share-id] :as params}] (with-open [conn (db/open pool)] (let [thread (db/get-by-id conn :comment-thread thread-id)] - (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) - (cmd.comments/retrieve-comments conn thread-id)))) + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id)) + (cmd.comments/get-comments conn thread-id))) + ;; --- QUERY: Get file comments users -(s/def ::file-id ::us/uuid) -(s/def ::share-id (s/nilable ::us/uuid)) - -(s/def ::file-comments-users - (s/keys :req-un [::profile-id ::file-id] - :opt-un [::share-id])) +(s/def ::file-comments-users ::cmd.comments/get-profiles-for-file-comments) (sv/defmethod ::file-comments-users {::doc/deprecated "1.15" @@ -105,4 +80,4 @@ [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id]}] (with-open [conn (db/open pool)] (files/check-comment-permissions! conn profile-id file-id share-id) - (cmd.comments/retrieve-file-comments-users conn file-id profile-id))) + (cmd.comments/get-file-comments-users conn file-id profile-id))) diff --git a/backend/src/app/rpc/queries/viewer.clj b/backend/src/app/rpc/queries/viewer.clj index 681b8ef47..90234e4e2 100644 --- a/backend/src/app/rpc/queries/viewer.clj +++ b/backend/src/app/rpc/queries/viewer.clj @@ -27,7 +27,7 @@ (p/let [file (files/retrieve-file cfg file-id) project (retrieve-project pool (:project-id file)) libs (files/retrieve-file-libraries cfg false file-id) - users (comments/retrieve-file-comments-users pool file-id profile-id) + users (comments/get-file-comments-users pool file-id profile-id) links (->> (db/query pool :share-link {:file-id file-id}) (mapv slnk/decode-share-link-row)) From 0b3d25a890ad050e4c64a49da4b9e7f2b47b3568 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 8 Aug 2022 09:51:11 +0200 Subject: [PATCH 06/11] :sparkles: Make frontend use new cmd based repo methods for comments queries --- frontend/src/app/main/data/comments.cljs | 32 +++++++++---------- frontend/src/app/main/data/users.cljs | 4 +-- frontend/src/app/main/data/viewer.cljs | 8 ++--- .../app/main/data/workspace/persistence.cljs | 10 +++--- frontend/src/app/main/repo.cljs | 4 +++ 5 files changed, 31 insertions(+), 27 deletions(-) diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs index cf9922b3d..b1b910a6f 100644 --- a/frontend/src/app/main/data/comments.cljs +++ b/frontend/src/app/main/data/comments.cljs @@ -93,8 +93,8 @@ objects (wsh/lookup-page-objects state page-id) frame-id (cph/frame-id-by-position objects (:position params)) params (assoc params :frame-id frame-id)] - (->> (rp/mutation :create-comment-thread params) - (rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %)})) + (->> (rp/mutation! :create-comment-thread params) + (rx/mapcat #(rp/cmd! :get-comment-thread {:file-id (:file-id %) :id (:id %)})) (rx/map created-thread-on-workspace) (rx/catch #(rx/throw {:type :comment-error}))))))) @@ -122,8 +122,8 @@ (let [share-id (-> state :viewer-local :share-id) frame-id (:frame-id params) params (assoc params :share-id share-id :frame-id frame-id)] - (->> (rp/mutation :create-comment-thread params) - (rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %) :share-id share-id})) + (->> (rp/mutation! :create-comment-thread params) + (rx/mapcat #(rp/cmd! :get-comment-thread {:file-id (:file-id %) :id (:id %) :share-id share-id})) (rx/map created-thread-on-viewer) (rx/catch #(rx/throw {:type :comment-error}))))))) @@ -135,7 +135,7 @@ (watch [_ state _] (let [done #(d/update-in-when % [:comment-threads id] assoc :count-unread-comments 0) share-id (-> state :viewer-local :share-id)] - (->> (rp/mutation :update-comment-thread-status {:id id :share-id share-id}) + (->> (rp/mutation! :update-comment-thread-status {:id id :share-id share-id}) (rx/map (constantly done)) (rx/catch #(rx/throw {:type :comment-error}))))))) @@ -153,7 +153,7 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rp/mutation :update-comment-thread {:id id :is-resolved is-resolved :share-id share-id}) + (->> (rp/mutation! :update-comment-thread {:id id :is-resolved is-resolved :share-id share-id}) (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore)))))) @@ -168,7 +168,7 @@ (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] (rx/concat - (->> (rp/mutation :add-comment {:thread-id (:id thread) :content content :share-id share-id}) + (->> (rp/mutation! :add-comment {:thread-id (:id thread) :content content :share-id share-id}) (rx/map #(partial created %)) (rx/catch #(rx/throw {:type :comment-error}))) (rx/of (refresh-comment-thread thread)))))))) @@ -184,7 +184,7 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rp/mutation :update-comment {:id id :content content :share-id share-id}) + (->> (rp/mutation! :update-comment {:id id :content content :share-id share-id}) (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore)))))) @@ -202,7 +202,7 @@ ptk/WatchEvent (watch [_ _ _] - (->> (rp/mutation :delete-comment-thread {:id id}) + (->> (rp/mutation! :delete-comment-thread {:id id}) (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore))))) @@ -221,7 +221,7 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rp/mutation :delete-comment-thread {:id id :share-id share-id}) + (->> (rp/mutation! :delete-comment-thread {:id id :share-id share-id}) (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore)))))) @@ -236,7 +236,7 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rp/mutation :delete-comment {:id id :share-id share-id}) + (->> (rp/mutation! :delete-comment {:id id :share-id share-id}) (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore)))))) @@ -249,7 +249,7 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rp/query :comment-thread {:file-id file-id :id id :share-id share-id}) + (->> (rp/cmd! :get-comment-thread {:file-id file-id :id id :share-id share-id}) (rx/map #(partial fetched %)) (rx/catch #(rx/throw {:type :comment-error})))))))) @@ -272,7 +272,7 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rp/query :comment-threads {:file-id file-id :share-id share-id}) + (->> (rp/cmd! :get-comment-threads {:file-id file-id :share-id share-id}) (rx/map #(partial fetched %)) (rx/catch #(rx/throw {:type :comment-error})))))))) @@ -285,7 +285,7 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rp/query :comments {:thread-id thread-id :share-id share-id}) + (->> (rp/cmd! :get-comments {:thread-id thread-id :share-id share-id}) (rx/map #(partial fetched %)) (rx/catch #(rx/throw {:type :comment-error})))))))) @@ -297,7 +297,7 @@ ptk/WatchEvent (watch [_ _ _] (let [fetched #(assoc %2 :comment-threads (d/index-by :id %1))] - (->> (rp/query :unread-comment-threads {:team-id team-id}) + (->> (rp/cmd! :get-unread-comment-threads {:team-id team-id}) (rx/map #(partial fetched %)) (rx/catch #(rx/throw {:type :comment-error}))))))) @@ -426,7 +426,7 @@ ptk/WatchEvent (watch [_ _ _] (let [thread-id (:id thread)] - (->> (rp/mutation :update-comment-thread-frame {:id thread-id :frame-id frame-id}) + (->> (rp/mutation! :update-comment-thread-frame {:id thread-id :frame-id frame-id}) (rx/catch #(rx/throw {:type :comment-error :code :update-comment-thread-frame})) (rx/ignore))))))) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index 104b2619e..a0c6bfdea 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -445,7 +445,7 @@ (ptk/reify ::fetch-team-users ptk/WatchEvent (watch [_ _ _] - (->> (rp/query :team-users {:team-id team-id}) + (->> (rp/query! :team-users {:team-id team-id}) (rx/map #(partial fetched %))))))) (defn fetch-file-comments-users @@ -459,7 +459,7 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rp/query :file-comments-users {:team-id team-id :share-id share-id}) + (->> (rp/command! :get-profiles-for-file-comments {:team-id team-id :share-id share-id}) (rx/map #(partial fetched %)))))))) ;; --- EVENT: request-account-deletion diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index 54c042222..d920b7685 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -102,7 +102,7 @@ (watch [_ _ _] (let [params' (cond-> {:file-id file-id} (uuid? share-id) (assoc :share-id share-id))] - (->> (rp/query :view-only-bundle params') + (->> (rp/query! :view-only-bundle params') (rx/mapcat (fn [{:keys [fonts] :as bundle}] (rx/of (df/fonts-fetched fonts) @@ -156,7 +156,7 @@ (ptk/reify ::fetch-comment-threads ptk/WatchEvent (watch [_ _ _] - (->> (rp/query :comment-threads {:file-id file-id :share-id share-id}) + (->> (rp/cmd! :get-comment-threads {:file-id file-id :share-id share-id}) (rx/map #(partial fetched %)) (rx/catch on-error)))))) @@ -167,7 +167,7 @@ (ptk/reify ::refresh-comment-thread ptk/WatchEvent (watch [_ _ _] - (->> (rp/query :comment-thread {:file-id file-id :id id}) + (->> (rp/cmd! :get-comment-thread {:file-id file-id :id id}) (rx/map #(partial fetched %))))))) (defn fetch-comments @@ -178,7 +178,7 @@ (ptk/reify ::retrieve-comments ptk/WatchEvent (watch [_ _ _] - (->> (rp/query :comments {:thread-id thread-id}) + (->> (rp/cmd! :get-comments {:thread-id thread-id}) (rx/map #(partial fetched %))))))) ;; --- Zoom Management diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 445f5001c..e88d0c750 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -262,11 +262,11 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rx/zip (rp/query :file-raw {:id file-id}) - (rp/query :team-users {:file-id file-id}) - (rp/query :project {:id project-id}) - (rp/query :file-libraries {:file-id file-id}) - (rp/query :file-comments-users {:file-id file-id :share-id share-id})) + (->> (rx/zip (rp/query! :file-raw {:id file-id}) + (rp/query! :team-users {:file-id file-id}) + (rp/query! :project {:id project-id}) + (rp/query! :file-libraries {:file-id file-id}) + (rp/cmd! :get-profiles-for-file-comments {:file-id file-id :share-id share-id})) (rx/take 1) (rx/map (fn [[file-raw users project libraries file-comments-users]] {:file-raw file-raw diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index 3028d7c2d..a5e0b115a 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -127,6 +127,10 @@ ([id] (command id {})) ([id params] (command id params))) +(defn cmd! + ([id] (command id {})) + ([id params] (command id params))) + (defmethod command :login-with-oidc [_ {:keys [provider] :as params}] (let [uri (u/join base-uri "api/auth/oauth/" (d/name provider)) From aceefc0485e52f24584a35efbbfb13b931d61929 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 8 Aug 2022 10:36:15 +0200 Subject: [PATCH 07/11] :recycle: Move comments mutations to commands --- backend/src/app/rpc/commands/comments.clj | 325 +++++++++++++++++- backend/src/app/rpc/mutations/comments.clj | 233 +------------ backend/src/app/rpc/queries/comments.clj | 3 +- frontend/src/app/main/data/comments.cljs | 20 +- .../src/app/main/data/workspace/comments.cljs | 2 +- 5 files changed, 353 insertions(+), 230 deletions(-) diff --git a/backend/src/app/rpc/commands/comments.clj b/backend/src/app/rpc/commands/comments.clj index 0ccd222a8..f7bf9e521 100644 --- a/backend/src/app/rpc/commands/comments.clj +++ b/backend/src/app/rpc/commands/comments.clj @@ -6,14 +6,23 @@ (ns app.rpc.commands.comments (:require + [app.common.exceptions :as ex] + [app.common.geom.point :as gpt] [app.common.spec :as us] [app.db :as db] [app.rpc.doc :as-alias doc] [app.rpc.queries.files :as files] [app.rpc.queries.teams :as teams] + [app.rpc.retry :as retry] + [app.util.blob :as blob] [app.util.services :as sv] + [app.util.time :as dt] [clojure.spec.alpha :as s])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; QUERY COMMANDS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn decode-row [{:keys [participants position] :as row}] (cond-> row @@ -131,7 +140,7 @@ (decode-row))))) (defn get-comment-thread - [conn {:keys [profile-id file-id id share-id] :as params}] + [conn {:keys [profile-id file-id id] :as params}] (let [sql (str "with threads as (" sql:comment-threads ")" "select * from threads where id = ?")] (-> (db/exec-one! conn [sql profile-id file-id id]) @@ -209,3 +218,317 @@ (defn get-file-comments-users [conn file-id profile-id] (db/exec! conn [sql:file-comment-users file-id profile-id])) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; MUTATION COMMANDS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; --- COMMAND: Create Comment Thread + +(declare upsert-comment-thread-status!) +(declare create-comment-thread) +(declare retrieve-page-name) + +(s/def ::page-id ::us/uuid) +(s/def ::file-id ::us/uuid) +(s/def ::share-id (s/nilable ::us/uuid)) +(s/def ::profile-id ::us/uuid) +(s/def ::position ::gpt/point) +(s/def ::content ::us/string) +(s/def ::frame-id ::us/uuid) + +(s/def ::create-comment-thread + (s/keys :req-un [::profile-id ::file-id ::position ::content ::page-id ::frame-id] + :opt-un [::share-id])) + +(sv/defmethod ::create-comment-thread + {::retry/max-retries 3 + ::retry/matches retry/conflict-db-insert? + ::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] + (db/with-atomic [conn pool] + (files/check-comment-permissions! conn profile-id file-id share-id) + (create-comment-thread conn params))) + +(defn- retrieve-next-seqn + [conn file-id] + (let [sql "select (f.comment_thread_seqn + 1) as next_seqn from file as f where f.id = ?" + res (db/exec-one! conn [sql file-id])] + (:next-seqn res))) + +(defn create-comment-thread + [conn {:keys [profile-id file-id page-id position content frame-id] :as params}] + (let [seqn (retrieve-next-seqn conn file-id) + now (dt/now) + pname (retrieve-page-name conn params) + thread (db/insert! conn :comment-thread + {:file-id file-id + :owner-id profile-id + :participants (db/tjson #{profile-id}) + :page-name pname + :page-id page-id + :created-at now + :modified-at now + :seqn seqn + :position (db/pgpoint position) + :frame-id frame-id})] + + + ;; Create a comment entry + (db/insert! conn :comment + {:thread-id (:id thread) + :owner-id profile-id + :created-at now + :modified-at now + :content content}) + + ;; Make the current thread as read. + (upsert-comment-thread-status! conn profile-id (:id thread)) + + ;; Optimistic update of current seq number on file. + (db/update! conn :file + {:comment-thread-seqn seqn} + {:id file-id}) + + (select-keys thread [:id :file-id :page-id]))) + +(defn- retrieve-page-name + [conn {:keys [file-id page-id]}] + (let [{:keys [data]} (db/get-by-id conn :file file-id) + data (blob/decode data)] + (get-in data [:pages-index page-id :name]))) + + +;; --- COMMAND: Update Comment Thread Status + +(s/def ::id ::us/uuid) +(s/def ::share-id (s/nilable ::us/uuid)) + +(s/def ::update-comment-thread-status + (s/keys :req-un [::profile-id ::id] + :opt-un [::share-id])) + +(sv/defmethod ::update-comment-thread-status + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id share-id] :as params}] + (db/with-atomic [conn pool] + (let [cthr (db/get-by-id conn :comment-thread id {:for-update true})] + (when-not cthr + (ex/raise :type :not-found)) + + (files/check-comment-permissions! conn profile-id (:file-id cthr) share-id) + (upsert-comment-thread-status! conn profile-id (:id cthr))))) + +(def sql:upsert-comment-thread-status + "insert into comment_thread_status (thread_id, profile_id) + values (?, ?) + on conflict (thread_id, profile_id) + do update set modified_at = clock_timestamp() + returning modified_at;") + +(defn upsert-comment-thread-status! + [conn profile-id thread-id] + (db/exec-one! conn [sql:upsert-comment-thread-status thread-id profile-id])) + + +;; --- COMMAND: Update Comment Thread + +(s/def ::is-resolved ::us/boolean) +(s/def ::update-comment-thread + (s/keys :req-un [::profile-id ::id ::is-resolved] + :opt-un [::share-id])) + +(sv/defmethod ::update-comment-thread + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id is-resolved share-id] :as params}] + (db/with-atomic [conn pool] + (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] + (when-not thread + (ex/raise :type :not-found)) + + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) + + (db/update! conn :comment-thread + {:is-resolved is-resolved} + {:id id}) + nil))) + + +;; --- COMMAND: Add Comment + +(declare create-comment) + +(s/def ::create-comment + (s/keys :req-un [::profile-id ::thread-id ::content] + :opt-un [::share-id])) + +(sv/defmethod ::create-comment + {::doc/added "1.15"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (create-comment conn params))) + +(defn create-comment + [conn {:keys [profile-id thread-id content share-id] :as params}] + (let [thread (-> (db/get-by-id conn :comment-thread thread-id {:for-update true}) + (decode-row)) + pname (retrieve-page-name conn thread)] + + ;; Standard Checks + (when-not thread (ex/raise :type :not-found)) + + ;; Permission Checks + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) + + ;; Update the page-name cachedattribute on comment thread table. + (when (not= pname (:page-name thread)) + (db/update! conn :comment-thread + {:page-name pname} + {:id thread-id})) + + ;; NOTE: is important that all timestamptz related fields are + ;; created or updated on the database level for avoid clock + ;; inconsistencies (some user sees something read that is not + ;; read, etc...) + (let [ppants (:participants thread #{}) + comment (db/insert! conn :comment + {:thread-id thread-id + :owner-id profile-id + :content content})] + + ;; NOTE: this is done in SQL instead of using db/update! + ;; helper because currently the helper does not allow pass raw + ;; function call parameters to the underlying prepared + ;; statement; in a future when we fix/improve it, this can be + ;; changed to use the helper. + + ;; Update thread modified-at attribute and assoc the current + ;; profile to the participant set. + (let [ppants (conj ppants profile-id) + sql "update comment_thread + set modified_at = clock_timestamp(), + participants = ? + where id = ?"] + (db/exec-one! conn [sql (db/tjson ppants) thread-id])) + + ;; Update the current profile status in relation to the + ;; current thread. + (upsert-comment-thread-status! conn profile-id thread-id) + + ;; Return the created comment object. + comment))) + +;; --- COMMAND: Update Comment + +(declare update-comment) + +(s/def ::update-comment + (s/keys :req-un [::profile-id ::id ::content] + :opt-un [::share-id])) + +(sv/defmethod ::update-comment + {::doc/added "1.15"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (update-comment conn params))) + +(defn update-comment + [conn {:keys [profile-id id content share-id] :as params}] + (let [comment (db/get-by-id conn :comment id {:for-update true}) + _ (when-not comment (ex/raise :type :not-found)) + thread (db/get-by-id conn :comment-thread (:thread-id comment) {:for-update true}) + _ (when-not thread (ex/raise :type :not-found)) + pname (retrieve-page-name conn thread)] + + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) + + ;; Don't allow edit comments to not owners + (when-not (= (:owner-id thread) profile-id) + (ex/raise :type :validation + :code :not-allowed)) + + (db/update! conn :comment + {:content content + :modified-at (dt/now)} + {:id (:id comment)}) + + (db/update! conn :comment-thread + {:modified-at (dt/now) + :page-name pname} + {:id (:id thread)}) + nil)) + + +;; --- COMMAND: Delete Comment Thread + +(s/def ::delete-comment-thread + (s/keys :req-un [::profile-id ::id])) + +(sv/defmethod ::delete-comment-thread + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] + (db/with-atomic [conn pool] + (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] + (when-not (= (:owner-id thread) profile-id) + (ex/raise :type :validation + :code :not-allowed)) + (db/delete! conn :comment-thread {:id id}) + nil))) + + +;; --- COMMAND: Delete comment + +(s/def ::delete-comment + (s/keys :req-un [::profile-id ::id])) + +(sv/defmethod ::delete-comment + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] + (db/with-atomic [conn pool] + (let [comment (db/get-by-id conn :comment id {:for-update true})] + (when-not (= (:owner-id comment) profile-id) + (ex/raise :type :validation + :code :not-allowed)) + + (db/delete! conn :comment {:id id})))) + +;; --- COMMAND: Update comment thread position + +(s/def ::update-comment-thread-position + (s/keys :req-un [::profile-id ::id ::position ::frame-id])) + +(sv/defmethod ::update-comment-thread-position + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id position frame-id] :as params}] + (db/with-atomic [conn pool] + (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] + (when-not (= (:owner-id thread) profile-id) + (ex/raise :type :validation + :code :not-allowed)) + (db/update! conn :comment-thread + {:modified-at (dt/now) + :position (db/pgpoint position) + :frame-id frame-id} + {:id (:id thread)}) + nil))) + +;; --- COMMAND: Update comment frame + +(s/def ::update-comment-thread-frame + (s/keys :req-un [::profile-id ::id ::frame-id])) + +(sv/defmethod ::update-comment-thread-frame + {::doc/added "1.15"} + [{:keys [pool] :as cfg} {:keys [profile-id id frame-id] :as params}] + (db/with-atomic [conn pool] + (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] + (when-not (= (:owner-id thread) profile-id) + (ex/raise :type :validation + :code :not-allowed)) + (db/update! conn :comment-thread + {:modified-at (dt/now) + :frame-id frame-id} + {:id (:id thread)}) + nil))) + diff --git a/backend/src/app/rpc/mutations/comments.clj b/backend/src/app/rpc/mutations/comments.clj index feda6566b..2d138886b 100644 --- a/backend/src/app/rpc/mutations/comments.clj +++ b/backend/src/app/rpc/mutations/comments.clj @@ -7,35 +7,18 @@ (ns app.rpc.mutations.comments (:require [app.common.exceptions :as ex] - [app.common.geom.point :as gpt] [app.common.spec :as us] [app.db :as db] + [app.rpc.commands.comments :as cmd.comments] [app.rpc.doc :as-alias doc] - [app.rpc.queries.comments :as comments] [app.rpc.queries.files :as files] [app.rpc.retry :as retry] - [app.util.blob :as blob] [app.util.services :as sv] - [app.util.time :as dt] [clojure.spec.alpha :as s])) ;; --- Mutation: Create Comment Thread -(declare upsert-comment-thread-status!) -(declare create-comment-thread) -(declare retrieve-page-name) - -(s/def ::page-id ::us/uuid) -(s/def ::file-id ::us/uuid) -(s/def ::share-id (s/nilable ::us/uuid)) -(s/def ::profile-id ::us/uuid) -(s/def ::position ::gpt/point) -(s/def ::content ::us/string) -(s/def ::frame-id ::us/uuid) - -(s/def ::create-comment-thread - (s/keys :req-un [::profile-id ::file-id ::position ::content ::page-id ::frame-id] - :opt-un [::share-id])) +(s/def ::create-comment-thread ::cmd.comments/create-comment-thread) (sv/defmethod ::create-comment-thread {::retry/max-retries 3 @@ -45,65 +28,14 @@ [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] (db/with-atomic [conn pool] (files/check-comment-permissions! conn profile-id file-id share-id) - (create-comment-thread conn params))) - -(defn- retrieve-next-seqn - [conn file-id] - (let [sql "select (f.comment_thread_seqn + 1) as next_seqn from file as f where f.id = ?" - res (db/exec-one! conn [sql file-id])] - (:next-seqn res))) - -(defn- create-comment-thread - [conn {:keys [profile-id file-id page-id position content frame-id] :as params}] - (let [seqn (retrieve-next-seqn conn file-id) - now (dt/now) - pname (retrieve-page-name conn params) - thread (db/insert! conn :comment-thread - {:file-id file-id - :owner-id profile-id - :participants (db/tjson #{profile-id}) - :page-name pname - :page-id page-id - :created-at now - :modified-at now - :seqn seqn - :position (db/pgpoint position) - :frame-id frame-id})] - - - ;; Create a comment entry - (db/insert! conn :comment - {:thread-id (:id thread) - :owner-id profile-id - :created-at now - :modified-at now - :content content}) - - ;; Make the current thread as read. - (upsert-comment-thread-status! conn profile-id (:id thread)) - - ;; Optimistic update of current seq number on file. - (db/update! conn :file - {:comment-thread-seqn seqn} - {:id file-id}) - - (select-keys thread [:id :file-id :page-id]))) - -(defn- retrieve-page-name - [conn {:keys [file-id page-id]}] - (let [{:keys [data]} (db/get-by-id conn :file file-id) - data (blob/decode data)] - (get-in data [:pages-index page-id :name]))) - + (cmd.comments/create-comment-thread conn params))) ;; --- Mutation: Update Comment Thread Status (s/def ::id ::us/uuid) (s/def ::share-id (s/nilable ::us/uuid)) -(s/def ::update-comment-thread-status - (s/keys :req-un [::profile-id ::id] - :opt-un [::share-id])) +(s/def ::update-comment-thread-status ::cmd.comments/update-comment-thread-status) (sv/defmethod ::update-comment-thread-status {::doc/added "1.0" @@ -111,30 +43,14 @@ [{:keys [pool] :as cfg} {:keys [profile-id id share-id] :as params}] (db/with-atomic [conn pool] (let [cthr (db/get-by-id conn :comment-thread id {:for-update true})] - (when-not cthr - (ex/raise :type :not-found)) - + (when-not cthr (ex/raise :type :not-found)) (files/check-comment-permissions! conn profile-id (:file-id cthr) share-id) - (upsert-comment-thread-status! conn profile-id (:id cthr))))) - -(def sql:upsert-comment-thread-status - "insert into comment_thread_status (thread_id, profile_id) - values (?, ?) - on conflict (thread_id, profile_id) - do update set modified_at = clock_timestamp() - returning modified_at;") - -(defn- upsert-comment-thread-status! - [conn profile-id thread-id] - (db/exec-one! conn [sql:upsert-comment-thread-status thread-id profile-id])) + (cmd.comments/upsert-comment-thread-status! conn profile-id (:id cthr))))) ;; --- Mutation: Update Comment Thread -(s/def ::is-resolved ::us/boolean) -(s/def ::update-comment-thread - (s/keys :req-un [::profile-id ::id ::is-resolved] - :opt-un [::share-id])) +(s/def ::update-comment-thread ::cmd.comments/update-comment-thread) (sv/defmethod ::update-comment-thread {::doc/added "1.0" @@ -146,7 +62,6 @@ (ex/raise :type :not-found)) (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) - (db/update! conn :comment-thread {:is-resolved is-resolved} {:id id}) @@ -155,104 +70,31 @@ ;; --- Mutation: Add Comment -(s/def ::add-comment - (s/keys :req-un [::profile-id ::thread-id ::content] - :opt-un [::share-id])) +(s/def ::add-comment ::cmd.comments/create-comment) (sv/defmethod ::add-comment {::doc/added "1.0" ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id thread-id content share-id] :as params}] + [{:keys [pool] :as cfg} params] (db/with-atomic [conn pool] - (let [thread (-> (db/get-by-id conn :comment-thread thread-id {:for-update true}) - (comments/decode-row)) - pname (retrieve-page-name conn thread)] - - ;; Standard Checks - (when-not thread (ex/raise :type :not-found)) - - ;; Permission Checks - (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) - - ;; Update the page-name cachedattribute on comment thread table. - (when (not= pname (:page-name thread)) - (db/update! conn :comment-thread - {:page-name pname} - {:id thread-id})) - - ;; NOTE: is important that all timestamptz related fields are - ;; created or updated on the database level for avoid clock - ;; inconsistencies (some user sees something read that is not - ;; read, etc...) - (let [ppants (:participants thread #{}) - comment (db/insert! conn :comment - {:thread-id thread-id - :owner-id profile-id - :content content})] - - ;; NOTE: this is done in SQL instead of using db/update! - ;; helper because currently the helper does not allow pass raw - ;; function call parameters to the underlying prepared - ;; statement; in a future when we fix/improve it, this can be - ;; changed to use the helper. - - ;; Update thread modified-at attribute and assoc the current - ;; profile to the participant set. - (let [ppants (conj ppants profile-id) - sql "update comment_thread - set modified_at = clock_timestamp(), - participants = ? - where id = ?"] - (db/exec-one! conn [sql (db/tjson ppants) thread-id])) - - ;; Update the current profile status in relation to the - ;; current thread. - (upsert-comment-thread-status! conn profile-id thread-id) - - ;; Return the created comment object. - comment)))) + (cmd.comments/create-comment conn params))) ;; --- Mutation: Update Comment -(s/def ::update-comment - (s/keys :req-un [::profile-id ::id ::content] - :opt-un [::share-id])) +(s/def ::update-comment ::cmd.comments/update-comment) (sv/defmethod ::update-comment {::doc/added "1.0" ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id id content share-id] :as params}] + [{:keys [pool] :as cfg} params] (db/with-atomic [conn pool] - (let [comment (db/get-by-id conn :comment id {:for-update true}) - _ (when-not comment (ex/raise :type :not-found)) - thread (db/get-by-id conn :comment-thread (:thread-id comment) {:for-update true}) - _ (when-not thread (ex/raise :type :not-found)) - pname (retrieve-page-name conn thread)] - - (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) - - ;; Don't allow edit comments to not owners - (when-not (= (:owner-id thread) profile-id) - (ex/raise :type :validation - :code :not-allowed)) - - (db/update! conn :comment - {:content content - :modified-at (dt/now)} - {:id (:id comment)}) - - (db/update! conn :comment-thread - {:modified-at (dt/now) - :page-name pname} - {:id (:id thread)}) - nil))) + (cmd.comments/update-comment conn params))) ;; --- Mutation: Delete Comment Thread -(s/def ::delete-comment-thread - (s/keys :req-un [::profile-id ::id])) +(s/def ::delete-comment-thread ::cmd.comments/delete-comment-thread) (sv/defmethod ::delete-comment-thread {::doc/added "1.0" @@ -261,16 +103,14 @@ (db/with-atomic [conn pool] (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] (when-not (= (:owner-id thread) profile-id) - (ex/raise :type :validation - :code :not-allowed)) + (ex/raise :type :validation :code :not-allowed)) (db/delete! conn :comment-thread {:id id}) nil))) ;; --- Mutation: Delete comment -(s/def ::delete-comment - (s/keys :req-un [::profile-id ::id])) +(s/def ::delete-comment ::cmd.comments/delete-comment) (sv/defmethod ::delete-comment {::doc/added "1.0" @@ -279,44 +119,5 @@ (db/with-atomic [conn pool] (let [comment (db/get-by-id conn :comment id {:for-update true})] (when-not (= (:owner-id comment) profile-id) - (ex/raise :type :validation - :code :not-allowed)) - + (ex/raise :type :validation :code :not-allowed)) (db/delete! conn :comment {:id id})))) - -;; --- Mutation: Update comment thread position - -(s/def ::update-comment-thread-position - (s/keys :req-un [::profile-id ::id ::position ::frame-id])) - -(sv/defmethod ::update-comment-thread-position - [{:keys [pool] :as cfg} {:keys [profile-id id position frame-id] :as params}] - (db/with-atomic [conn pool] - (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] - (when-not (= (:owner-id thread) profile-id) - (ex/raise :type :validation - :code :not-allowed)) - (db/update! conn :comment-thread - {:modified-at (dt/now) - :position (db/pgpoint position) - :frame-id frame-id} - {:id (:id thread)}) - nil))) - -;; --- Mutation: Update comment frame - -(s/def ::update-comment-thread-frame - (s/keys :req-un [::profile-id ::id ::frame-id])) - -(sv/defmethod ::update-comment-thread-frame - [{:keys [pool] :as cfg} {:keys [profile-id id frame-id] :as params}] - (db/with-atomic [conn pool] - (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] - (when-not (= (:owner-id thread) profile-id) - (ex/raise :type :validation - :code :not-allowed)) - (db/update! conn :comment-thread - {:modified-at (dt/now) - :frame-id frame-id} - {:id (:id thread)}) - nil))) diff --git a/backend/src/app/rpc/queries/comments.clj b/backend/src/app/rpc/queries/comments.clj index 4a05a3924..6c89f18ec 100644 --- a/backend/src/app/rpc/queries/comments.clj +++ b/backend/src/app/rpc/queries/comments.clj @@ -6,7 +6,6 @@ (ns app.rpc.queries.comments (:require - [app.common.spec :as us] [app.db :as db] [app.rpc.commands.comments :as cmd.comments] [app.rpc.doc :as-alias doc] @@ -51,7 +50,7 @@ (sv/defmethod ::comment-thread {::doc/added "1.0" ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id file-id id share-id] :as params}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] (with-open [conn (db/open pool)] (files/check-comment-permissions! conn profile-id file-id share-id) (cmd.comments/get-comment-thread conn params))) diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs index b1b910a6f..e09d8eff8 100644 --- a/frontend/src/app/main/data/comments.cljs +++ b/frontend/src/app/main/data/comments.cljs @@ -93,7 +93,7 @@ objects (wsh/lookup-page-objects state page-id) frame-id (cph/frame-id-by-position objects (:position params)) params (assoc params :frame-id frame-id)] - (->> (rp/mutation! :create-comment-thread params) + (->> (rp/cmd! :create-comment-thread params) (rx/mapcat #(rp/cmd! :get-comment-thread {:file-id (:file-id %) :id (:id %)})) (rx/map created-thread-on-workspace) (rx/catch #(rx/throw {:type :comment-error}))))))) @@ -122,7 +122,7 @@ (let [share-id (-> state :viewer-local :share-id) frame-id (:frame-id params) params (assoc params :share-id share-id :frame-id frame-id)] - (->> (rp/mutation! :create-comment-thread params) + (->> (rp/cmd! :create-comment-thread params) (rx/mapcat #(rp/cmd! :get-comment-thread {:file-id (:file-id %) :id (:id %) :share-id share-id})) (rx/map created-thread-on-viewer) (rx/catch #(rx/throw {:type :comment-error}))))))) @@ -135,7 +135,7 @@ (watch [_ state _] (let [done #(d/update-in-when % [:comment-threads id] assoc :count-unread-comments 0) share-id (-> state :viewer-local :share-id)] - (->> (rp/mutation! :update-comment-thread-status {:id id :share-id share-id}) + (->> (rp/cmd! :update-comment-thread-status {:id id :share-id share-id}) (rx/map (constantly done)) (rx/catch #(rx/throw {:type :comment-error}))))))) @@ -153,7 +153,7 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rp/mutation! :update-comment-thread {:id id :is-resolved is-resolved :share-id share-id}) + (->> (rp/cmd! :update-comment-thread {:id id :is-resolved is-resolved :share-id share-id}) (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore)))))) @@ -168,7 +168,7 @@ (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] (rx/concat - (->> (rp/mutation! :add-comment {:thread-id (:id thread) :content content :share-id share-id}) + (->> (rp/cmd! :create-comment {:thread-id (:id thread) :content content :share-id share-id}) (rx/map #(partial created %)) (rx/catch #(rx/throw {:type :comment-error}))) (rx/of (refresh-comment-thread thread)))))))) @@ -184,7 +184,7 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rp/mutation! :update-comment {:id id :content content :share-id share-id}) + (->> (rp/cmd! :update-comment {:id id :content content :share-id share-id}) (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore)))))) @@ -202,7 +202,7 @@ ptk/WatchEvent (watch [_ _ _] - (->> (rp/mutation! :delete-comment-thread {:id id}) + (->> (rp/cmd! :delete-comment-thread {:id id}) (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore))))) @@ -221,7 +221,7 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rp/mutation! :delete-comment-thread {:id id :share-id share-id}) + (->> (rp/cmd! :delete-comment-thread {:id id :share-id share-id}) (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore)))))) @@ -236,7 +236,7 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id)] - (->> (rp/mutation! :delete-comment {:id id :share-id share-id}) + (->> (rp/cmd! :delete-comment {:id id :share-id share-id}) (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore)))))) @@ -426,7 +426,7 @@ ptk/WatchEvent (watch [_ _ _] (let [thread-id (:id thread)] - (->> (rp/mutation! :update-comment-thread-frame {:id thread-id :frame-id frame-id}) + (->> (rp/cmd! :update-comment-thread-frame {:id thread-id :frame-id frame-id}) (rx/catch #(rx/throw {:type :comment-error :code :update-comment-thread-frame})) (rx/ignore))))))) diff --git a/frontend/src/app/main/data/workspace/comments.cljs b/frontend/src/app/main/data/workspace/comments.cljs index c8d3d94c3..2ce75718e 100644 --- a/frontend/src/app/main/data/workspace/comments.cljs +++ b/frontend/src/app/main/data/workspace/comments.cljs @@ -136,7 +136,7 @@ (rx/merge (rx/of (dwc/commit-changes changes)) - (->> (rp/mutation :update-comment-thread-position thread) + (->> (rp/cmd! :update-comment-thread-position thread) (rx/catch #(rx/throw {:type :update-comment-thread-position})) (rx/ignore)))))))) From 183e0bf985670f66052d25ce66dfb7f1c5be745b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 8 Aug 2022 10:51:08 +0200 Subject: [PATCH 08/11] :sparkles: Simplify select all implementation --- .../app/main/data/workspace/selection.cljs | 65 ++++++------------- 1 file changed, 21 insertions(+), 44 deletions(-) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index e859c47f1..72d3298fb 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -178,56 +178,33 @@ (let [objects (wsh/lookup-page-objects state)] (rx/of (dwc/expand-all-parents ids objects)))))) - - -(defn- select-siblings - [state parent] - (let [children (wsh/lookup-shapes state (:shapes parent)) - selected (into (d/ordered-set) - (comp (remove :blocked) (map :id)) - children)] - (rx/of (select-shapes selected)))) - -(defn- select-all-frame - [state] - (let [focus (:workspace-focus-selected state) - objects (-> (wsh/lookup-page-objects state) - (cp/focus-objects focus)) - - selected (let [frame-ids (into #{} (comp - (map (d/getf objects)) - (map :frame-id)) - (wsh/lookup-selected state)) - frame-id (if (= 1 (count frame-ids)) - (first frame-ids) - uuid/zero)] - (cph/get-immediate-children objects frame-id)) - - selected (into (d/ordered-set) - (comp (remove :blocked) (map :id)) - selected)] - - (rx/of (select-shapes selected)))) - - (defn select-all [] (ptk/reify ::select-all ptk/WatchEvent (watch [_ state _] - (let [current-selection-parents (->> (wsh/lookup-selected state) - (wsh/lookup-shapes state) - (into #{} (map :parent-id))) - num-parents (count current-selection-parents) - parent (when (= num-parents 1) - (wsh/lookup-shape state (first current-selection-parents)))] + (let [;; Make the select-all aware of the focus mode; in this + ;; case delimit the objects to the focused shapes if focus + ;; mode is active + focus (:workspace-focus-selected state) + objects (-> (wsh/lookup-page-objects state) + (cp/focus-objects focus)) - (case num-parents - 0 (select-all-frame state) - 1 (if (cph/frame-shape? parent) - (select-all-frame state) - (select-siblings state parent)) - nil))))) + lookup (d/getf objects) + parents (->> (wsh/lookup-selected state) + (into #{} (comp (keep lookup) (map :parent-id)))) + + ;; If we have a only unique parent, then use it as main + ;; anchor for the selection; if not, use the root frame as + ;; parent + parent (if (= 1 (count parents)) + (-> parents first lookup) + (lookup uuid/zero)) + + toselect (->> (cph/get-immediate-children objects (:id parent)) + (into (d/ordered-set) (comp (remove :blocked) (map :id))))] + + (rx/of (select-shapes toselect)))))) (defn deselect-all "Clear all possible state of drawing, edition From c30d4d313cc27a0e06a2444a56136bfaeb69a47b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 8 Aug 2022 12:09:16 +0200 Subject: [PATCH 09/11] :bug: Force file-id association with file-media-object on exportation This is needed because we may have situation when a file is using a file-media-object reference from other file (probably a library that is not included in the exportation); in this case we need to forcely embed it. --- backend/src/app/rpc/commands/binfile.clj | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index 36c51b0e6..3bea4d660 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -301,11 +301,17 @@ "SELECT * FROM file_media_object WHERE id = ANY(?)") (defn- retrieve-file-media - [pool {:keys [data] :as file}] + [pool {:keys [data id] :as file}] (with-open [^AutoCloseable conn (db/open pool)] (let [ids (app.tasks.file-gc/collect-used-media data) ids (db/create-array conn "uuid" ids)] - (db/exec! conn [sql:file-media-objects ids])))) + + ;; We assoc the file-id again to the file-media-object row + ;; because there are cases that used objects refer to other + ;; files and we need to ensure in the exportation process that + ;; all ids matches + (->> (db/exec! conn [sql:file-media-objects ids]) + (mapv #(assoc % :file-id id)))))) (def ^:private storage-object-id-xf (comp From 58a06b8cf3f7b0f9d35439fce5e6510016f4e1d9 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 8 Aug 2022 12:14:37 +0200 Subject: [PATCH 10/11] :bug: Ignore invalid file references on importing file-media-object --- backend/src/app/rpc/commands/binfile.clj | 58 ++++++++++++++---------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index 3bea4d660..e10abf969 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -545,16 +545,13 @@ (us/assert! ::read-import-options options) (letfn [(lookup-index [id] - (if ignore-index-errors? - (or (get @*index* id) id) - (let [val (get @*index* id)] - (l/trace :fn "lookup-index" :id id :val val ::l/async false) - (when-not val - (ex/raise :type :validation - :code :incomplete-index - :hint "looks like index has missing data")) - val))) - + (let [val (get @*index* id)] + (l/trace :fn "lookup-index" :id id :val val ::l/async false) + (when (and (not ignore-index-errors?) (not val)) + (ex/raise :type :validation + :code :incomplete-index + :hint "looks like index has missing data")) + (or val id))) (update-index [index coll] (loop [items (seq coll) index index] @@ -707,7 +704,6 @@ (let [storage (media/configure-assets-storage storage) ids (read-obj! input)] - ;; Step 1: process all storage objects (doseq [expected-storage-id ids] (let [id (read-uuid! input) mdata (read-obj! input)] @@ -729,18 +725,28 @@ (assoc ::sto/touched-at (dt/now))) sobject @(sto/put-object! storage params)] (l/trace :hint "persisted storage object" :id id :new-id (:id sobject) ::l/async false) - (vswap! *index* assoc id (:id sobject))))) + (vswap! *index* assoc id (:id sobject))))))) - ;; Step 2: insert all file-media-object rows with correct - ;; storage-id reference. - (doseq [item @*media*] - (l/trace :hint "inserting file media objects" :id (:id item) ::l/async false) - (db/insert! *conn* :file-media-object - (-> item - (update :file-id lookup-index) - (d/update-when :media-id lookup-index) - (d/update-when :thumbnail-id lookup-index)) - {:on-conflict-do-nothing overwrite?}))))] + (persist-file-media-objects! [] + (l/debug :hint "processing file media objects" :section :v1/sobjects ::l/async false) + + ;; Step 2: insert all file-media-object rows with correct + ;; storage-id reference. + (doseq [item @*media*] + (l/trace :hint "inserting file media object" + :id (:id item) + :file-id (:file-id item) + ::l/async false) + + (let [file-id (lookup-index (:file-id item))] + (if (= file-id (:file-id item)) + (l/warn :hint "ignoring file media object" :file-id (:file-id item) ::l/async false) + (db/insert! *conn* :file-media-object + (-> item + (assoc :file-id file-id) + (d/update-when :media-id lookup-index) + (d/update-when :thumbnail-id lookup-index)) + {:on-conflict-do-nothing overwrite?})))))] (with-open [input (bs/zstd-input-stream input)] (with-open [input (bs/data-input-stream input)] @@ -756,9 +762,11 @@ (doseq [section sections] (case section - :v1/rels (read-rels-section! input) - :v1/files (read-files-section! input files) - :v1/sobjects (read-sobjects-section! input)))))))))) + :v1/rels (read-rels-section! input) + :v1/files (read-files-section! input files) + :v1/sobjects (do + (read-sobjects-section! input) + (persist-file-media-objects!))))))))))) (defn export! [cfg] From 763877b7133335fa26b3ad40edbee8089cd55d75 Mon Sep 17 00:00:00 2001 From: Eva Date: Tue, 9 Aug 2022 12:07:16 +0200 Subject: [PATCH 11/11] :bug: Fix recent fonts info --- CHANGES.md | 1 + frontend/src/app/main/refs.cljs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index cb899ee5e..18053ea6e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -34,6 +34,7 @@ ### :bug: Bugs fixed +- Fix recent fonts info [Taiga #3953](https://tree.taiga.io/project/penpot/issue/3953) - Fix clipped elements affect boards and centering [Taiga #3666](https://tree.taiga.io/project/penpot/issue/3666) - Fix intro action in multi input [Taiga #3541](https://tree.taiga.io/project/penpot/issue/3541) - Fix team default image [Taiga #3919](https://tree.taiga.io/project/penpot/issue/3919) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 81d51b461..112d4b001 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -209,7 +209,7 @@ (def workspace-recent-fonts (l/derived (fn [data] - (get data :workspace-data [])) + (get data :recent-fonts [])) workspace-data)) (def workspace-file-typography