mirror of
https://github.com/penpot/penpot.git
synced 2026-05-29 19:58:09 +00:00
The main objective is prevent deletion of objects that can leave unreachable orphan objects which we are unable to correctly track. Additionally, this commit includes: 1. Properly implement safe cascade deletion of all participating tables on soft deletion in the objects-gc task; 2. Make the file thumbnail related tables also participate in the touch/refcount mechanism applyign to the same safety checks; 3. Add helper for db query lazy iteration using PostgreSQL support for server side cursors; 4. Fix efficiency issues on gc related task using server side cursors instead of custom chunked iteration for processing data. The problem resided when a large chunk of rows that has identical value on the deleted_at column and the chunk size is small (the default); when the custom chunked iteration only reads a first N items and skip the rest of the set to the next run. This has caused many objects to remain pending to be eliminated, taking up space for longer than expected. The server side cursor based iteration does not has this problem and iterates correctly over all objects. 5. Fix refcount issues on font variant deletion RPC methods
574 lines
22 KiB
Clojure
574 lines
22 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC
|
|
|
|
(ns app.rpc.commands.comments
|
|
(:require
|
|
[app.common.data.macros :as dm]
|
|
[app.common.exceptions :as ex]
|
|
[app.common.geom.point :as gpt]
|
|
[app.common.spec :as us]
|
|
[app.common.uuid :as uuid]
|
|
[app.db :as db]
|
|
[app.features.fdata :as feat.fdata]
|
|
[app.loggers.audit :as-alias audit]
|
|
[app.loggers.webhooks :as-alias webhooks]
|
|
[app.rpc :as-alias rpc]
|
|
[app.rpc.commands.files :as files]
|
|
[app.rpc.commands.teams :as teams]
|
|
[app.rpc.doc :as-alias doc]
|
|
[app.rpc.quotes :as quotes]
|
|
[app.rpc.retry :as rtry]
|
|
[app.util.pointer-map :as pmap]
|
|
[app.util.services :as sv]
|
|
[app.util.time :as dt]
|
|
[clojure.spec.alpha :as s]))
|
|
|
|
;; --- GENERAL PURPOSE INTERNAL HELPERS
|
|
|
|
(defn decode-row
|
|
[{:keys [participants position] :as row}]
|
|
(cond-> row
|
|
(db/pgpoint? position) (assoc :position (db/decode-pgpoint position))
|
|
(db/pgobject? participants) (assoc :participants (db/decode-transit-pgobject participants))))
|
|
|
|
(def sql:get-file
|
|
"select f.id, f.modified_at, f.revn, f.features,
|
|
f.project_id, p.team_id, f.data
|
|
from file as f
|
|
join project as p on (p.id = f.project_id)
|
|
where f.id = ?
|
|
and f.deleted_at is null")
|
|
|
|
(defn- get-file
|
|
"A specialized version of get-file for comments module."
|
|
[{:keys [::db/conn] :as cfg} file-id page-id]
|
|
(if-let [{:keys [data] :as file} (some-> (db/exec-one! conn [sql:get-file file-id])
|
|
(files/decode-row))]
|
|
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
|
|
(-> file
|
|
(assoc :page-name (dm/get-in data [:pages-index page-id :name]))
|
|
(assoc :page-id page-id)))
|
|
|
|
(ex/raise :type :not-found
|
|
:code :object-not-found
|
|
:hint "file not found")))
|
|
|
|
(defn- get-comment-thread
|
|
[conn thread-id & {:as opts}]
|
|
(-> (db/get-by-id conn :comment-thread thread-id opts)
|
|
(decode-row)))
|
|
|
|
(defn- get-comment
|
|
[conn comment-id & {:keys [for-update?]}]
|
|
(db/get-by-id conn :comment comment-id {:for-update for-update?}))
|
|
|
|
(defn- get-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)))
|
|
|
|
(def sql:upsert-comment-thread-status
|
|
"insert into comment_thread_status (thread_id, profile_id, modified_at)
|
|
values (?, ?, ?)
|
|
on conflict (thread_id, profile_id)
|
|
do update set modified_at = ?
|
|
returning modified_at;")
|
|
|
|
(defn upsert-comment-thread-status!
|
|
([conn profile-id thread-id]
|
|
(upsert-comment-thread-status! conn profile-id thread-id (dt/now)))
|
|
([conn profile-id thread-id mod-at]
|
|
(db/exec-one! conn [sql:upsert-comment-thread-status thread-id profile-id mod-at mod-at])))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; QUERY COMMANDS
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; --- COMMAND: Get Comment Threads
|
|
|
|
(declare ^:private get-comment-threads)
|
|
|
|
(s/def ::team-id ::us/uuid)
|
|
(s/def ::file-id ::us/uuid)
|
|
(s/def ::share-id (s/nilable ::us/uuid))
|
|
|
|
(s/def ::get-comment-threads
|
|
(s/and (s/keys :req [::rpc/profile-id]
|
|
:opt-un [::file-id ::share-id ::team-id])
|
|
#(or (:file-id %) (:team-id %))))
|
|
|
|
(sv/defmethod ::get-comment-threads
|
|
{::doc/added "1.15"}
|
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id share-id] :as params}]
|
|
(dm/with-open [conn (db/open pool)]
|
|
(files/check-comment-permissions! conn profile-id file-id share-id)
|
|
(get-comment-threads conn profile-id file-id)))
|
|
|
|
(def sql:comment-threads
|
|
"select distinct on (ct.id)
|
|
ct.*,
|
|
f.name as file_name,
|
|
f.project_id as project_id,
|
|
first_value(c.content) over w as content,
|
|
(select count(1)
|
|
from comment as c
|
|
where c.thread_id = ct.id) as count_comments,
|
|
(select count(1)
|
|
from comment as c
|
|
where c.thread_id = ct.id
|
|
and c.created_at >= coalesce(cts.modified_at, ct.created_at)) as count_unread_comments
|
|
from comment_thread as ct
|
|
inner join comment as c on (c.thread_id = ct.id)
|
|
inner join file as f on (f.id = ct.file_id)
|
|
left join comment_thread_status as cts
|
|
on (cts.thread_id = ct.id and
|
|
cts.profile_id = ?)
|
|
where ct.file_id = ?
|
|
window w as (partition by c.thread_id order by c.created_at asc)")
|
|
|
|
(defn- get-comment-threads
|
|
[conn profile-id file-id]
|
|
(->> (db/exec! conn [sql:comment-threads profile-id file-id])
|
|
(into [] (map decode-row))))
|
|
|
|
;; --- COMMAND: Get Unread Comment Threads
|
|
|
|
(declare ^:private get-unread-comment-threads)
|
|
|
|
(s/def ::team-id ::us/uuid)
|
|
(s/def ::get-unread-comment-threads
|
|
(s/keys :req [::rpc/profile-id]
|
|
:req-un [::team-id]))
|
|
|
|
(sv/defmethod ::get-unread-comment-threads
|
|
{::doc/added "1.15"}
|
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id] :as params}]
|
|
(dm/with-open [conn (db/open pool)]
|
|
(teams/check-read-permissions! conn profile-id team-id)
|
|
(get-unread-comment-threads conn profile-id team-id)))
|
|
|
|
(def sql:comment-threads-by-team
|
|
"select distinct on (ct.id)
|
|
ct.*,
|
|
f.name as file_name,
|
|
f.project_id as project_id,
|
|
first_value(c.content) over w as content,
|
|
(select count(1)
|
|
from comment as c
|
|
where c.thread_id = ct.id) as count_comments,
|
|
(select count(1)
|
|
from comment as c
|
|
where c.thread_id = ct.id
|
|
and c.created_at >= coalesce(cts.modified_at, ct.created_at)) as count_unread_comments
|
|
from comment_thread as ct
|
|
inner join comment as c on (c.thread_id = ct.id)
|
|
inner join file as f on (f.id = ct.file_id)
|
|
inner join project as p on (p.id = f.project_id)
|
|
left join comment_thread_status as cts
|
|
on (cts.thread_id = ct.id and
|
|
cts.profile_id = ?)
|
|
where p.team_id = ?
|
|
window w as (partition by c.thread_id order by c.created_at asc)")
|
|
|
|
(def sql:unread-comment-threads-by-team
|
|
(str "with threads as (" sql:comment-threads-by-team ")"
|
|
"select * from threads where count_unread_comments > 0"))
|
|
|
|
(defn- get-unread-comment-threads
|
|
[conn profile-id team-id]
|
|
(->> (db/exec! conn [sql:unread-comment-threads-by-team profile-id team-id])
|
|
(into [] (map decode-row))))
|
|
|
|
|
|
;; --- COMMAND: Get Single Comment Thread
|
|
|
|
(s/def ::get-comment-thread
|
|
(s/keys :req [::rpc/profile-id]
|
|
:req-un [::file-id ::us/id]
|
|
:opt-un [::share-id]))
|
|
|
|
(sv/defmethod ::get-comment-thread
|
|
{::doc/added "1.15"}
|
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id id share-id] :as params}]
|
|
(dm/with-open [conn (db/open pool)]
|
|
(files/check-comment-permissions! conn profile-id file-id share-id)
|
|
(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)))))
|
|
|
|
;; --- COMMAND: Retrieve Comments
|
|
|
|
(declare ^:private get-comments)
|
|
|
|
(s/def ::thread-id ::us/uuid)
|
|
(s/def ::get-comments
|
|
(s/keys :req [::rpc/profile-id]
|
|
:req-un [::thread-id]
|
|
:opt-un [::share-id]))
|
|
|
|
(sv/defmethod ::get-comments
|
|
{::doc/added "1.15"}
|
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id thread-id share-id] :as params}]
|
|
(dm/with-open [conn (db/open pool)]
|
|
(let [{:keys [file-id] :as thread} (get-comment-thread conn thread-id)]
|
|
(files/check-comment-permissions! conn profile-id file-id 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- get-comments
|
|
[conn thread-id]
|
|
(->> (db/query conn :comment
|
|
{:thread-id thread-id}
|
|
{:order-by [[:created-at :asc]]})
|
|
(into [] (map decode-row))))
|
|
|
|
;; --- COMMAND: Get file comments users
|
|
|
|
;; All the profiles that had comment the file, plus the current
|
|
;; profile.
|
|
|
|
(def sql:file-comment-users
|
|
"WITH available_profiles AS (
|
|
SELECT DISTINCT owner_id AS id
|
|
FROM comment
|
|
WHERE thread_id IN (SELECT id FROM comment_thread WHERE file_id=?)
|
|
)
|
|
SELECT p.id,
|
|
p.email,
|
|
p.fullname AS name,
|
|
p.fullname AS fullname,
|
|
p.photo_id,
|
|
p.is_active
|
|
FROM profile AS p
|
|
WHERE p.id IN (SELECT id FROM available_profiles) OR p.id=?")
|
|
|
|
(defn get-file-comments-users
|
|
[conn file-id profile-id]
|
|
(db/exec! conn [sql:file-comment-users file-id profile-id]))
|
|
|
|
(s/def ::get-profiles-for-file-comments
|
|
(s/keys :req [::rpc/profile-id]
|
|
:req-un [::file-id]
|
|
:opt-un [::share-id]))
|
|
|
|
(sv/defmethod ::get-profiles-for-file-comments
|
|
"Retrieves a list of profiles with limited set of properties of all
|
|
participants on comment threads of the file."
|
|
{::doc/added "1.15"
|
|
::doc/changes ["1.15" "Imported from queries and renamed."]}
|
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id share-id]}]
|
|
(dm/with-open [conn (db/open pool)]
|
|
(files/check-comment-permissions! conn profile-id file-id share-id)
|
|
(get-file-comments-users conn file-id profile-id)))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; MUTATION COMMANDS
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(declare ^:private create-comment-thread)
|
|
|
|
;; --- COMMAND: Create Comment Thread
|
|
|
|
(s/def ::page-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 [::rpc/profile-id]
|
|
:req-un [::file-id ::position ::content ::page-id ::frame-id]
|
|
:opt-un [::share-id]))
|
|
|
|
(sv/defmethod ::create-comment-thread
|
|
{::doc/added "1.15"
|
|
::webhooks/event? true}
|
|
[cfg {:keys [::rpc/profile-id ::rpc/request-at file-id page-id share-id position content frame-id]}]
|
|
(db/tx-run! cfg
|
|
(fn [{:keys [::db/conn] :as cfg}]
|
|
(files/check-comment-permissions! conn profile-id file-id share-id)
|
|
(let [{:keys [team-id project-id page-name] :as file} (get-file cfg file-id page-id)]
|
|
|
|
(run! (partial quotes/check-quote! conn)
|
|
(list {::quotes/id ::quotes/comment-threads-per-file
|
|
::quotes/profile-id profile-id
|
|
::quotes/team-id team-id
|
|
::quotes/project-id project-id
|
|
::quotes/file-id file-id}
|
|
{::quotes/id ::quotes/comments-per-file
|
|
::quotes/profile-id profile-id
|
|
::quotes/team-id team-id
|
|
::quotes/project-id project-id
|
|
::quotes/file-id file-id}))
|
|
|
|
|
|
(-> cfg
|
|
(assoc ::rtry/when rtry/conflict-exception?)
|
|
(assoc ::rtry/label "create-comment-thread")
|
|
(rtry/invoke create-comment-thread {:created-at request-at
|
|
:profile-id profile-id
|
|
:file-id file-id
|
|
:page-id page-id
|
|
:page-name page-name
|
|
:position position
|
|
:content content
|
|
:frame-id frame-id}))))))
|
|
|
|
(defn- create-comment-thread
|
|
[{:keys [::db/conn]} {:keys [profile-id file-id page-id page-name created-at position content frame-id]}]
|
|
(let [;; NOTE: we take the next seq number from a separate query because the whole
|
|
;; operation can be retried on conflict, and in this case the new seq shold be
|
|
;; retrieved from the database.
|
|
seqn (get-next-seqn conn file-id)
|
|
thread-id (uuid/next)
|
|
thread (db/insert! conn :comment-thread
|
|
{:id thread-id
|
|
:file-id file-id
|
|
:owner-id profile-id
|
|
:participants (db/tjson #{profile-id})
|
|
:page-name page-name
|
|
:page-id page-id
|
|
:created-at created-at
|
|
:modified-at created-at
|
|
:seqn seqn
|
|
:position (db/pgpoint position)
|
|
:frame-id frame-id})
|
|
comment (db/insert! conn :comment
|
|
{:id (uuid/next)
|
|
:thread-id thread-id
|
|
:owner-id profile-id
|
|
:created-at created-at
|
|
:modified-at created-at
|
|
:content content})]
|
|
|
|
;; Make the current thread as read.
|
|
(upsert-comment-thread-status! conn profile-id thread-id created-at)
|
|
|
|
;; Optimistic update of current seq number on file.
|
|
(db/update! conn :file
|
|
{:comment-thread-seqn seqn}
|
|
{:id file-id})
|
|
|
|
(-> thread
|
|
(select-keys [:id :file-id :page-id])
|
|
(assoc :comment-id (:id comment)))))
|
|
|
|
;; --- 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 [::rpc/profile-id]
|
|
:req-un [::id]
|
|
:opt-un [::share-id]))
|
|
|
|
(sv/defmethod ::update-comment-thread-status
|
|
{::doc/added "1.15"}
|
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id share-id] :as params}]
|
|
(db/with-atomic [conn pool]
|
|
(let [{:keys [file-id] :as thread} (get-comment-thread conn id ::db/for-update? true)]
|
|
(files/check-comment-permissions! conn profile-id file-id share-id)
|
|
(upsert-comment-thread-status! conn profile-id id))))
|
|
|
|
|
|
;; --- COMMAND: Update Comment Thread
|
|
|
|
(s/def ::is-resolved ::us/boolean)
|
|
(s/def ::update-comment-thread
|
|
(s/keys :req [::rpc/profile-id]
|
|
:req-un [::id ::is-resolved]
|
|
:opt-un [::share-id]))
|
|
|
|
(sv/defmethod ::update-comment-thread
|
|
{::doc/added "1.15"}
|
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id is-resolved share-id] :as params}]
|
|
(db/with-atomic [conn pool]
|
|
(let [{:keys [file-id] :as thread} (get-comment-thread conn id ::db/for-update? true)]
|
|
(files/check-comment-permissions! conn profile-id file-id share-id)
|
|
(db/update! conn :comment-thread
|
|
{:is-resolved is-resolved}
|
|
{:id id})
|
|
nil)))
|
|
|
|
|
|
;; --- COMMAND: Add Comment
|
|
|
|
(declare ^:private get-comment-thread)
|
|
|
|
(s/def ::create-comment
|
|
(s/keys :req [::rpc/profile-id]
|
|
:req-un [::thread-id ::content]
|
|
:opt-un [::share-id]))
|
|
|
|
(sv/defmethod ::create-comment
|
|
{::doc/added "1.15"
|
|
::webhooks/event? true}
|
|
[cfg {:keys [::rpc/profile-id ::rpc/request-at thread-id share-id content]}]
|
|
(db/tx-run! cfg
|
|
(fn [{:keys [::db/conn] :as cfg}]
|
|
(let [{:keys [file-id page-id] :as thread} (get-comment-thread conn thread-id ::db/for-update? true)
|
|
{:keys [team-id project-id page-name] :as file} (get-file cfg file-id page-id)]
|
|
|
|
(files/check-comment-permissions! conn profile-id (:id file) share-id)
|
|
(quotes/check-quote! conn
|
|
{::quotes/id ::quotes/comments-per-file
|
|
::quotes/profile-id profile-id
|
|
::quotes/team-id team-id
|
|
::quotes/project-id project-id
|
|
::quotes/file-id (:id file)})
|
|
|
|
;; Update the page-name cached attribute on comment thread table.
|
|
(when (not= page-name (:page-name thread))
|
|
(db/update! conn :comment-thread
|
|
{:page-name page-name}
|
|
{:id thread-id}))
|
|
|
|
(let [comment (db/insert! conn :comment
|
|
{:id (uuid/next)
|
|
:created-at request-at
|
|
:modified-at request-at
|
|
:thread-id thread-id
|
|
:owner-id profile-id
|
|
:content content})
|
|
props {:file-id file-id
|
|
:share-id nil}]
|
|
|
|
;; Update thread modified-at attribute and assoc the current
|
|
;; profile to the participant set.
|
|
(db/update! conn :comment-thread
|
|
{:modified-at request-at
|
|
:participants (-> (:participants thread #{})
|
|
(conj profile-id)
|
|
(db/tjson))}
|
|
{:id thread-id})
|
|
|
|
;; Update the current profile status in relation to the
|
|
;; current thread.
|
|
(upsert-comment-thread-status! conn profile-id thread-id request-at)
|
|
|
|
(vary-meta comment assoc ::audit/props props))))))
|
|
|
|
|
|
;; --- COMMAND: Update Comment
|
|
|
|
(s/def ::update-comment
|
|
(s/keys :req [::rpc/profile-id]
|
|
:req-un [::id ::content]
|
|
:opt-un [::share-id]))
|
|
|
|
(sv/defmethod ::update-comment
|
|
{::doc/added "1.15"}
|
|
[cfg {:keys [::rpc/profile-id ::rpc/request-at id share-id content]}]
|
|
|
|
(db/tx-run! cfg
|
|
(fn [{:keys [::db/conn] :as cfg}]
|
|
(let [{:keys [thread-id owner-id] :as comment} (get-comment conn id ::db/for-update? true)
|
|
{:keys [file-id page-id] :as thread} (get-comment-thread conn thread-id ::db/for-update? true)]
|
|
|
|
(files/check-comment-permissions! conn profile-id file-id share-id)
|
|
|
|
;; Don't allow edit comments to not owners
|
|
(when-not (= owner-id profile-id)
|
|
(ex/raise :type :validation
|
|
:code :not-allowed))
|
|
|
|
(let [{:keys [page-name] :as file} (get-file cfg file-id page-id)]
|
|
(db/update! conn :comment
|
|
{:content content
|
|
:modified-at request-at}
|
|
{:id id})
|
|
|
|
(db/update! conn :comment-thread
|
|
{:modified-at request-at
|
|
:page-name page-name}
|
|
{:id thread-id})
|
|
nil)))))
|
|
|
|
;; --- COMMAND: Delete Comment Thread
|
|
|
|
(s/def ::delete-comment-thread
|
|
(s/keys :req [::rpc/profile-id]
|
|
:req-un [::id]
|
|
:opt-un [::share-id]))
|
|
|
|
(sv/defmethod ::delete-comment-thread
|
|
{::doc/added "1.15"}
|
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id share-id]}]
|
|
(db/with-atomic [conn pool]
|
|
(let [{:keys [owner-id file-id] :as thread} (get-comment-thread conn id ::db/for-update? true)]
|
|
(files/check-comment-permissions! conn profile-id file-id share-id)
|
|
(when-not (= owner-id 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 [::rpc/profile-id]
|
|
:req-un [::id]
|
|
:opt-un [::share-id]))
|
|
|
|
(sv/defmethod ::delete-comment
|
|
{::doc/added "1.15"}
|
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id id share-id] :as params}]
|
|
(db/with-atomic [conn pool]
|
|
(let [{:keys [owner-id thread-id] :as comment} (get-comment conn id ::db/for-update? true)
|
|
{:keys [file-id] :as thread} (get-comment-thread conn thread-id)]
|
|
(files/check-comment-permissions! conn profile-id file-id share-id)
|
|
(when-not (= owner-id 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 [::rpc/profile-id]
|
|
:req-un [::id ::position ::frame-id]
|
|
:opt-un [::share-id]))
|
|
|
|
(sv/defmethod ::update-comment-thread-position
|
|
{::doc/added "1.15"}
|
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id ::rpc/request-at id position frame-id share-id]}]
|
|
(db/with-atomic [conn pool]
|
|
(let [{:keys [file-id] :as thread} (get-comment-thread conn id ::db/for-update? true)]
|
|
(files/check-comment-permissions! conn profile-id file-id share-id)
|
|
(db/update! conn :comment-thread
|
|
{:modified-at request-at
|
|
: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 [::rpc/profile-id]
|
|
:req-un [::id ::frame-id]
|
|
:opt-un [::share-id]))
|
|
|
|
(sv/defmethod ::update-comment-thread-frame
|
|
{::doc/added "1.15"}
|
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id ::rpc/request-at id frame-id share-id]}]
|
|
(db/with-atomic [conn pool]
|
|
(let [{:keys [file-id] :as thread} (get-comment-thread conn id ::db/for-update? true)]
|
|
(files/check-comment-permissions! conn profile-id file-id share-id)
|
|
(db/update! conn :comment-thread
|
|
{:modified-at request-at
|
|
:frame-id frame-id}
|
|
{:id id})
|
|
nil)))
|