mirror of
https://github.com/penpot/penpot.git
synced 2025-03-11 07:11:32 -05:00
♻️ Move management mutations to commands
This commit is contained in:
parent
d832482dae
commit
ed701fd9c5
6 changed files with 381 additions and 412 deletions
|
@ -242,6 +242,7 @@
|
||||||
(let [cfg (assoc cfg ::type "command" ::metrics-id :rpc-command-timing)]
|
(let [cfg (assoc cfg ::type "command" ::metrics-id :rpc-command-timing)]
|
||||||
(->> (sv/scan-ns 'app.rpc.commands.binfile
|
(->> (sv/scan-ns 'app.rpc.commands.binfile
|
||||||
'app.rpc.commands.comments
|
'app.rpc.commands.comments
|
||||||
|
'app.rpc.commands.management
|
||||||
'app.rpc.commands.auth
|
'app.rpc.commands.auth
|
||||||
'app.rpc.commands.ldap
|
'app.rpc.commands.ldap
|
||||||
'app.rpc.commands.demo)
|
'app.rpc.commands.demo)
|
||||||
|
|
352
backend/src/app/rpc/commands/management.clj
Normal file
352
backend/src/app/rpc/commands/management.clj
Normal file
|
@ -0,0 +1,352 @@
|
||||||
|
;; 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) UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.rpc.commands.management
|
||||||
|
"A collection of RPC methods for manage the files, projects and team organization."
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
|
[app.common.pages.migrations :as pmg]
|
||||||
|
[app.common.spec :as us]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[app.db :as db]
|
||||||
|
[app.rpc.doc :as-alias doc]
|
||||||
|
[app.rpc.mutations.projects :refer [create-project-role create-project]]
|
||||||
|
[app.rpc.queries.projects :as proj]
|
||||||
|
[app.rpc.queries.teams :as teams]
|
||||||
|
[app.util.blob :as blob]
|
||||||
|
[app.util.services :as sv]
|
||||||
|
[app.util.time :as dt]
|
||||||
|
[clojure.spec.alpha :as s]
|
||||||
|
[clojure.walk :as walk]))
|
||||||
|
|
||||||
|
;; --- COMMAND: Duplicate File
|
||||||
|
|
||||||
|
(declare duplicate-file)
|
||||||
|
|
||||||
|
(s/def ::id ::us/uuid)
|
||||||
|
(s/def ::profile-id ::us/uuid)
|
||||||
|
(s/def ::project-id ::us/uuid)
|
||||||
|
(s/def ::file-id ::us/uuid)
|
||||||
|
(s/def ::team-id ::us/uuid)
|
||||||
|
(s/def ::name ::us/string)
|
||||||
|
|
||||||
|
(s/def ::duplicate-file
|
||||||
|
(s/keys :req-un [::profile-id ::file-id]
|
||||||
|
:opt-un [::name]))
|
||||||
|
|
||||||
|
(sv/defmethod ::duplicate-file
|
||||||
|
"Duplicate a single file in the same team."
|
||||||
|
{::doc/added "1.16"}
|
||||||
|
[{:keys [pool] :as cfg} params]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(duplicate-file conn params)))
|
||||||
|
|
||||||
|
(defn- remap-id
|
||||||
|
[item index key]
|
||||||
|
(cond-> item
|
||||||
|
(contains? item key)
|
||||||
|
(assoc key (get index (get item key) (get item key)))))
|
||||||
|
|
||||||
|
(defn- process-file
|
||||||
|
[file index]
|
||||||
|
(letfn [(process-form [form]
|
||||||
|
(cond-> form
|
||||||
|
;; Relink library items
|
||||||
|
(and (map? form)
|
||||||
|
(uuid? (:component-file form)))
|
||||||
|
(update :component-file #(get index % %))
|
||||||
|
|
||||||
|
(and (map? form)
|
||||||
|
(uuid? (:fill-color-ref-file form)))
|
||||||
|
(update :fill-color-ref-file #(get index % %))
|
||||||
|
|
||||||
|
(and (map? form)
|
||||||
|
(uuid? (:stroke-color-ref-file form)))
|
||||||
|
(update :stroke-color-ref-file #(get index % %))
|
||||||
|
|
||||||
|
(and (map? form)
|
||||||
|
(uuid? (:typography-ref-file form)))
|
||||||
|
(update :typography-ref-file #(get index % %))
|
||||||
|
|
||||||
|
;; Relink Image Shapes
|
||||||
|
(and (map? form)
|
||||||
|
(map? (:metadata form))
|
||||||
|
(= :image (:type form)))
|
||||||
|
(update-in [:metadata :id] #(get index % %))))
|
||||||
|
|
||||||
|
;; A function responsible to analyze all file data and
|
||||||
|
;; replace the old :component-file reference with the new
|
||||||
|
;; ones, using the provided file-index
|
||||||
|
(relink-shapes [data]
|
||||||
|
(walk/postwalk process-form data))
|
||||||
|
|
||||||
|
;; A function responsible of process the :media attr of file
|
||||||
|
;; data and remap the old ids with the new ones.
|
||||||
|
(relink-media [media]
|
||||||
|
(reduce-kv (fn [res k v]
|
||||||
|
(let [id (get index k)]
|
||||||
|
(if (uuid? id)
|
||||||
|
(-> res
|
||||||
|
(assoc id (assoc v :id id))
|
||||||
|
(dissoc k))
|
||||||
|
res)))
|
||||||
|
media
|
||||||
|
media))]
|
||||||
|
|
||||||
|
(update file :data
|
||||||
|
(fn [data]
|
||||||
|
(-> data
|
||||||
|
(blob/decode)
|
||||||
|
(assoc :id (:id file))
|
||||||
|
(pmg/migrate-data)
|
||||||
|
(update :pages-index relink-shapes)
|
||||||
|
(update :components relink-shapes)
|
||||||
|
(update :media relink-media)
|
||||||
|
(d/without-nils)
|
||||||
|
(blob/encode))))))
|
||||||
|
|
||||||
|
(def sql:retrieve-used-libraries
|
||||||
|
"select flr.*
|
||||||
|
from file_library_rel as flr
|
||||||
|
inner join file as l on (flr.library_file_id = l.id)
|
||||||
|
where flr.file_id = ?
|
||||||
|
and l.deleted_at is null")
|
||||||
|
|
||||||
|
(def sql:retrieve-used-media-objects
|
||||||
|
"select fmo.*
|
||||||
|
from file_media_object as fmo
|
||||||
|
inner join storage_object as so on (fmo.media_id = so.id)
|
||||||
|
where fmo.file_id = ?
|
||||||
|
and so.deleted_at is null")
|
||||||
|
|
||||||
|
(defn duplicate-file*
|
||||||
|
[conn {:keys [profile-id file index project-id name flibs fmeds]} {:keys [reset-shared-flag] :as opts}]
|
||||||
|
(let [flibs (or flibs (db/exec! conn [sql:retrieve-used-libraries (:id file)]))
|
||||||
|
fmeds (or fmeds (db/exec! conn [sql:retrieve-used-media-objects (:id file)]))
|
||||||
|
|
||||||
|
;; memo uniform creation/modification date
|
||||||
|
now (dt/now)
|
||||||
|
ignore (dt/plus now (dt/duration {:seconds 5}))
|
||||||
|
|
||||||
|
;; add to the index all file media objects.
|
||||||
|
index (reduce #(assoc %1 (:id %2) (uuid/next)) index fmeds)
|
||||||
|
|
||||||
|
flibs-xf (comp
|
||||||
|
(map #(remap-id % index :file-id))
|
||||||
|
(map #(remap-id % index :library-file-id))
|
||||||
|
(map #(assoc % :synced-at now))
|
||||||
|
(map #(assoc % :created-at now)))
|
||||||
|
|
||||||
|
;; remap all file-library-rel row
|
||||||
|
flibs (sequence flibs-xf flibs)
|
||||||
|
|
||||||
|
fmeds-xf (comp
|
||||||
|
(map #(assoc % :id (get index (:id %))))
|
||||||
|
(map #(assoc % :created-at now))
|
||||||
|
(map #(remap-id % index :file-id)))
|
||||||
|
|
||||||
|
;; remap all file-media-object rows
|
||||||
|
fmeds (sequence fmeds-xf fmeds)
|
||||||
|
|
||||||
|
file (cond-> file
|
||||||
|
(some? project-id)
|
||||||
|
(assoc :project-id project-id)
|
||||||
|
|
||||||
|
(some? name)
|
||||||
|
(assoc :name name)
|
||||||
|
|
||||||
|
(true? reset-shared-flag)
|
||||||
|
(assoc :is-shared false))
|
||||||
|
|
||||||
|
file (-> file
|
||||||
|
(assoc :created-at now)
|
||||||
|
(assoc :modified-at now)
|
||||||
|
(assoc :ignore-sync-until ignore)
|
||||||
|
(update :id #(get index %))
|
||||||
|
(process-file index))]
|
||||||
|
|
||||||
|
(db/insert! conn :file file)
|
||||||
|
(db/insert! conn :file-profile-rel
|
||||||
|
{:file-id (:id file)
|
||||||
|
:profile-id profile-id
|
||||||
|
:is-owner true
|
||||||
|
:is-admin true
|
||||||
|
:can-edit true})
|
||||||
|
|
||||||
|
(doseq [params flibs]
|
||||||
|
(db/insert! conn :file-library-rel params))
|
||||||
|
|
||||||
|
(doseq [params fmeds]
|
||||||
|
(db/insert! conn :file-media-object params))
|
||||||
|
|
||||||
|
file))
|
||||||
|
|
||||||
|
(defn duplicate-file
|
||||||
|
[conn {:keys [profile-id file-id] :as params}]
|
||||||
|
(let [file (db/get-by-id conn :file file-id)
|
||||||
|
index {file-id (uuid/next)}
|
||||||
|
params (assoc params :index index :file file)]
|
||||||
|
(proj/check-edition-permissions! conn profile-id (:project-id file))
|
||||||
|
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])
|
||||||
|
(-> (duplicate-file* conn params {:reset-shared-flag true})
|
||||||
|
(update :data blob/decode))))
|
||||||
|
|
||||||
|
;; --- COMMAND: Duplicate Project
|
||||||
|
|
||||||
|
(declare duplicate-project)
|
||||||
|
|
||||||
|
(s/def ::duplicate-project
|
||||||
|
(s/keys :req-un [::profile-id ::project-id]
|
||||||
|
:opt-un [::name]))
|
||||||
|
|
||||||
|
(sv/defmethod ::duplicate-project
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(duplicate-project conn params)))
|
||||||
|
|
||||||
|
(defn duplicate-project
|
||||||
|
[conn {:keys [profile-id project-id name] :as params}]
|
||||||
|
|
||||||
|
;; Defer all constraints
|
||||||
|
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])
|
||||||
|
|
||||||
|
(let [project (db/get-by-id conn :project project-id)
|
||||||
|
|
||||||
|
files (db/query conn :file
|
||||||
|
{:project-id (:id project)
|
||||||
|
:deleted-at nil}
|
||||||
|
{:columns [:id]})
|
||||||
|
|
||||||
|
project (cond-> project
|
||||||
|
(string? name)
|
||||||
|
(assoc :name name)
|
||||||
|
|
||||||
|
:always
|
||||||
|
(assoc :id (uuid/next)))]
|
||||||
|
|
||||||
|
;; Check if the source team-id allow creating new project for current user
|
||||||
|
(teams/check-edition-permissions! conn profile-id (:team-id project))
|
||||||
|
|
||||||
|
;; create the duplicated project and assign the current profile as
|
||||||
|
;; a project owner
|
||||||
|
(create-project conn project)
|
||||||
|
(create-project-role conn {:project-id (:id project)
|
||||||
|
:profile-id profile-id
|
||||||
|
:role :owner})
|
||||||
|
|
||||||
|
;; duplicate all files
|
||||||
|
(let [index (reduce #(assoc %1 (:id %2) (uuid/next)) {} files)
|
||||||
|
params (-> params
|
||||||
|
(dissoc :name)
|
||||||
|
(assoc :project-id (:id project))
|
||||||
|
(assoc :index index))]
|
||||||
|
(doseq [{:keys [id]} files]
|
||||||
|
(let [file (db/get-by-id conn :file id)
|
||||||
|
params (assoc params :file file)
|
||||||
|
opts {:reset-shared-flag false}]
|
||||||
|
(duplicate-file* conn params opts))))
|
||||||
|
|
||||||
|
;; return the created project
|
||||||
|
project))
|
||||||
|
|
||||||
|
;; --- COMMAND: Move file
|
||||||
|
|
||||||
|
(def sql:retrieve-files
|
||||||
|
"select id, project_id from file where id = ANY(?)")
|
||||||
|
|
||||||
|
(def sql:move-files
|
||||||
|
"update file set project_id = ? where id = ANY(?)")
|
||||||
|
|
||||||
|
(def sql:delete-broken-relations
|
||||||
|
"with broken as (
|
||||||
|
(select * from file_library_rel as flr
|
||||||
|
inner join file as f on (flr.file_id = f.id)
|
||||||
|
inner join project as p on (f.project_id = p.id)
|
||||||
|
inner join file as lf on (flr.library_file_id = lf.id)
|
||||||
|
inner join project as lp on (lf.project_id = lp.id)
|
||||||
|
where p.id = ANY(?)
|
||||||
|
and lp.team_id != p.team_id)
|
||||||
|
)
|
||||||
|
delete from file_library_rel as rel
|
||||||
|
using broken as br
|
||||||
|
where rel.file_id = br.file_id
|
||||||
|
and rel.library_file_id = br.library_file_id")
|
||||||
|
|
||||||
|
(defn move-files
|
||||||
|
[conn {:keys [profile-id ids project-id] :as params}]
|
||||||
|
|
||||||
|
(let [fids (db/create-array conn "uuid" ids)
|
||||||
|
files (db/exec! conn [sql:retrieve-files fids])
|
||||||
|
source (into #{} (map :project-id) files)
|
||||||
|
pids (->> (conj source project-id)
|
||||||
|
(db/create-array conn "uuid"))]
|
||||||
|
|
||||||
|
;; Check if we have permissions on the destination project
|
||||||
|
(proj/check-edition-permissions! conn profile-id project-id)
|
||||||
|
|
||||||
|
;; Check if we have permissions on all source projects
|
||||||
|
(doseq [project-id source]
|
||||||
|
(proj/check-edition-permissions! conn profile-id project-id))
|
||||||
|
|
||||||
|
(when (contains? source project-id)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :cant-move-to-same-project
|
||||||
|
:hint "Unable to move a file to the same project"))
|
||||||
|
|
||||||
|
;; move all files to the project
|
||||||
|
(db/exec-one! conn [sql:move-files project-id fids])
|
||||||
|
|
||||||
|
;; delete possible broken relations on moved files
|
||||||
|
(db/exec-one! conn [sql:delete-broken-relations pids])
|
||||||
|
|
||||||
|
nil))
|
||||||
|
|
||||||
|
(s/def ::ids (s/every ::us/uuid :kind set?))
|
||||||
|
(s/def ::move-files
|
||||||
|
(s/keys :req-un [::profile-id ::ids ::project-id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::move-files
|
||||||
|
[{:keys [pool] :as cfg} params]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(move-files conn params)))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- COMMAND: Move project
|
||||||
|
|
||||||
|
(defn move-project
|
||||||
|
[conn {:keys [profile-id team-id project-id] :as params}]
|
||||||
|
(let [project (db/get-by-id conn :project project-id {:columns [:id :team-id]})
|
||||||
|
pids (->> (db/query conn :project {:team-id (:team-id project)} {:columns [:id]})
|
||||||
|
(map :id)
|
||||||
|
(db/create-array conn "uuid"))]
|
||||||
|
|
||||||
|
(teams/check-edition-permissions! conn profile-id (:team-id project))
|
||||||
|
(teams/check-edition-permissions! conn profile-id team-id)
|
||||||
|
|
||||||
|
(when (= team-id (:team-id project))
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :cant-move-to-same-team
|
||||||
|
:hint "Unable to move a project to same team"))
|
||||||
|
|
||||||
|
;; move project to the destination team
|
||||||
|
(db/update! conn :project
|
||||||
|
{:team-id team-id}
|
||||||
|
{:id project-id})
|
||||||
|
|
||||||
|
;; delete possible broken relations on moved files
|
||||||
|
(db/exec-one! conn [sql:delete-broken-relations pids])
|
||||||
|
|
||||||
|
nil))
|
||||||
|
|
||||||
|
|
||||||
|
(s/def ::move-project
|
||||||
|
(s/keys :req-un [::profile-id ::team-id ::project-id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::move-project
|
||||||
|
[{:keys [pool] :as cfg} params]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(move-project conn params)))
|
|
@ -7,329 +7,52 @@
|
||||||
(ns app.rpc.mutations.management
|
(ns app.rpc.mutations.management
|
||||||
"Move & Duplicate RPC methods for files and projects."
|
"Move & Duplicate RPC methods for files and projects."
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
|
||||||
[app.common.exceptions :as ex]
|
|
||||||
[app.common.pages.migrations :as pmg]
|
|
||||||
[app.common.spec :as us]
|
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc.mutations.projects :refer [create-project-role create-project]]
|
[app.rpc.commands.management :as cmd.mgm]
|
||||||
[app.rpc.queries.projects :as proj]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.queries.teams :as teams]
|
|
||||||
[app.util.blob :as blob]
|
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[app.util.time :as dt]
|
[clojure.spec.alpha :as s]))
|
||||||
[clojure.spec.alpha :as s]
|
|
||||||
[clojure.walk :as walk]))
|
|
||||||
|
|
||||||
(s/def ::id ::us/uuid)
|
|
||||||
(s/def ::profile-id ::us/uuid)
|
|
||||||
(s/def ::project-id ::us/uuid)
|
|
||||||
(s/def ::file-id ::us/uuid)
|
|
||||||
(s/def ::team-id ::us/uuid)
|
|
||||||
(s/def ::name ::us/string)
|
|
||||||
|
|
||||||
(defn- remap-id
|
|
||||||
[item index key]
|
|
||||||
(cond-> item
|
|
||||||
(contains? item key)
|
|
||||||
(assoc key (get index (get item key) (get item key)))))
|
|
||||||
|
|
||||||
(defn- process-file
|
|
||||||
[file index]
|
|
||||||
(letfn [(process-form [form]
|
|
||||||
(cond-> form
|
|
||||||
;; Relink library items
|
|
||||||
(and (map? form)
|
|
||||||
(uuid? (:component-file form)))
|
|
||||||
(update :component-file #(get index % %))
|
|
||||||
|
|
||||||
(and (map? form)
|
|
||||||
(uuid? (:fill-color-ref-file form)))
|
|
||||||
(update :fill-color-ref-file #(get index % %))
|
|
||||||
|
|
||||||
(and (map? form)
|
|
||||||
(uuid? (:stroke-color-ref-file form)))
|
|
||||||
(update :stroke-color-ref-file #(get index % %))
|
|
||||||
|
|
||||||
(and (map? form)
|
|
||||||
(uuid? (:typography-ref-file form)))
|
|
||||||
(update :typography-ref-file #(get index % %))
|
|
||||||
|
|
||||||
;; Relink Image Shapes
|
|
||||||
(and (map? form)
|
|
||||||
(map? (:metadata form))
|
|
||||||
(= :image (:type form)))
|
|
||||||
(update-in [:metadata :id] #(get index % %))))
|
|
||||||
|
|
||||||
;; A function responsible to analyze all file data and
|
|
||||||
;; replace the old :component-file reference with the new
|
|
||||||
;; ones, using the provided file-index
|
|
||||||
(relink-shapes [data]
|
|
||||||
(walk/postwalk process-form data))
|
|
||||||
|
|
||||||
;; A function responsible of process the :media attr of file
|
|
||||||
;; data and remap the old ids with the new ones.
|
|
||||||
(relink-media [media]
|
|
||||||
(reduce-kv (fn [res k v]
|
|
||||||
(let [id (get index k)]
|
|
||||||
(if (uuid? id)
|
|
||||||
(-> res
|
|
||||||
(assoc id (assoc v :id id))
|
|
||||||
(dissoc k))
|
|
||||||
res)))
|
|
||||||
media
|
|
||||||
media))]
|
|
||||||
|
|
||||||
(update file :data
|
|
||||||
(fn [data]
|
|
||||||
(-> data
|
|
||||||
(blob/decode)
|
|
||||||
(assoc :id (:id file))
|
|
||||||
(pmg/migrate-data)
|
|
||||||
(update :pages-index relink-shapes)
|
|
||||||
(update :components relink-shapes)
|
|
||||||
(update :media relink-media)
|
|
||||||
(d/without-nils)
|
|
||||||
(blob/encode))))))
|
|
||||||
|
|
||||||
(def sql:retrieve-used-libraries
|
|
||||||
"select flr.*
|
|
||||||
from file_library_rel as flr
|
|
||||||
inner join file as l on (flr.library_file_id = l.id)
|
|
||||||
where flr.file_id = ?
|
|
||||||
and l.deleted_at is null")
|
|
||||||
|
|
||||||
(def sql:retrieve-used-media-objects
|
|
||||||
"select fmo.*
|
|
||||||
from file_media_object as fmo
|
|
||||||
inner join storage_object as so on (fmo.media_id = so.id)
|
|
||||||
where fmo.file_id = ?
|
|
||||||
and so.deleted_at is null")
|
|
||||||
|
|
||||||
(defn duplicate-file
|
|
||||||
[conn {:keys [profile-id file index project-id name flibs fmeds]} {:keys [reset-shared-flag] :as opts}]
|
|
||||||
(let [flibs (or flibs (db/exec! conn [sql:retrieve-used-libraries (:id file)]))
|
|
||||||
fmeds (or fmeds (db/exec! conn [sql:retrieve-used-media-objects (:id file)]))
|
|
||||||
|
|
||||||
;; memo uniform creation/modification date
|
|
||||||
now (dt/now)
|
|
||||||
ignore (dt/plus now (dt/duration {:seconds 5}))
|
|
||||||
|
|
||||||
;; add to the index all file media objects.
|
|
||||||
index (reduce #(assoc %1 (:id %2) (uuid/next)) index fmeds)
|
|
||||||
|
|
||||||
flibs-xf (comp
|
|
||||||
(map #(remap-id % index :file-id))
|
|
||||||
(map #(remap-id % index :library-file-id))
|
|
||||||
(map #(assoc % :synced-at now))
|
|
||||||
(map #(assoc % :created-at now)))
|
|
||||||
|
|
||||||
;; remap all file-library-rel row
|
|
||||||
flibs (sequence flibs-xf flibs)
|
|
||||||
|
|
||||||
fmeds-xf (comp
|
|
||||||
(map #(assoc % :id (get index (:id %))))
|
|
||||||
(map #(assoc % :created-at now))
|
|
||||||
(map #(remap-id % index :file-id)))
|
|
||||||
|
|
||||||
;; remap all file-media-object rows
|
|
||||||
fmeds (sequence fmeds-xf fmeds)
|
|
||||||
|
|
||||||
file (cond-> file
|
|
||||||
(some? project-id)
|
|
||||||
(assoc :project-id project-id)
|
|
||||||
|
|
||||||
(some? name)
|
|
||||||
(assoc :name name)
|
|
||||||
|
|
||||||
(true? reset-shared-flag)
|
|
||||||
(assoc :is-shared false))
|
|
||||||
|
|
||||||
file (-> file
|
|
||||||
(assoc :created-at now)
|
|
||||||
(assoc :modified-at now)
|
|
||||||
(assoc :ignore-sync-until ignore)
|
|
||||||
(update :id #(get index %))
|
|
||||||
(process-file index))]
|
|
||||||
|
|
||||||
(db/insert! conn :file file)
|
|
||||||
(db/insert! conn :file-profile-rel
|
|
||||||
{:file-id (:id file)
|
|
||||||
:profile-id profile-id
|
|
||||||
:is-owner true
|
|
||||||
:is-admin true
|
|
||||||
:can-edit true})
|
|
||||||
|
|
||||||
(doseq [params flibs]
|
|
||||||
(db/insert! conn :file-library-rel params))
|
|
||||||
|
|
||||||
(doseq [params fmeds]
|
|
||||||
(db/insert! conn :file-media-object params))
|
|
||||||
|
|
||||||
file))
|
|
||||||
|
|
||||||
|
|
||||||
;; --- MUTATION: Duplicate File
|
;; --- MUTATION: Duplicate File
|
||||||
|
|
||||||
(declare duplicate-file)
|
(s/def ::duplicate-file ::cmd.mgm/duplicate-file)
|
||||||
|
|
||||||
(s/def ::duplicate-file
|
|
||||||
(s/keys :req-un [::profile-id ::file-id]
|
|
||||||
:opt-un [::name]))
|
|
||||||
|
|
||||||
(sv/defmethod ::duplicate-file
|
(sv/defmethod ::duplicate-file
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
|
{::doc/added "1.2"
|
||||||
|
::doc/deprecated "1.16"}
|
||||||
|
[{:keys [pool] :as cfg} params]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [file (db/get-by-id conn :file file-id)
|
(cmd.mgm/duplicate-file conn params)))
|
||||||
index {file-id (uuid/next)}
|
|
||||||
params (assoc params :index index :file file)]
|
|
||||||
(proj/check-edition-permissions! conn profile-id (:project-id file))
|
|
||||||
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])
|
|
||||||
(-> (duplicate-file conn params {:reset-shared-flag true})
|
|
||||||
(update :data blob/decode)))))
|
|
||||||
|
|
||||||
|
|
||||||
;; --- MUTATION: Duplicate Project
|
;; --- MUTATION: Duplicate Project
|
||||||
|
|
||||||
(declare duplicate-project)
|
(s/def ::duplicate-project ::cmd.mgm/duplicate-project)
|
||||||
|
|
||||||
(s/def ::duplicate-project
|
|
||||||
(s/keys :req-un [::profile-id ::project-id]
|
|
||||||
:opt-un [::name]))
|
|
||||||
|
|
||||||
(sv/defmethod ::duplicate-project
|
(sv/defmethod ::duplicate-project
|
||||||
|
{::doc/added "1.2"
|
||||||
|
::doc/deprecated "1.16"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [project (db/get-by-id conn :project project-id)]
|
(cmd.mgm/duplicate-project conn params)))
|
||||||
(teams/check-edition-permissions! conn profile-id (:team-id project))
|
|
||||||
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])
|
|
||||||
(duplicate-project conn (assoc params :project project)))))
|
|
||||||
|
|
||||||
(defn duplicate-project
|
|
||||||
[conn {:keys [profile-id project name] :as params}]
|
|
||||||
(let [files (db/query conn :file
|
|
||||||
{:project-id (:id project)
|
|
||||||
:deleted-at nil}
|
|
||||||
{:columns [:id]})
|
|
||||||
|
|
||||||
project (cond-> project
|
|
||||||
(string? name)
|
|
||||||
(assoc :name name)
|
|
||||||
|
|
||||||
:always
|
|
||||||
(assoc :id (uuid/next)))]
|
|
||||||
|
|
||||||
;; create the duplicated project and assign the current profile as
|
|
||||||
;; a project owner
|
|
||||||
(create-project conn project)
|
|
||||||
(create-project-role conn {:project-id (:id project)
|
|
||||||
:profile-id profile-id
|
|
||||||
:role :owner})
|
|
||||||
|
|
||||||
;; duplicate all files
|
|
||||||
(let [index (reduce #(assoc %1 (:id %2) (uuid/next)) {} files)
|
|
||||||
params (-> params
|
|
||||||
(dissoc :name)
|
|
||||||
(assoc :project-id (:id project))
|
|
||||||
(assoc :index index))]
|
|
||||||
(doseq [{:keys [id]} files]
|
|
||||||
(let [file (db/get-by-id conn :file id)
|
|
||||||
params (assoc params :file file)
|
|
||||||
opts {:reset-shared-flag false}]
|
|
||||||
(duplicate-file conn params opts))))
|
|
||||||
|
|
||||||
;; return the created project
|
|
||||||
project))
|
|
||||||
|
|
||||||
|
|
||||||
;; --- MUTATION: Move file
|
;; --- MUTATION: Move file
|
||||||
|
|
||||||
(def sql:retrieve-files
|
(s/def ::move-files ::cmd.mgm/move-files)
|
||||||
"select id, project_id from file where id = ANY(?)")
|
|
||||||
|
|
||||||
(def sql:move-files
|
|
||||||
"update file set project_id = ? where id = ANY(?)")
|
|
||||||
|
|
||||||
(def sql:delete-broken-relations
|
|
||||||
"with broken as (
|
|
||||||
(select * from file_library_rel as flr
|
|
||||||
inner join file as f on (flr.file_id = f.id)
|
|
||||||
inner join project as p on (f.project_id = p.id)
|
|
||||||
inner join file as lf on (flr.library_file_id = lf.id)
|
|
||||||
inner join project as lp on (lf.project_id = lp.id)
|
|
||||||
where p.id = ANY(?)
|
|
||||||
and lp.team_id != p.team_id)
|
|
||||||
)
|
|
||||||
delete from file_library_rel as rel
|
|
||||||
using broken as br
|
|
||||||
where rel.file_id = br.file_id
|
|
||||||
and rel.library_file_id = br.library_file_id")
|
|
||||||
|
|
||||||
(s/def ::ids (s/every ::us/uuid :kind set?))
|
|
||||||
(s/def ::move-files
|
|
||||||
(s/keys :req-un [::profile-id ::ids ::project-id]))
|
|
||||||
|
|
||||||
(sv/defmethod ::move-files
|
(sv/defmethod ::move-files
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id ids project-id] :as params}]
|
{::doc/added "1.2"
|
||||||
|
::doc/deprecated "1.16"}
|
||||||
|
[{:keys [pool] :as cfg} params]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [fids (db/create-array conn "uuid" ids)
|
(cmd.mgm/move-files conn params)))
|
||||||
files (db/exec! conn [sql:retrieve-files fids])
|
|
||||||
source (into #{} (map :project-id) files)
|
|
||||||
pids (->> (conj source project-id)
|
|
||||||
(db/create-array conn "uuid"))]
|
|
||||||
|
|
||||||
;; Check if we have permissions on the destination project
|
|
||||||
(proj/check-edition-permissions! conn profile-id project-id)
|
|
||||||
|
|
||||||
;; Check if we have permissions on all source projects
|
|
||||||
(doseq [project-id source]
|
|
||||||
(proj/check-edition-permissions! conn profile-id project-id))
|
|
||||||
|
|
||||||
(when (contains? source project-id)
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :cant-move-to-same-project
|
|
||||||
:hint "Unable to move a file to the same project"))
|
|
||||||
|
|
||||||
;; move all files to the project
|
|
||||||
(db/exec-one! conn [sql:move-files project-id fids])
|
|
||||||
|
|
||||||
;; delete possible broken relations on moved files
|
|
||||||
(db/exec-one! conn [sql:delete-broken-relations pids])
|
|
||||||
|
|
||||||
nil)))
|
|
||||||
|
|
||||||
|
|
||||||
;; --- MUTATION: Move project
|
;; --- MUTATION: Move project
|
||||||
|
|
||||||
(declare move-project)
|
(s/def ::move-project ::cmd.mgm/move-project)
|
||||||
|
|
||||||
(s/def ::move-project
|
|
||||||
(s/keys :req-un [::profile-id ::team-id ::project-id]))
|
|
||||||
|
|
||||||
(sv/defmethod ::move-project
|
(sv/defmethod ::move-project
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id project-id] :as params}]
|
{::doc/added "1.2"
|
||||||
|
::doc/deprecated "1.16"}
|
||||||
|
[{:keys [pool] :as cfg} params]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [project (db/get-by-id conn :project project-id {:columns [:id :team-id]})
|
(cmd.mgm/move-project conn params)))
|
||||||
|
|
||||||
pids (->> (db/query conn :project {:team-id (:team-id project)} {:columns [:id]})
|
|
||||||
(map :id)
|
|
||||||
(db/create-array conn "uuid"))]
|
|
||||||
|
|
||||||
(teams/check-edition-permissions! conn profile-id (:team-id project))
|
|
||||||
(teams/check-edition-permissions! conn profile-id team-id)
|
|
||||||
|
|
||||||
(when (= team-id (:team-id project))
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :cant-move-to-same-team
|
|
||||||
:hint "Unable to move a project to same team"))
|
|
||||||
|
|
||||||
;; move project to the destination team
|
|
||||||
(db/update! conn :project
|
|
||||||
{:team-id team-id}
|
|
||||||
{:id project-id})
|
|
||||||
|
|
||||||
;; delete possible broken relations on moved files
|
|
||||||
(db/exec-one! conn [sql:delete-broken-relations pids])
|
|
||||||
|
|
||||||
nil)))
|
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
;; 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) UXBOX Labs SL
|
|
||||||
|
|
||||||
(ns app.setup.initial-data
|
|
||||||
(:refer-clojure :exclude [load])
|
|
||||||
(:require
|
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.config :as cf]
|
|
||||||
[app.db :as db]
|
|
||||||
[app.rpc.mutations.management :refer [duplicate-file]]
|
|
||||||
[app.rpc.mutations.projects :refer [create-project create-project-role]]
|
|
||||||
[app.rpc.queries.profile :as profile]))
|
|
||||||
|
|
||||||
;; --- DUMP GENERATION
|
|
||||||
|
|
||||||
(def sql:file
|
|
||||||
"select * from file where project_id = ?")
|
|
||||||
|
|
||||||
(def sql:file-library-rel
|
|
||||||
"with file_ids as (select id from file where project_id = ?)
|
|
||||||
select *
|
|
||||||
from file_library_rel
|
|
||||||
where file_id in (select id from file_ids)")
|
|
||||||
|
|
||||||
(def sql:file-media-object
|
|
||||||
"with file_ids as (select id from file where project_id = ?)
|
|
||||||
select *
|
|
||||||
from file_media_object
|
|
||||||
where file_id in (select id from file_ids)")
|
|
||||||
|
|
||||||
(defn dump
|
|
||||||
([system project-id] (dump system project-id nil))
|
|
||||||
([system project-id {:keys [skey project-name]
|
|
||||||
:or {project-name "Penpot Onboarding"}}]
|
|
||||||
(db/with-atomic [conn (:app.db/pool system)]
|
|
||||||
(let [skey (or skey (cf/get :initial-project-skey))
|
|
||||||
files (db/exec! conn [sql:file project-id])
|
|
||||||
flibs (db/exec! conn [sql:file-library-rel project-id])
|
|
||||||
fmeds (db/exec! conn [sql:file-media-object project-id])
|
|
||||||
data {:project-name project-name
|
|
||||||
:files files
|
|
||||||
:flibs flibs
|
|
||||||
:fmeds fmeds}]
|
|
||||||
|
|
||||||
(db/delete! conn :server-prop {:id skey})
|
|
||||||
(db/insert! conn :server-prop
|
|
||||||
{:id skey
|
|
||||||
:preload false
|
|
||||||
:content (db/tjson data)})
|
|
||||||
skey))))
|
|
||||||
|
|
||||||
|
|
||||||
;; --- DUMP LOADING
|
|
||||||
|
|
||||||
(defn- retrieve-data
|
|
||||||
[conn skey]
|
|
||||||
(when-let [row (db/exec-one! conn ["select content from server_prop where id = ?" skey])]
|
|
||||||
(when-let [content (:content row)]
|
|
||||||
(when (db/pgobject? content)
|
|
||||||
(db/decode-transit-pgobject content)))))
|
|
||||||
|
|
||||||
(defn load-initial-project!
|
|
||||||
([conn profile] (load-initial-project! conn profile nil))
|
|
||||||
([conn profile opts]
|
|
||||||
(let [skey (or (:skey opts) (cf/get :initial-project-skey))
|
|
||||||
data (retrieve-data conn skey)]
|
|
||||||
(when data
|
|
||||||
(let [index (reduce #(assoc %1 (:id %2) (uuid/next)) {} (:files data))
|
|
||||||
project {:id (uuid/next)
|
|
||||||
:profile-id (:id profile)
|
|
||||||
:team-id (:default-team-id profile)
|
|
||||||
:name (:project-name data)}]
|
|
||||||
|
|
||||||
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])
|
|
||||||
|
|
||||||
(create-project conn project)
|
|
||||||
(create-project-role conn {:project-id (:id project)
|
|
||||||
:profile-id (:id profile)
|
|
||||||
:role :owner})
|
|
||||||
|
|
||||||
(doseq [file (:files data)]
|
|
||||||
(let [flibs (filterv #(= (:id file) (:file-id %)) (:flibs data))
|
|
||||||
fmeds (filterv #(= (:id file) (:file-id %)) (:fmeds data))
|
|
||||||
|
|
||||||
params {:profile-id (:id profile)
|
|
||||||
:project-id (:id project)
|
|
||||||
:file file
|
|
||||||
:index index
|
|
||||||
:flibs flibs
|
|
||||||
:fmeds fmeds}
|
|
||||||
|
|
||||||
opts {:reset-shared-flag false}]
|
|
||||||
(duplicate-file conn params opts))))))))
|
|
||||||
|
|
||||||
(defn load
|
|
||||||
[system {:keys [email] :as opts}]
|
|
||||||
(db/with-atomic [conn (:app.db/pool system)]
|
|
||||||
(when-let [profile (some->> email
|
|
||||||
(profile/retrieve-profile-data-by-email conn)
|
|
||||||
(profile/populate-additional-data conn))]
|
|
||||||
(load-initial-project! conn profile opts)
|
|
||||||
true)))
|
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
(t/use-fixtures :once th/state-init)
|
(t/use-fixtures :once th/state-init)
|
||||||
(t/use-fixtures :each th/database-reset)
|
(t/use-fixtures :each th/database-reset)
|
||||||
|
|
||||||
|
;; TODO: migrate to commands
|
||||||
|
|
||||||
(t/deftest duplicate-file
|
(t/deftest duplicate-file
|
||||||
(let [storage (-> (:app.storage/storage th/*system*)
|
(let [storage (-> (:app.storage/storage th/*system*)
|
||||||
(configure-storage-backend))
|
(configure-storage-backend))
|
||||||
|
|
|
@ -568,8 +568,7 @@
|
||||||
|
|
||||||
new-name (str name " " (tr "dashboard.copy-suffix"))]
|
new-name (str name " " (tr "dashboard.copy-suffix"))]
|
||||||
|
|
||||||
(->> (rp/mutation! :duplicate-project {:project-id id
|
(->> (rp/command! :duplicate-project {:project-id id :name new-name})
|
||||||
:name new-name})
|
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/map project-duplicated)
|
(rx/map project-duplicated)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
@ -589,8 +588,7 @@
|
||||||
:or {on-success identity
|
:or {on-success identity
|
||||||
on-error rx/throw}} (meta params)]
|
on-error rx/throw}} (meta params)]
|
||||||
|
|
||||||
(->> (rp/mutation! :move-project {:project-id id
|
(->> (rp/command! :move-project {:project-id id :team-id team-id})
|
||||||
:team-id team-id})
|
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
|
||||||
|
@ -771,8 +769,7 @@
|
||||||
|
|
||||||
new-name (str name " " (tr "dashboard.copy-suffix"))]
|
new-name (str name " " (tr "dashboard.copy-suffix"))]
|
||||||
|
|
||||||
(->> (rp/mutation! :duplicate-file {:file-id id
|
(->> (rp/command! :duplicate-file {:file-id id :name new-name})
|
||||||
:name new-name})
|
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/map file-created)
|
(rx/map file-created)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
@ -794,7 +791,7 @@
|
||||||
(let [{:keys [on-success on-error]
|
(let [{:keys [on-success on-error]
|
||||||
:or {on-success identity
|
:or {on-success identity
|
||||||
on-error rx/throw}} (meta params)]
|
on-error rx/throw}} (meta params)]
|
||||||
(->> (rp/mutation! :move-files {:ids ids :project-id project-id})
|
(->> (rp/command! :move-files {:ids ids :project-id project-id})
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue