diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index 73026bae7..ad86fc737 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -217,9 +217,10 @@ (defn get-by-params ([ds table params] (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))] - (when (or (:deleted-at res) (not res)) + (when (and (not uncheked) + (or (:deleted-at res) (not res))) (ex/raise :type :not-found :hint "database object not found")) res))) @@ -261,9 +262,12 @@ (PGpoint. (:x p) (:y p))) (defn create-array - [conn type aobjects] + [conn type objects] (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 [^PGpoint v] diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index e236eac4e..51a75aa63 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -135,6 +135,7 @@ 'app.rpc.mutations.projects 'app.rpc.mutations.viewer 'app.rpc.mutations.teams + 'app.rpc.mutations.management 'app.rpc.mutations.ldap 'app.rpc.mutations.verify-token) (map (partial process-method cfg)) diff --git a/backend/src/app/rpc/mutations/management.clj b/backend/src/app/rpc/mutations/management.clj new file mode 100644 index 000000000..f4a7b1e45 --- /dev/null +++ b/backend/src/app/rpc/mutations/management.clj @@ -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))) diff --git a/backend/tests/app/tests/helpers.clj b/backend/tests/app/tests/helpers.clj index 1804a81b0..748552b27 100644 --- a/backend/tests/app/tests/helpers.clj +++ b/backend/tests/app/tests/helpers.clj @@ -52,8 +52,10 @@ :app.http/server :app.http/router :app.notifications/handler - :app.http.auth/google - :app.http.auth/gitlab + :app.http.oauth/google + :app.http.oauth/gitlab + :app.http.oauth/github + :app.http.oauth/all :app.worker/scheduler :app.worker/worker) (d/deep-merge @@ -160,6 +162,21 @@ :can-edit true}) 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* ([params] (link-file-to-library* *pool* params)) ([conn {:keys [file-id library-id] :as params}] @@ -213,6 +230,19 @@ :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 (defn handle-error diff --git a/backend/tests/app/tests/test_services_management.clj b/backend/tests/app/tests/test_services_management.clj new file mode 100644 index 000000000..bc60d9e4f --- /dev/null +++ b/backend/tests/app/tests/test_services_management.clj @@ -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)))) + + ))) + + +