Merge pull request #694 from penpot/dashboard-files

Dashboard files
This commit is contained in:
Andrey Antukh 2021-03-04 17:11:41 +01:00 committed by GitHub
commit 2ca8ff4db1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1766 additions and 376 deletions

View file

@ -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]

View file

@ -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))

View file

@ -16,6 +16,7 @@
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.db :as db]
[app.rpc.permissions :as perms]
[app.rpc.queries.files :as files]
[app.rpc.queries.projects :as proj]
[app.tasks :as tasks]
@ -47,14 +48,13 @@
(proj/check-edition-permissions! conn profile-id project-id)
(create-file conn params)))
(defn- create-file-profile
[conn {:keys [profile-id file-id] :as params}]
(db/insert! conn :file-profile-rel
{:profile-id profile-id
:file-id file-id
:is-owner true
:is-admin true
:can-edit true}))
(defn create-file-role
[conn {:keys [file-id profile-id role]}]
(let [params {:file-id file-id
:profile-id profile-id}]
(->> (perms/assign-role-flags params role)
(db/insert! conn :file-profile-rel))))
(defn create-file
[conn {:keys [id name project-id is-shared]
@ -68,8 +68,8 @@
:name name
:is-shared is-shared
:data (blob/encode data)})]
(->> (assoc params :file-id id)
(create-file-profile conn))
(->> (assoc params :file-id id :role :owner)
(create-file-role conn))
(assoc file :data data)))

View file

@ -0,0 +1,310 @@
;; 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.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]))
(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 Components
(and (map? form)
(uuid? (:component-file form)))
(update :component-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 analize 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))))))
(defn duplicate-file
[conn {:keys [profile-id file index project-id name]} {: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)})
;; 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
(declare duplicate-file)
(s/def ::duplicate-file
(s/keys :req-un [::profile-id ::file-id]
:opt-un [::name]))
(sv/defmethod ::duplicate-file
[{:keys [pool] :as cfg} {:keys [profile-id file-id name] :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]
:opt-un [::name]))
(sv/defmethod ::duplicate-project
[{:keys [pool] :as cfg} {:keys [profile-id project-id name] :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 name] :as params}]
(let [files (db/query conn :file
{:project-id (:id project)}
{: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
(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")
(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)))
;; --- 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)))

View file

@ -196,21 +196,25 @@
(defn create-profile-relations
[conn profile]
(let [team (teams/create-team conn {:profile-id (:id profile)
:name "Default"
:default? true})
proj (projects/create-project conn {:profile-id (:id profile)
:team-id (:id team)
:name "Drafts"
:default? true})]
(teams/create-team-profile conn {:team-id (:id team)
:profile-id (:id profile)})
(projects/create-project-profile conn {:project-id (:id proj)
:profile-id (:id profile)})
(let [team (teams/create-team conn {:profile-id (:id profile)
:name "Default"
:is-default true})
project (projects/create-project conn {:profile-id (:id profile)
:team-id (:id team)
:name "Drafts"
:is-default true})
params {:team-id (:id team)
:profile-id (:id profile)
:project-id (:id project)
:role :owner}]
(merge (profile/strip-private-attrs profile)
{:default-team-id (:id team)
:default-project-id (:id proj)})))
(teams/create-team-role conn params)
(projects/create-project-role conn params)
(-> profile
(profile/strip-private-attrs)
(assoc :default-team-id (:id team))
(assoc :default-project-id (:id project)))))
;; --- Mutation: Login

View file

@ -13,6 +13,7 @@
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.db :as db]
[app.rpc.permissions :as perms]
[app.rpc.queries.projects :as proj]
[app.rpc.queries.teams :as teams]
[app.tasks :as tasks]
@ -30,7 +31,7 @@
;; --- Mutation: Create Project
(declare create-project)
(declare create-project-profile)
(declare create-project-role)
(declare create-team-project-profile)
(s/def ::team-id ::us/uuid)
@ -43,30 +44,31 @@
(db/with-atomic [conn pool]
(teams/check-edition-permissions! conn profile-id team-id)
(let [project (create-project conn params)
params (assoc params :project-id (:id project))]
(create-project-profile conn params)
params (assoc params
:project-id (:id project)
:role :owner)]
(create-project-role conn params)
(create-team-project-profile conn params)
(assoc project :is-pinned true))))
(defn create-project
[conn {:keys [id team-id name default?] :as params}]
(let [id (or id (uuid/next))
default? (if (boolean? default?) default? false)]
[conn {:keys [id team-id name is-default] :as params}]
(let [id (or id (uuid/next))
is-default (if (boolean? is-default) is-default false)]
(db/insert! conn :project
{:id id
:team-id team-id
:name name
:is-default default?})))
:team-id team-id
:is-default is-default})))
(defn create-project-profile
[conn {:keys [project-id profile-id] :as params}]
(db/insert! conn :project-profile-rel
{:project-id project-id
:profile-id profile-id
:is-owner true
:is-admin true
:can-edit true}))
(defn create-project-role
[conn {:keys [project-id profile-id role]}]
(let [params {:project-id project-id
:profile-id profile-id}]
(->> (perms/assign-role-flags params role)
(db/insert! conn :project-profile-rel))))
;; TODO: pending to be refactored
(defn create-team-project-profile
[conn {:keys [team-id project-id profile-id] :as params}]
(db/insert! conn :team-project-profile-rel

View file

@ -18,6 +18,7 @@
[app.emails :as emails]
[app.media :as media]
[app.rpc.mutations.projects :as projects]
[app.rpc.permissions :as perms]
[app.rpc.queries.profile :as profile]
[app.rpc.queries.teams :as teams]
[app.storage :as sto]
@ -36,7 +37,7 @@
;; --- Mutation: Create Team
(declare create-team)
(declare create-team-profile)
(declare create-team-role)
(declare create-team-default-project)
(s/def ::create-team
@ -47,37 +48,39 @@
[{:keys [pool] :as cfg} params]
(db/with-atomic [conn pool]
(let [team (create-team conn params)
params (assoc params :team-id (:id team))]
(create-team-profile conn params)
params (assoc params
:team-id (:id team)
:role :owner)]
(create-team-role conn params)
(create-team-default-project conn params)
team)))
(defn create-team
[conn {:keys [id name default?] :as params}]
(let [id (or id (uuid/next))
default? (if (boolean? default?) default? false)]
[conn {:keys [id name is-default] :as params}]
(let [id (or id (uuid/next))
is-default (if (boolean? is-default) is-default false)]
(db/insert! conn :team
{:id id
:name name
:is-default default?})))
:is-default is-default})))
(defn create-team-profile
[conn {:keys [team-id profile-id] :as params}]
(db/insert! conn :team-profile-rel
{:team-id team-id
:profile-id profile-id
:is-owner true
:is-admin true
:can-edit true}))
(defn create-team-role
[conn {:keys [team-id profile-id role] :as params}]
(let [params {:team-id team-id
:profile-id profile-id}]
(->> (perms/assign-role-flags params role)
(db/insert! conn :team-profile-rel))))
(defn create-team-default-project
[conn {:keys [team-id profile-id] :as params}]
(let [proj (projects/create-project conn {:team-id team-id
:name "Drafts"
:default? true})]
(projects/create-project-profile conn {:project-id (:id proj)
:profile-id profile-id})))
(let [project {:id (uuid/next)
:team-id team-id
:name "Drafts"
:is-default true}]
(projects/create-project conn project)
(projects/create-project-role conn {:project-id (:id project)
:profile-id profile-id
:role :owner})))
;; --- Mutation: Update Team

View file

@ -11,7 +11,34 @@
"A permission checking helper factories."
(:require
[app.common.exceptions :as ex]
[app.common.spec :as us]))
[app.common.spec :as us]
[clojure.spec.alpha :as s]))
(s/def ::role #{:admin :owner :editor :viewer})
(defn assign-role-flags
[params role]
(us/verify ::role role)
(cond-> params
(= role :owner)
(assoc :is-owner true
:is-admin true
:can-edit true)
(= role :admin)
(assoc :is-owner false
:is-admin true
:can-edit true)
(= role :editor)
(assoc :is-owner false
:is-admin false
:can-edit true)
(= role :viewer)
(assoc :is-owner false
:is-admin false
:can-edit false)))
(defn make-edition-check-fn
"A simple factory for edition permission check functions."

View file

@ -82,6 +82,50 @@
(db/exec! conn [sql:projects profile-id team-id]))
;; --- Query: All projects
(declare retrieve-all-projects)
(s/def ::profile-id ::us/uuid)
(s/def ::all-projects
(s/keys :req-un [::profile-id]))
(sv/defmethod ::all-projects
[{:keys [pool]} {:keys [profile-id]}]
(with-open [conn (db/open pool)]
(retrieve-all-projects conn profile-id)))
(def sql:all-projects
"select p1.*, t.name as team_name
from project as p1
inner join team as t
on t.id = p1.team_id
where t.id in (select team_id
from team_profile_rel as tpr
where tpr.profile_id = ?
and (tpr.can_edit = true or
tpr.is_owner = true or
tpr.is_admin = true))
and p1.deleted_at is null
union
select p2.*, t.name as team_name
from project as p2
inner join team as t
on t.id = p2.team_id
where p2.id in (select project_id
from project_profile_rel as ppr
where ppr.profile_id = ?
and (ppr.can_edit = true or
ppr.is_owner = true or
ppr.is_admin = true))
and p2.deleted_at is null
order by team_name, name;")
(defn retrieve-all-projects
[conn profile-id]
(db/exec! conn [sql:all-projects profile-id profile-id]))
;; --- Query: Project
(s/def ::id ::us/uuid)
@ -94,3 +138,4 @@
(let [project (db/get-by-id conn :project id)]
(check-read-permissions! conn profile-id id)
project)))

View file

@ -10,16 +10,12 @@
(ns app.setup.initial-data
(:refer-clojure :exclude [load])
(:require
[app.common.data :as d]
[app.common.pages.migrations :as pmg]
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.db :as db]
[app.rpc.mutations.projects :as projects]
[app.rpc.queries.profile :as profile]
[app.util.blob :as blob]
[app.util.time :as dt]
[clojure.walk :as walk]))
[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
@ -62,58 +58,6 @@
;; --- DUMP LOADING
(defn- process-file
[file index]
(letfn [(process-form [form]
(cond-> form
;; Relink Components
(and (map? form)
(uuid? (:component-file form)))
(update :component-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 analize 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))))))
(defn- remap-id
[item index key]
(cond-> item
(contains? item key)
(assoc key (get index (get item key) (get item key)))))
(defn- retrieve-data
[conn skey]
(when-let [row (db/exec-one! conn ["select content from server_prop where id = ?" skey])]
@ -127,60 +71,24 @@
(let [skey (or (:skey opts) (cfg/get :initial-project-skey))
data (retrieve-data conn skey)]
(when data
(let [project (projects/create-project conn {:profile-id (:id profile)
:team-id (:default-team-id profile)
:name (:project-name 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)}]
now (dt/now)
ignore (dt/plus now (dt/duration {:seconds 5}))
index (as-> {} index
(reduce #(assoc %1 (:id %2) (uuid/next)) index (:files data))
(reduce #(assoc %1 (:id %2) (uuid/next)) index (:fmeds data)))
(create-project conn project)
(create-project-role conn {:project-id (:id project)
:profile-id (:id profile)
:role :owner})
flibs (->> (:flibs data)
(map #(remap-id % index :file-id))
(map #(remap-id % index :library-file-id))
(map #(assoc % :synced-at now))
(map #(assoc % :created-at now)))
files (->> (:files data)
(map #(assoc % :id (get index (:id %))))
(map #(assoc % :project-id (:id project)))
(map #(assoc % :created-at now))
(map #(assoc % :modified-at now))
(map #(assoc % :ignore-sync-until ignore))
(map #(process-file % index)))
fmeds (->> (:fmeds data)
(map #(assoc % :id (get index (:id %))))
(map #(assoc % :created-at now))
(map #(remap-id % index :file-id)))
fprofs (map #(array-map :file-id (:id %)
:profile-id (:id profile)
:is-owner true
:is-admin true
:can-edit true) files)]
(projects/create-project-profile conn {:project-id (:id project)
:profile-id (:id profile)})
(projects/create-team-project-profile conn {:team-id (:default-team-id profile)
:project-id (:id project)
:profile-id (:id profile)})
;; Re-insert into the database
(doseq [params files]
(db/insert! conn :file params))
(doseq [params fprofs]
(db/insert! conn :file-profile-rel params))
(doseq [params flibs]
(db/insert! conn :file-library-rel params))
(doseq [params fmeds]
(db/insert! conn :file-media-object params)))))))
(doseq [file (:files data)]
(let [params {:profile-id (:id profile)
:project-id (:id project)
:file file
:index index}
opts {:reset-shared-flag false}]
(duplicate-file conn params opts))))))))
(defn load
[system {:keys [email] :as opts}]

View file

@ -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
@ -152,14 +154,27 @@
team (#'teams/create-team conn {:id id
:profile-id profile-id
:name (str "team" i)})]
(#'teams/create-team-profile conn
{:team-id id
:profile-id profile-id
:is-owner true
:is-admin true
:can-edit true})
(#'teams/create-team-role conn
{:team-id id
:profile-id profile-id
:role :owner})
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}]
@ -181,37 +196,39 @@
:created-at (or created-at (dt/now))
:content (db/tjson {})}))
(defn create-team-role*
([params] (create-team-role* *pool* params))
([conn {:keys [team-id profile-id role] :or {role :owner}}]
(#'teams/create-team-role conn {:team-id team-id
:profile-id profile-id
:role role})))
(defn create-team-permission*
([params] (create-team-permission* *pool* params))
([conn {:keys [team-id profile-id is-owner is-admin can-edit]
:or {is-owner true is-admin true can-edit true}}]
(db/insert! conn :team-profile-rel {:team-id team-id
:profile-id profile-id
:is-owner is-owner
:is-admin is-admin
:can-edit can-edit})))
(defn create-project-role*
([params] (create-project-role* *pool* params))
([conn {:keys [project-id profile-id role] :or {role :owner}}]
(#'projects/create-project-role conn {:project-id project-id
:profile-id profile-id
:role role})))
(defn create-project-permission*
([params] (create-project-permission* *pool* params))
([conn {:keys [project-id profile-id is-owner is-admin can-edit]
:or {is-owner true is-admin true can-edit true}}]
(db/insert! conn :project-profile-rel {:project-id project-id
:profile-id profile-id
:is-owner is-owner
:is-admin is-admin
:can-edit can-edit})))
(defn create-file-permission*
([params] (create-file-permission* *pool* params))
([conn {:keys [file-id profile-id is-owner is-admin can-edit]
:or {is-owner true is-admin true can-edit true}}]
(db/insert! conn :project-profile-rel {:file-id file-id
:profile-id profile-id
:is-owner is-owner
:is-admin is-admin
:can-edit can-edit})))
(defn create-file-role*
([params] (create-file-role* *pool* params))
([conn {:keys [file-id profile-id role] :or {role :owner}}]
(#'files/create-file-role conn {:file-id file-id
:profile-id profile-id
:role role})))
(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

View file

@ -0,0 +1,479 @@
;; 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)
:name "file 1 (copy)"}
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 id
;; and different name.
(t/is (= "file 1 (copy)" (: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)
:name "project 1 (copy)"}
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 id and name
(t/is (= "project 1 (copy)" (: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 (false? (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))))
)))

View file

@ -24,7 +24,7 @@
team (th/create-team* 1 {:profile-id (:id profile)})
project-id (uuid/next)]
;; crate project
;; create project
(let [data {::th/type :create-project
:id project-id
:profile-id (:id profile)
@ -37,7 +37,7 @@
(let [result (:result out)]
(t/is (= (:name data) (:name result)))))
;; query a list of projects
;; query the list of projects of a team
(let [data {::th/type :projects
:team-id (:id team)
:profile-id (:id profile)}
@ -50,7 +50,21 @@
(t/is project-id (get-in result [0 :id]))
(t/is (= "test project" (get-in result [0 :name])))))
;; rename project"
;; query all projects of a user
(let [data {::th/type :all-projects
:profile-id (:id profile)}
out (th/query! data)]
;; (th/print-result! out)
(t/is (nil? (:error out)))
(let [result (:result out)]
(t/is (= 2 (count result)))
(t/is (not= project-id (get-in result [0 :id])))
(t/is (= "Drafts" (get-in result [0 :name])))
(t/is project-id (get-in result [1 :id]))
(t/is (= "test project" (get-in result [1 :name])))))
;; rename project
(let [data {::th/type :rename-project
:id project-id
:name "renamed project"