mirror of
https://github.com/penpot/penpot.git
synced 2025-05-14 01:17:13 +02:00
🎉 Add backend management module with duplicate file and duplicate project
This commit is contained in:
parent
d1261fc841
commit
9945243a23
5 changed files with 790 additions and 6 deletions
|
@ -217,9 +217,10 @@
|
||||||
(defn get-by-params
|
(defn get-by-params
|
||||||
([ds table params]
|
([ds table params]
|
||||||
(get-by-params ds table params nil))
|
(get-by-params ds table params nil))
|
||||||
([ds table params opts]
|
([ds table params {:keys [uncheked] :or {uncheked false} :as opts}]
|
||||||
(let [res (exec-one! ds (sql/select table params opts))]
|
(let [res (exec-one! ds (sql/select table params opts))]
|
||||||
(when (or (:deleted-at res) (not res))
|
(when (and (not uncheked)
|
||||||
|
(or (:deleted-at res) (not res)))
|
||||||
(ex/raise :type :not-found
|
(ex/raise :type :not-found
|
||||||
:hint "database object not found"))
|
:hint "database object not found"))
|
||||||
res)))
|
res)))
|
||||||
|
@ -261,9 +262,12 @@
|
||||||
(PGpoint. (:x p) (:y p)))
|
(PGpoint. (:x p) (:y p)))
|
||||||
|
|
||||||
(defn create-array
|
(defn create-array
|
||||||
[conn type aobjects]
|
[conn type objects]
|
||||||
(let [^PGConnection conn (unwrap conn org.postgresql.PGConnection)]
|
(let [^PGConnection conn (unwrap conn org.postgresql.PGConnection)]
|
||||||
(.createArrayOf conn ^String type aobjects)))
|
(if (coll? objects)
|
||||||
|
(.createArrayOf conn ^String type (into-array Object objects))
|
||||||
|
(.createArrayOf conn ^String type objects))))
|
||||||
|
|
||||||
|
|
||||||
(defn decode-pgpoint
|
(defn decode-pgpoint
|
||||||
[^PGpoint v]
|
[^PGpoint v]
|
||||||
|
|
|
@ -135,6 +135,7 @@
|
||||||
'app.rpc.mutations.projects
|
'app.rpc.mutations.projects
|
||||||
'app.rpc.mutations.viewer
|
'app.rpc.mutations.viewer
|
||||||
'app.rpc.mutations.teams
|
'app.rpc.mutations.teams
|
||||||
|
'app.rpc.mutations.management
|
||||||
'app.rpc.mutations.ldap
|
'app.rpc.mutations.ldap
|
||||||
'app.rpc.mutations.verify-token)
|
'app.rpc.mutations.verify-token)
|
||||||
(map (partial process-method cfg))
|
(map (partial process-method cfg))
|
||||||
|
|
272
backend/src/app/rpc/mutations/management.clj
Normal file
272
backend/src/app/rpc/mutations/management.clj
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
;; 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/.
|
||||||
|
;;
|
||||||
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) 2021 UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.rpc.mutations.management
|
||||||
|
"Move & Duplicate RPC methods for files and projects."
|
||||||
|
(: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.queries.projects :as proj]
|
||||||
|
[app.rpc.queries.teams :as teams]
|
||||||
|
[app.util.blob :as blob]
|
||||||
|
[app.util.services :as sv]
|
||||||
|
[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)
|
||||||
|
|
||||||
|
(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 [;; A function responsible to analize all file data and
|
||||||
|
;; replace the old :component-file reference with the new
|
||||||
|
;; ones, using the provided file-index
|
||||||
|
(relink-components [data]
|
||||||
|
(walk/postwalk (fn [form]
|
||||||
|
(cond-> form
|
||||||
|
(and (map? form) (uuid? (:component-file form)))
|
||||||
|
(update :component-file #(get index % %))))
|
||||||
|
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)
|
||||||
|
(pmg/migrate-data)
|
||||||
|
(update :pages-index relink-components)
|
||||||
|
(update :components relink-components)
|
||||||
|
(update :media relink-media)
|
||||||
|
(d/without-nils)
|
||||||
|
(blob/encode))))))
|
||||||
|
|
||||||
|
(defn- duplicate-file
|
||||||
|
[conn {:keys [profile-id file index project-id]} {:keys [reset-shared-flag] :as opts}]
|
||||||
|
(let [flibs (db/query conn :file-library-rel {:file-id (:id file)})
|
||||||
|
fmeds (db/query conn :file-media-object {:file-id (:id file)})
|
||||||
|
|
||||||
|
;; Remap all file-librar-rel rows to the new file id
|
||||||
|
flibs (map #(remap-id % index :file-id) flibs)
|
||||||
|
|
||||||
|
;; Add to the index all non-local file media objects
|
||||||
|
index (reduce #(assoc %1 (:id %2) (uuid/next))
|
||||||
|
index
|
||||||
|
(remove :is-local fmeds))
|
||||||
|
|
||||||
|
;; Remap all file-media-object rows and assing correct new id
|
||||||
|
;; to each row
|
||||||
|
fmeds (->> fmeds
|
||||||
|
(map #(assoc % :id (or (get index (:id %)) (uuid/next))))
|
||||||
|
(map #(remap-id % index :file-id)))
|
||||||
|
|
||||||
|
file (cond-> file
|
||||||
|
(some? project-id)
|
||||||
|
(assoc :project-id project-id)
|
||||||
|
|
||||||
|
(true? reset-shared-flag)
|
||||||
|
(assoc :is-shared false))
|
||||||
|
|
||||||
|
file (-> file
|
||||||
|
(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
|
||||||
|
|
||||||
|
(declare duplicate-file)
|
||||||
|
|
||||||
|
(s/def ::duplicate-file
|
||||||
|
(s/keys :req-un [::profile-id ::file-id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::duplicate-file
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(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))
|
||||||
|
(-> (duplicate-file conn params {:reset-shared-flag true})
|
||||||
|
(update :data blob/decode)))))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- MUTATION: Duplicate Project
|
||||||
|
|
||||||
|
(declare duplicate-project)
|
||||||
|
|
||||||
|
(s/def ::duplicate-project
|
||||||
|
(s/keys :req-un [::profile-id ::project-id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::duplicate-project
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(let [project (db/get-by-id conn :project project-id)]
|
||||||
|
(teams/check-edition-permissions! conn profile-id (:team-id project))
|
||||||
|
(duplicate-project conn (assoc params :project project)))))
|
||||||
|
|
||||||
|
(defn duplicate-project
|
||||||
|
[conn {:keys [profile-id project] :as params}]
|
||||||
|
(let [files (db/query conn :file
|
||||||
|
{:project-id (:id project)}
|
||||||
|
{:columns [:id]})
|
||||||
|
|
||||||
|
index (reduce #(assoc %1 (:id %2) (uuid/next)) {} files)
|
||||||
|
project (assoc project :id (uuid/next))
|
||||||
|
params (assoc params
|
||||||
|
:project-id (:id project)
|
||||||
|
:index index)]
|
||||||
|
|
||||||
|
(db/insert! conn :project project)
|
||||||
|
(db/insert! conn :project-profile-rel {:project-id (:id project)
|
||||||
|
:profile-id profile-id
|
||||||
|
:is-owner true
|
||||||
|
:is-admin true
|
||||||
|
:can-edit true})
|
||||||
|
(doseq [{:keys [id]} files]
|
||||||
|
(let [file (db/get-by-id conn :file id)
|
||||||
|
params (assoc params :file file)]
|
||||||
|
(duplicate-file conn params {:reset-shared-flag false
|
||||||
|
:remap-libraries true})))
|
||||||
|
project))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- MUTATION: Move file
|
||||||
|
|
||||||
|
(declare sql:retrieve-files)
|
||||||
|
(declare sql:move-files)
|
||||||
|
(declare sql:delete-broken-relations)
|
||||||
|
|
||||||
|
(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} {:keys [profile-id ids project-id] :as params}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(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 posible broken relations on moved files
|
||||||
|
(db/exec-one! conn [sql:delete-broken-relations pids])
|
||||||
|
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
(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")
|
||||||
|
|
||||||
|
|
||||||
|
;; --- MUTATION: Move project
|
||||||
|
|
||||||
|
(declare move-project)
|
||||||
|
|
||||||
|
(s/def ::move-project
|
||||||
|
(s/keys :req-un [::profile-id ::team-id ::project-id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::move-project
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id team-id project-id] :as params}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(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 posible broken relations on moved files
|
||||||
|
(db/exec-one! conn [sql:delete-broken-relations pids])
|
||||||
|
|
||||||
|
nil)))
|
|
@ -52,8 +52,10 @@
|
||||||
:app.http/server
|
:app.http/server
|
||||||
:app.http/router
|
:app.http/router
|
||||||
:app.notifications/handler
|
:app.notifications/handler
|
||||||
:app.http.auth/google
|
:app.http.oauth/google
|
||||||
:app.http.auth/gitlab
|
:app.http.oauth/gitlab
|
||||||
|
:app.http.oauth/github
|
||||||
|
:app.http.oauth/all
|
||||||
:app.worker/scheduler
|
:app.worker/scheduler
|
||||||
:app.worker/worker)
|
:app.worker/worker)
|
||||||
(d/deep-merge
|
(d/deep-merge
|
||||||
|
@ -160,6 +162,21 @@
|
||||||
:can-edit true})
|
:can-edit true})
|
||||||
team)))
|
team)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn create-file-media-object*
|
||||||
|
([params] (create-file-media-object* *pool* params))
|
||||||
|
([conn {:keys [name width height mtype file-id is-local media-id]
|
||||||
|
:or {name "sample" width 100 height 100 mtype "image/svg+xml" is-local true}}]
|
||||||
|
(db/insert! conn :file-media-object
|
||||||
|
{:id (uuid/next)
|
||||||
|
:file-id file-id
|
||||||
|
:is-local is-local
|
||||||
|
:name name
|
||||||
|
:media-id media-id
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:mtype mtype})))
|
||||||
|
|
||||||
(defn link-file-to-library*
|
(defn link-file-to-library*
|
||||||
([params] (link-file-to-library* *pool* params))
|
([params] (link-file-to-library* *pool* params))
|
||||||
([conn {:keys [file-id library-id] :as params}]
|
([conn {:keys [file-id library-id] :as params}]
|
||||||
|
@ -213,6 +230,19 @@
|
||||||
:can-edit can-edit})))
|
:can-edit can-edit})))
|
||||||
|
|
||||||
|
|
||||||
|
(defn update-file*
|
||||||
|
([params] (update-file* *pool* params))
|
||||||
|
([conn {:keys [file-id changes session-id profile-id revn]
|
||||||
|
:or {session-id (uuid/next) revn 0}}]
|
||||||
|
(let [file (db/get-by-id conn :file file-id)
|
||||||
|
msgbus (:app.msgbus/msgbus *system*)]
|
||||||
|
(#'files/update-file {:conn conn :msgbus msgbus}
|
||||||
|
{:file file
|
||||||
|
:revn revn
|
||||||
|
:changes changes
|
||||||
|
:session-id session-id
|
||||||
|
:profile-id profile-id}))))
|
||||||
|
|
||||||
;; --- RPC HELPERS
|
;; --- RPC HELPERS
|
||||||
|
|
||||||
(defn handle-error
|
(defn handle-error
|
||||||
|
|
477
backend/tests/app/tests/test_services_management.clj
Normal file
477
backend/tests/app/tests/test_services_management.clj
Normal file
|
@ -0,0 +1,477 @@
|
||||||
|
;; 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/.
|
||||||
|
;;
|
||||||
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) 2021 UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.tests.test-services-management
|
||||||
|
(:require
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[app.db :as db]
|
||||||
|
[app.http :as http]
|
||||||
|
[app.storage :as sto]
|
||||||
|
[app.tests.helpers :as th]
|
||||||
|
[clojure.test :as t]
|
||||||
|
[buddy.core.bytes :as b]
|
||||||
|
[datoteka.core :as fs]))
|
||||||
|
|
||||||
|
(t/use-fixtures :once th/state-init)
|
||||||
|
(t/use-fixtures :each th/database-reset)
|
||||||
|
|
||||||
|
(t/deftest duplicate-file
|
||||||
|
(let [storage (:app.storage/storage th/*system*)
|
||||||
|
sobject (sto/put-object storage {:content (sto/content "content")
|
||||||
|
:content-type "text/plain"
|
||||||
|
:other "data"})
|
||||||
|
profile (th/create-profile* 1 {:is-active true})
|
||||||
|
project (th/create-project* 1 {:team-id (:default-team-id profile)
|
||||||
|
:profile-id (:id profile)})
|
||||||
|
file1 (th/create-file* 1 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project)})
|
||||||
|
file2 (th/create-file* 2 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project)
|
||||||
|
:is-shared true})
|
||||||
|
|
||||||
|
libl (th/link-file-to-library* {:file-id (:id file1)
|
||||||
|
:library-id (:id file2)})
|
||||||
|
|
||||||
|
mobj (th/create-file-media-object* {:file-id (:id file1)
|
||||||
|
:is-local false
|
||||||
|
:media-id (:id sobject)})]
|
||||||
|
(th/update-file*
|
||||||
|
{:file-id (:id file1)
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:changes [{:type :add-media
|
||||||
|
:object (select-keys mobj [:id :width :height :mtype :name])}]})
|
||||||
|
|
||||||
|
(let [data {::th/type :duplicate-file
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:file-id (:id file1)}
|
||||||
|
out (th/mutation! data)]
|
||||||
|
|
||||||
|
;; (th/print-result! out)
|
||||||
|
|
||||||
|
;; Check tha tresult is correct
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(let [result (:result out)]
|
||||||
|
|
||||||
|
;; Check that the returned result is a file but has different
|
||||||
|
;; and different name.
|
||||||
|
(t/is (= (:name file1) (:name result)))
|
||||||
|
(t/is (not= (:id file1) (:id result)))
|
||||||
|
|
||||||
|
;; Check that the new file has a correct file library relation
|
||||||
|
(let [[item :as rows] (db/query th/*pool* :file-library-rel {:file-id (:id result)})]
|
||||||
|
(t/is (= 1 (count rows)))
|
||||||
|
(t/is (= (:id file2) (:library-file-id item))))
|
||||||
|
|
||||||
|
;; Check that the new file has a correct file media objects
|
||||||
|
(let [[item :as rows] (db/query th/*pool* :file-media-object {:file-id (:id result)})]
|
||||||
|
(t/is (= 1 (count rows)))
|
||||||
|
|
||||||
|
;; Checj that bot items have different ids
|
||||||
|
(t/is (not= (:id item) (:id mobj)))
|
||||||
|
|
||||||
|
;; check that both file-media-objects points to the same storage object.
|
||||||
|
(t/is (= (:media-id item) (:media-id mobj)))
|
||||||
|
(t/is (= (:media-id item) (:id sobject)))
|
||||||
|
|
||||||
|
;; Check if media correctly contains the new file-media-object id
|
||||||
|
(t/is (contains? (get-in result [:data :media]) (:id item)))
|
||||||
|
|
||||||
|
;; And does not contains the old one
|
||||||
|
(t/is (not (contains? (get-in result [:data :media]) (:id mobj)))))
|
||||||
|
|
||||||
|
;; Check the total number of files
|
||||||
|
(let [rows (db/query th/*pool* :file {:project-id (:id project)})]
|
||||||
|
(t/is (= 3 (count rows))))
|
||||||
|
|
||||||
|
))))
|
||||||
|
|
||||||
|
(t/deftest duplicate-project
|
||||||
|
(let [storage (:app.storage/storage th/*system*)
|
||||||
|
sobject (sto/put-object storage {:content (sto/content "content")
|
||||||
|
:content-type "text/plain"
|
||||||
|
:other "data"})
|
||||||
|
profile (th/create-profile* 1 {:is-active true})
|
||||||
|
project (th/create-project* 1 {:team-id (:default-team-id profile)
|
||||||
|
:profile-id (:id profile)})
|
||||||
|
file1 (th/create-file* 1 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project)})
|
||||||
|
file2 (th/create-file* 2 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project)
|
||||||
|
:is-shared true})
|
||||||
|
|
||||||
|
libl (th/link-file-to-library* {:file-id (:id file1)
|
||||||
|
:library-id (:id file2)})
|
||||||
|
mobj (th/create-file-media-object* {:file-id (:id file1)
|
||||||
|
:is-local false
|
||||||
|
:media-id (:id sobject)})]
|
||||||
|
|
||||||
|
(th/update-file*
|
||||||
|
{:file-id (:id file1)
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:changes [{:type :add-media
|
||||||
|
:object (select-keys mobj [:id :width :height :mtype :name])}]})
|
||||||
|
|
||||||
|
|
||||||
|
(let [data {::th/type :duplicate-project
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:project-id (:id project)}
|
||||||
|
out (th/mutation! data)]
|
||||||
|
|
||||||
|
;; Check tha tresult is correct
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
|
||||||
|
(let [result (:result out)]
|
||||||
|
;; Check that they are the same project but different ids
|
||||||
|
(t/is (= (:name project) (:name result)))
|
||||||
|
(t/is (not= (:id project) (:id result)))
|
||||||
|
|
||||||
|
;; Check the total number of projects (previously is 2, now is 3)
|
||||||
|
(let [rows (db/query th/*pool* :project {:team-id (:default-team-id profile)})]
|
||||||
|
(t/is (= 3 (count rows))))
|
||||||
|
|
||||||
|
;; Check that the new project has the same files
|
||||||
|
(let [p1-files (db/query th/*pool* :file
|
||||||
|
{:project-id (:id project)}
|
||||||
|
{:order-by [:name]})
|
||||||
|
p2-files (db/query th/*pool* :file
|
||||||
|
{:project-id (:id result)}
|
||||||
|
{:order-by [:name]})]
|
||||||
|
(t/is (= (count p1-files)
|
||||||
|
(count p2-files)))
|
||||||
|
|
||||||
|
;; check that the both files are equivalent
|
||||||
|
(doseq [[fa fb] (map vector p1-files p2-files)]
|
||||||
|
(t/is (not= (:id fa) (:id fb)))
|
||||||
|
(t/is (= (:name fa) (:name fb)))
|
||||||
|
|
||||||
|
(when (= (:id fa) (:id file1))
|
||||||
|
(t/is (false? (b/equals? (:data fa)
|
||||||
|
(:data fb)))))
|
||||||
|
|
||||||
|
(when (= (:id fa) (:id file2))
|
||||||
|
(t/is (true? (b/equals? (:data fa)
|
||||||
|
(:data fb))))))
|
||||||
|
|
||||||
|
)))))
|
||||||
|
|
||||||
|
(t/deftest move-file-on-same-team
|
||||||
|
(let [profile (th/create-profile* 1 {:is-active true})
|
||||||
|
team (th/create-team* 1 {:profile-id (:id profile)})
|
||||||
|
|
||||||
|
project1 (th/create-project* 1 {:team-id (:default-team-id profile)
|
||||||
|
:profile-id (:id profile)})
|
||||||
|
|
||||||
|
project2 (th/create-project* 2 {:team-id (:default-team-id profile)
|
||||||
|
:profile-id (:id profile)})
|
||||||
|
|
||||||
|
file1 (th/create-file* 1 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project1)})
|
||||||
|
file2 (th/create-file* 2 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project1)
|
||||||
|
:is-shared true})]
|
||||||
|
|
||||||
|
(th/link-file-to-library* {:file-id (:id file1)
|
||||||
|
:library-id (:id file2)})
|
||||||
|
|
||||||
|
;; Try to move to same project
|
||||||
|
(let [data {::th/type :move-files
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:project-id (:id project1)
|
||||||
|
:ids #{(:id file1)}}
|
||||||
|
|
||||||
|
out (th/mutation! data)
|
||||||
|
error (:error out)]
|
||||||
|
(t/is (th/ex-info? error))
|
||||||
|
(t/is (th/ex-of-type? error :validation))
|
||||||
|
(t/is (th/ex-of-code? error :cant-move-to-same-project)))
|
||||||
|
|
||||||
|
;; initially project1 should have 2 files
|
||||||
|
(let [rows (db/query th/*pool* :file {:project-id (:id project1)})]
|
||||||
|
(t/is (= 2 (count rows))))
|
||||||
|
|
||||||
|
;; initially project2 should be empty
|
||||||
|
(let [rows (db/query th/*pool* :file {:project-id (:id project2)})]
|
||||||
|
(t/is (= 0 (count rows))))
|
||||||
|
|
||||||
|
;; move a file1 to project2 (in the same team)
|
||||||
|
(let [data {::th/type :move-files
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:project-id (:id project2)
|
||||||
|
:ids #{(:id file1)}}
|
||||||
|
|
||||||
|
out (th/mutation! data)]
|
||||||
|
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(t/is (nil? (:result out)))
|
||||||
|
|
||||||
|
;; project1 now should contain 1 file
|
||||||
|
(let [rows (db/query th/*pool* :file {:project-id (:id project1)})]
|
||||||
|
(t/is (= 1 (count rows))))
|
||||||
|
|
||||||
|
;; project2 now should contain 1 file
|
||||||
|
(let [rows (db/query th/*pool* :file {:project-id (:id project2)})]
|
||||||
|
(t/is (= 1 (count rows))))
|
||||||
|
|
||||||
|
;; file1 should be still linked to file2
|
||||||
|
(let [[item :as rows] (db/query th/*pool* :file-library-rel {:file-id (:id file1)})]
|
||||||
|
(t/is (= 1 (count rows)))
|
||||||
|
(t/is (= (:file-id item) (:id file1)))
|
||||||
|
(t/is (= (:library-file-id item) (:id file2))))
|
||||||
|
|
||||||
|
;; should be no libraries on file2
|
||||||
|
(let [rows (db/query th/*pool* :file-library-rel {:file-id (:id file2)})]
|
||||||
|
(t/is (= 0 (count rows))))
|
||||||
|
)))
|
||||||
|
|
||||||
|
|
||||||
|
;; TODO: move a library to other team
|
||||||
|
(t/deftest move-file-to-other-team
|
||||||
|
(let [profile (th/create-profile* 1 {:is-active true})
|
||||||
|
team (th/create-team* 1 {:profile-id (:id profile)})
|
||||||
|
|
||||||
|
project1 (th/create-project* 1 {:team-id (:default-team-id profile)
|
||||||
|
:profile-id (:id profile)})
|
||||||
|
|
||||||
|
project2 (th/create-project* 2 {:team-id (:id team)
|
||||||
|
:profile-id (:id profile)})
|
||||||
|
|
||||||
|
file1 (th/create-file* 1 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project1)})
|
||||||
|
file2 (th/create-file* 2 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project1)
|
||||||
|
:is-shared true})
|
||||||
|
file3 (th/create-file* 3 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project1)
|
||||||
|
:is-shared true})]
|
||||||
|
|
||||||
|
(th/link-file-to-library* {:file-id (:id file1)
|
||||||
|
:library-id (:id file2)})
|
||||||
|
|
||||||
|
(th/link-file-to-library* {:file-id (:id file2)
|
||||||
|
:library-id (:id file3)})
|
||||||
|
|
||||||
|
;; --- initial data checks
|
||||||
|
|
||||||
|
;; the project1 should have 3 files
|
||||||
|
(let [rows (db/query th/*pool* :file {:project-id (:id project1)})]
|
||||||
|
(t/is (= 3 (count rows))))
|
||||||
|
|
||||||
|
;; should be no files on project2
|
||||||
|
(let [rows (db/query th/*pool* :file {:project-id (:id project2)})]
|
||||||
|
(t/is (= 0 (count rows))))
|
||||||
|
|
||||||
|
;; the file1 should be linked to file2
|
||||||
|
(let [[item :as rows] (db/query th/*pool* :file-library-rel {:file-id (:id file1)})]
|
||||||
|
(t/is (= 1 (count rows)))
|
||||||
|
(t/is (= (:file-id item) (:id file1)))
|
||||||
|
(t/is (= (:library-file-id item) (:id file2))))
|
||||||
|
|
||||||
|
;; the file2 should be linked to file3
|
||||||
|
(let [[item :as rows] (db/query th/*pool* :file-library-rel {:file-id (:id file2)})]
|
||||||
|
(t/is (= 1 (count rows)))
|
||||||
|
(t/is (= (:file-id item) (:id file2)))
|
||||||
|
(t/is (= (:library-file-id item) (:id file3))))
|
||||||
|
|
||||||
|
;; should be no libraries on file3
|
||||||
|
(let [rows (db/query th/*pool* :file-library-rel {:file-id (:id file3)})]
|
||||||
|
(t/is (= 0 (count rows))))
|
||||||
|
|
||||||
|
;; move to other project in other team
|
||||||
|
(let [data {::th/type :move-files
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:project-id (:id project2)
|
||||||
|
:ids #{(:id file1)}}
|
||||||
|
out (th/mutation! data)]
|
||||||
|
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(t/is (nil? (:result out)))
|
||||||
|
|
||||||
|
;; project1 now should have 2 file
|
||||||
|
(let [[item1 item2 :as rows] (db/query th/*pool* :file {:project-id (:id project1)}
|
||||||
|
{:order-by [:created-at]})]
|
||||||
|
;; (clojure.pprint/pprint rows)
|
||||||
|
(t/is (= 2 (count rows)))
|
||||||
|
(t/is (= (:id item1) (:id file2))))
|
||||||
|
|
||||||
|
;; project2 now should have 1 file
|
||||||
|
(let [[item :as rows] (db/query th/*pool* :file {:project-id (:id project2)})]
|
||||||
|
(t/is (= 1 (count rows)))
|
||||||
|
(t/is (= (:id item) (:id file1))))
|
||||||
|
|
||||||
|
;; the moved file1 should not have any link to libraries
|
||||||
|
(let [rows (db/query th/*pool* :file-library-rel {:file-id (:id file1)})]
|
||||||
|
(t/is (zero? (count rows))))
|
||||||
|
|
||||||
|
;; the file2 should still be linked to file3
|
||||||
|
(let [[item :as rows] (db/query th/*pool* :file-library-rel {:file-id (:id file2)})]
|
||||||
|
(t/is (= 1 (count rows)))
|
||||||
|
(t/is (= (:file-id item) (:id file2)))
|
||||||
|
(t/is (= (:library-file-id item) (:id file3))))
|
||||||
|
)))
|
||||||
|
|
||||||
|
|
||||||
|
(t/deftest move-library-to-other-team
|
||||||
|
(let [profile (th/create-profile* 1 {:is-active true})
|
||||||
|
team (th/create-team* 1 {:profile-id (:id profile)})
|
||||||
|
|
||||||
|
project1 (th/create-project* 1 {:team-id (:default-team-id profile)
|
||||||
|
:profile-id (:id profile)})
|
||||||
|
|
||||||
|
project2 (th/create-project* 2 {:team-id (:id team)
|
||||||
|
:profile-id (:id profile)})
|
||||||
|
|
||||||
|
file1 (th/create-file* 1 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project1)})
|
||||||
|
file2 (th/create-file* 2 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project1)
|
||||||
|
:is-shared true})]
|
||||||
|
|
||||||
|
(th/link-file-to-library* {:file-id (:id file1)
|
||||||
|
:library-id (:id file2)})
|
||||||
|
|
||||||
|
;; --- initial data checks
|
||||||
|
|
||||||
|
;; the project1 should have 2 files
|
||||||
|
(let [rows (db/query th/*pool* :file {:project-id (:id project1)})]
|
||||||
|
(t/is (= 2 (count rows))))
|
||||||
|
|
||||||
|
;; should be no files on project2
|
||||||
|
(let [rows (db/query th/*pool* :file {:project-id (:id project2)})]
|
||||||
|
(t/is (= 0 (count rows))))
|
||||||
|
|
||||||
|
;; the file1 should be linked to file2
|
||||||
|
(let [[item :as rows] (db/query th/*pool* :file-library-rel {:file-id (:id file1)})]
|
||||||
|
(t/is (= 1 (count rows)))
|
||||||
|
(t/is (= (:file-id item) (:id file1)))
|
||||||
|
(t/is (= (:library-file-id item) (:id file2))))
|
||||||
|
|
||||||
|
;; should be no libraries on file2
|
||||||
|
(let [rows (db/query th/*pool* :file-library-rel {:file-id (:id file2)})]
|
||||||
|
(t/is (= 0 (count rows))))
|
||||||
|
|
||||||
|
;; move the library to other project
|
||||||
|
(let [data {::th/type :move-files
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:project-id (:id project2)
|
||||||
|
:ids #{(:id file2)}}
|
||||||
|
out (th/mutation! data)]
|
||||||
|
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(t/is (nil? (:result out)))
|
||||||
|
|
||||||
|
;; project1 now should have 1 file
|
||||||
|
(let [[item :as rows] (db/query th/*pool* :file {:project-id (:id project1)}
|
||||||
|
{:order-by [:created-at]})]
|
||||||
|
(t/is (= 1 (count rows)))
|
||||||
|
(t/is (= (:id item) (:id file1))))
|
||||||
|
|
||||||
|
;; project2 now should have 1 file
|
||||||
|
(let [[item :as rows] (db/query th/*pool* :file {:project-id (:id project2)})]
|
||||||
|
(t/is (= 1 (count rows)))
|
||||||
|
(t/is (= (:id item) (:id file2))))
|
||||||
|
|
||||||
|
;; the file1 should not have any link to libraries
|
||||||
|
(let [rows (db/query th/*pool* :file-library-rel {:file-id (:id file1)})]
|
||||||
|
(t/is (zero? (count rows))))
|
||||||
|
|
||||||
|
;; the file2 should not have any link to libraries
|
||||||
|
(let [rows (db/query th/*pool* :file-library-rel {:file-id (:id file2)})]
|
||||||
|
(t/is (zero? (count rows))))
|
||||||
|
|
||||||
|
)))
|
||||||
|
|
||||||
|
(t/deftest move-project
|
||||||
|
(let [profile (th/create-profile* 1 {:is-active true})
|
||||||
|
team (th/create-team* 1 {:profile-id (:id profile)})
|
||||||
|
|
||||||
|
project1 (th/create-project* 1 {:team-id (:default-team-id profile)
|
||||||
|
:profile-id (:id profile)})
|
||||||
|
|
||||||
|
project2 (th/create-project* 2 {:team-id (:default-team-id profile)
|
||||||
|
:profile-id (:id profile)})
|
||||||
|
|
||||||
|
file1 (th/create-file* 1 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project1)})
|
||||||
|
|
||||||
|
file2 (th/create-file* 2 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project1)
|
||||||
|
:is-shared true})
|
||||||
|
|
||||||
|
file3 (th/create-file* 3 {:profile-id (:id profile)
|
||||||
|
:project-id (:id project2)
|
||||||
|
:is-shared true})]
|
||||||
|
|
||||||
|
(th/link-file-to-library* {:file-id (:id file1)
|
||||||
|
:library-id (:id file2)})
|
||||||
|
|
||||||
|
(th/link-file-to-library* {:file-id (:id file1)
|
||||||
|
:library-id (:id file3)})
|
||||||
|
|
||||||
|
;; --- initial data checks
|
||||||
|
|
||||||
|
;; the project1 should have 2 files
|
||||||
|
(let [rows (db/query th/*pool* :file {:project-id (:id project1)})]
|
||||||
|
(t/is (= 2 (count rows))))
|
||||||
|
|
||||||
|
;; the project2 should have 1 file
|
||||||
|
(let [rows (db/query th/*pool* :file {:project-id (:id project2)})]
|
||||||
|
(t/is (= 1 (count rows))))
|
||||||
|
|
||||||
|
;; the file1 should be linked to file2 and file3
|
||||||
|
(let [[item1 item2 :as rows] (db/query th/*pool* :file-library-rel {:file-id (:id file1)}
|
||||||
|
{:order-by [:created-at]})]
|
||||||
|
(t/is (= 2 (count rows)))
|
||||||
|
(t/is (= (:file-id item1) (:id file1)))
|
||||||
|
(t/is (= (:library-file-id item1) (:id file2)))
|
||||||
|
(t/is (= (:file-id item2) (:id file1)))
|
||||||
|
(t/is (= (:library-file-id item2) (:id file3))))
|
||||||
|
|
||||||
|
;; the file2 should not be linked to any file
|
||||||
|
(let [[rows] (db/query th/*pool* :file-library-rel {:file-id (:id file2)})]
|
||||||
|
(t/is (= 0 (count rows))))
|
||||||
|
|
||||||
|
;; the file3 should not be linked to any file
|
||||||
|
(let [[rows] (db/query th/*pool* :file-library-rel {:file-id (:id file3)})]
|
||||||
|
(t/is (= 0 (count rows))))
|
||||||
|
|
||||||
|
;; move project1 to other team
|
||||||
|
;; TODO: correct team change of project
|
||||||
|
(let [data {::th/type :move-project
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:project-id (:id project1)
|
||||||
|
:team-id (:id team)}
|
||||||
|
out (th/mutation! data)]
|
||||||
|
|
||||||
|
(t/is (nil? (:error out)))
|
||||||
|
(t/is (nil? (:result out)))
|
||||||
|
|
||||||
|
;; project1 now should still have 2 files
|
||||||
|
(let [[item1 item2 :as rows] (db/query th/*pool* :file {:project-id (:id project1)}
|
||||||
|
{:order-by [:created-at]})]
|
||||||
|
;; (clojure.pprint/pprint rows)
|
||||||
|
(t/is (= 2 (count rows)))
|
||||||
|
(t/is (= (:id item1) (:id file1)))
|
||||||
|
(t/is (= (:id item2) (:id file2))))
|
||||||
|
|
||||||
|
;; project2 now should still have 1 file
|
||||||
|
(let [[item :as rows] (db/query th/*pool* :file {:project-id (:id project2)})]
|
||||||
|
(t/is (= 1 (count rows)))
|
||||||
|
(t/is (= (:id item) (:id file3))))
|
||||||
|
|
||||||
|
;; the file1 should be linked to file2 but not file3
|
||||||
|
(let [[item1 :as rows] (db/query th/*pool* :file-library-rel {:file-id (:id file1)}
|
||||||
|
{:order-by [:created-at]})]
|
||||||
|
(t/is (= 1 (count rows)))
|
||||||
|
(t/is (= (:file-id item1) (:id file1)))
|
||||||
|
(t/is (= (:library-file-id item1) (:id file2))))
|
||||||
|
|
||||||
|
)))
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue