mirror of
https://github.com/penpot/penpot.git
synced 2025-03-29 08:01:41 -05:00
♻️ Move teams queries and mutations to commands
This commit is contained in:
parent
be5053ce22
commit
7a9172560d
23 changed files with 993 additions and 653 deletions
|
@ -268,6 +268,7 @@
|
||||||
'app.rpc.commands.management
|
'app.rpc.commands.management
|
||||||
'app.rpc.commands.verify-token
|
'app.rpc.commands.verify-token
|
||||||
'app.rpc.commands.search
|
'app.rpc.commands.search
|
||||||
|
'app.rpc.commands.teams
|
||||||
'app.rpc.commands.auth
|
'app.rpc.commands.auth
|
||||||
'app.rpc.commands.ldap
|
'app.rpc.commands.ldap
|
||||||
'app.rpc.commands.demo
|
'app.rpc.commands.demo
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
[app.http.session :as session]
|
[app.http.session :as session]
|
||||||
[app.loggers.audit :as audit]
|
[app.loggers.audit :as audit]
|
||||||
[app.rpc.climit :as climit]
|
[app.rpc.climit :as climit]
|
||||||
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.helpers :as rph]
|
[app.rpc.helpers :as rph]
|
||||||
[app.rpc.mutations.teams :as teams]
|
|
||||||
[app.rpc.queries.profile :as profile]
|
[app.rpc.queries.profile :as profile]
|
||||||
[app.tokens :as tokens]
|
[app.tokens :as tokens]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.loggers.webhooks :as-alias webhooks]
|
[app.loggers.webhooks :as-alias webhooks]
|
||||||
[app.rpc.commands.files :as files]
|
[app.rpc.commands.files :as files]
|
||||||
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.queries.teams :as teams]
|
|
||||||
[app.rpc.retry :as retry]
|
[app.rpc.retry :as retry]
|
||||||
[app.util.blob :as blob]
|
[app.util.blob :as blob]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
|
|
|
@ -19,13 +19,13 @@
|
||||||
[app.db.sql :as sql]
|
[app.db.sql :as sql]
|
||||||
[app.loggers.webhooks :as-alias webhooks]
|
[app.loggers.webhooks :as-alias webhooks]
|
||||||
[app.rpc.commands.files.thumbnails :as-alias thumbs]
|
[app.rpc.commands.files.thumbnails :as-alias thumbs]
|
||||||
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.cond :as-alias cond]
|
[app.rpc.cond :as-alias cond]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.helpers :as rph]
|
[app.rpc.helpers :as rph]
|
||||||
[app.rpc.permissions :as perms]
|
[app.rpc.permissions :as perms]
|
||||||
[app.rpc.queries.projects :as projects]
|
[app.rpc.queries.projects :as projects]
|
||||||
[app.rpc.queries.share-link :refer [retrieve-share-link]]
|
[app.rpc.queries.share-link :refer [retrieve-share-link]]
|
||||||
[app.rpc.queries.teams :as teams]
|
|
||||||
[app.util.blob :as blob]
|
[app.util.blob :as blob]
|
||||||
[app.util.pointer-map :as pmap]
|
[app.util.pointer-map :as pmap]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
|
|
|
@ -15,10 +15,9 @@
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc.commands.binfile :as binfile]
|
[app.rpc.commands.binfile :as binfile]
|
||||||
[app.rpc.commands.files :as files]
|
[app.rpc.commands.files :as files]
|
||||||
|
[app.rpc.commands.teams :as teams :refer [create-project-role create-project]]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.mutations.projects :refer [create-project-role create-project]]
|
|
||||||
[app.rpc.queries.projects :as proj]
|
[app.rpc.queries.projects :as proj]
|
||||||
[app.rpc.queries.teams :as teams]
|
|
||||||
[app.util.blob :as blob]
|
[app.util.blob :as blob]
|
||||||
[app.util.pointer-map :as pmap]
|
[app.util.pointer-map :as pmap]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
|
|
817
backend/src/app/rpc/commands/teams.clj
Normal file
817
backend/src/app/rpc/commands/teams.clj
Normal file
|
@ -0,0 +1,817 @@
|
||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns app.rpc.commands.teams
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
|
[app.common.logging :as l]
|
||||||
|
[app.common.spec :as us]
|
||||||
|
[app.common.uuid :as uuid]
|
||||||
|
[app.config :as cf]
|
||||||
|
[app.db :as db]
|
||||||
|
[app.emails :as eml]
|
||||||
|
[app.loggers.audit :as audit]
|
||||||
|
[app.media :as media]
|
||||||
|
[app.rpc.climit :as climit]
|
||||||
|
[app.rpc.doc :as-alias doc]
|
||||||
|
[app.rpc.helpers :as rph]
|
||||||
|
[app.rpc.permissions :as perms]
|
||||||
|
[app.rpc.queries.profile :as profile]
|
||||||
|
[app.storage :as sto]
|
||||||
|
[app.tokens :as tokens]
|
||||||
|
[app.util.services :as sv]
|
||||||
|
[app.util.time :as dt]
|
||||||
|
[clojure.spec.alpha :as s]
|
||||||
|
[cuerdas.core :as str]
|
||||||
|
[promesa.core :as p]
|
||||||
|
[promesa.exec :as px]))
|
||||||
|
|
||||||
|
;; --- Helpers & Specs
|
||||||
|
|
||||||
|
(s/def ::id ::us/uuid)
|
||||||
|
(s/def ::name ::us/string)
|
||||||
|
(s/def ::profile-id ::us/uuid)
|
||||||
|
(s/def ::file-id ::us/uuid)
|
||||||
|
(s/def ::team-id ::us/uuid)
|
||||||
|
|
||||||
|
(def ^:private sql:team-permissions
|
||||||
|
"select tpr.is_owner,
|
||||||
|
tpr.is_admin,
|
||||||
|
tpr.can_edit
|
||||||
|
from team_profile_rel as tpr
|
||||||
|
join team as t on (t.id = tpr.team_id)
|
||||||
|
where tpr.profile_id = ?
|
||||||
|
and tpr.team_id = ?
|
||||||
|
and t.deleted_at is null")
|
||||||
|
|
||||||
|
(defn get-permissions
|
||||||
|
[conn profile-id team-id]
|
||||||
|
(let [rows (db/exec! conn [sql:team-permissions profile-id team-id])
|
||||||
|
is-owner (boolean (some :is-owner rows))
|
||||||
|
is-admin (boolean (some :is-admin rows))
|
||||||
|
can-edit (boolean (some :can-edit rows))]
|
||||||
|
(when (seq rows)
|
||||||
|
{:is-owner is-owner
|
||||||
|
:is-admin (or is-owner is-admin)
|
||||||
|
:can-edit (or is-owner is-admin can-edit)
|
||||||
|
:can-read true})))
|
||||||
|
|
||||||
|
(def has-edit-permissions?
|
||||||
|
(perms/make-edition-predicate-fn get-permissions))
|
||||||
|
|
||||||
|
(def has-read-permissions?
|
||||||
|
(perms/make-read-predicate-fn get-permissions))
|
||||||
|
|
||||||
|
(def check-edition-permissions!
|
||||||
|
(perms/make-check-fn has-edit-permissions?))
|
||||||
|
|
||||||
|
(def check-read-permissions!
|
||||||
|
(perms/make-check-fn has-read-permissions?))
|
||||||
|
|
||||||
|
;; --- Query: Teams
|
||||||
|
|
||||||
|
(declare retrieve-teams)
|
||||||
|
|
||||||
|
(s/def ::get-teams
|
||||||
|
(s/keys :req-un [::profile-id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::get-teams
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id]}]
|
||||||
|
(with-open [conn (db/open pool)]
|
||||||
|
(retrieve-teams conn profile-id)))
|
||||||
|
|
||||||
|
(def sql:teams
|
||||||
|
"select t.*,
|
||||||
|
tp.is_owner,
|
||||||
|
tp.is_admin,
|
||||||
|
tp.can_edit,
|
||||||
|
(t.id = ?) as is_default
|
||||||
|
from team_profile_rel as tp
|
||||||
|
join team as t on (t.id = tp.team_id)
|
||||||
|
where t.deleted_at is null
|
||||||
|
and tp.profile_id = ?
|
||||||
|
order by tp.created_at asc")
|
||||||
|
|
||||||
|
(defn process-permissions
|
||||||
|
[team]
|
||||||
|
(let [is-owner (:is-owner team)
|
||||||
|
is-admin (:is-admin team)
|
||||||
|
can-edit (:can-edit team)
|
||||||
|
permissions {:type :membership
|
||||||
|
:is-owner is-owner
|
||||||
|
:is-admin (or is-owner is-admin)
|
||||||
|
:can-edit (or is-owner is-admin can-edit)}]
|
||||||
|
(-> team
|
||||||
|
(dissoc :is-owner :is-admin :can-edit)
|
||||||
|
(assoc :permissions permissions))))
|
||||||
|
|
||||||
|
(defn retrieve-teams
|
||||||
|
[conn profile-id]
|
||||||
|
(let [defaults (profile/retrieve-additional-data conn profile-id)]
|
||||||
|
(->> (db/exec! conn [sql:teams (:default-team-id defaults) profile-id])
|
||||||
|
(mapv process-permissions))))
|
||||||
|
|
||||||
|
;; --- Query: Team (by ID)
|
||||||
|
|
||||||
|
(declare retrieve-team)
|
||||||
|
|
||||||
|
(s/def ::get-team
|
||||||
|
(s/keys :req-un [::profile-id ::id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::get-team
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id id]}]
|
||||||
|
(with-open [conn (db/open pool)]
|
||||||
|
(retrieve-team conn profile-id id)))
|
||||||
|
|
||||||
|
(defn retrieve-team
|
||||||
|
[conn profile-id team-id]
|
||||||
|
(let [defaults (profile/retrieve-additional-data conn profile-id)
|
||||||
|
sql (str "WITH teams AS (" sql:teams ") SELECT * FROM teams WHERE id=?")
|
||||||
|
result (db/exec-one! conn [sql (:default-team-id defaults) profile-id team-id])]
|
||||||
|
(when-not result
|
||||||
|
(ex/raise :type :not-found
|
||||||
|
:code :team-does-not-exist))
|
||||||
|
(process-permissions result)))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Query: Team Members
|
||||||
|
|
||||||
|
(def sql:team-members
|
||||||
|
"select tp.*,
|
||||||
|
p.id,
|
||||||
|
p.email,
|
||||||
|
p.fullname as name,
|
||||||
|
p.fullname as fullname,
|
||||||
|
p.photo_id,
|
||||||
|
p.is_active
|
||||||
|
from team_profile_rel as tp
|
||||||
|
join profile as p on (p.id = tp.profile_id)
|
||||||
|
where tp.team_id = ?")
|
||||||
|
|
||||||
|
(defn retrieve-team-members
|
||||||
|
[conn team-id]
|
||||||
|
(db/exec! conn [sql:team-members team-id]))
|
||||||
|
|
||||||
|
(s/def ::team-id ::us/uuid)
|
||||||
|
(s/def ::get-team-members
|
||||||
|
(s/keys :req-un [::profile-id ::team-id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::get-team-members
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id]}]
|
||||||
|
(with-open [conn (db/open pool)]
|
||||||
|
(check-read-permissions! conn profile-id team-id)
|
||||||
|
(retrieve-team-members conn team-id)))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Query: Team Users
|
||||||
|
|
||||||
|
(declare retrieve-users)
|
||||||
|
(declare retrieve-team-for-file)
|
||||||
|
|
||||||
|
(s/def ::get-team-users
|
||||||
|
(s/and (s/keys :req-un [::profile-id]
|
||||||
|
:opt-un [::team-id ::file-id])
|
||||||
|
#(or (:team-id %) (:file-id %))))
|
||||||
|
|
||||||
|
(sv/defmethod ::get-team-users
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id file-id]}]
|
||||||
|
(with-open [conn (db/open pool)]
|
||||||
|
(if team-id
|
||||||
|
(do
|
||||||
|
(check-read-permissions! conn profile-id team-id)
|
||||||
|
(retrieve-users conn team-id))
|
||||||
|
(let [{team-id :id} (retrieve-team-for-file conn file-id)]
|
||||||
|
(check-read-permissions! conn profile-id team-id)
|
||||||
|
(retrieve-users conn team-id)))))
|
||||||
|
|
||||||
|
;; This is a similar query to team members but can contain more data
|
||||||
|
;; because some user can be explicitly added to project or file (not
|
||||||
|
;; implemented in UI)
|
||||||
|
|
||||||
|
(def sql:team-users
|
||||||
|
"select pf.id, pf.fullname, pf.photo_id
|
||||||
|
from profile as pf
|
||||||
|
inner join team_profile_rel as tpr on (tpr.profile_id = pf.id)
|
||||||
|
where tpr.team_id = ?
|
||||||
|
union
|
||||||
|
select pf.id, pf.fullname, pf.photo_id
|
||||||
|
from profile as pf
|
||||||
|
inner join project_profile_rel as ppr on (ppr.profile_id = pf.id)
|
||||||
|
inner join project as p on (ppr.project_id = p.id)
|
||||||
|
where p.team_id = ?
|
||||||
|
union
|
||||||
|
select pf.id, pf.fullname, pf.photo_id
|
||||||
|
from profile as pf
|
||||||
|
inner join file_profile_rel as fpr on (fpr.profile_id = pf.id)
|
||||||
|
inner join file as f on (fpr.file_id = f.id)
|
||||||
|
inner join project as p on (f.project_id = p.id)
|
||||||
|
where p.team_id = ?")
|
||||||
|
|
||||||
|
(def sql:team-by-file
|
||||||
|
"select p.team_id as id
|
||||||
|
from project as p
|
||||||
|
join file as f on (p.id = f.project_id)
|
||||||
|
where f.id = ?")
|
||||||
|
|
||||||
|
(defn retrieve-users
|
||||||
|
[conn team-id]
|
||||||
|
(db/exec! conn [sql:team-users team-id team-id team-id]))
|
||||||
|
|
||||||
|
(defn retrieve-team-for-file
|
||||||
|
[conn file-id]
|
||||||
|
(->> [sql:team-by-file file-id]
|
||||||
|
(db/exec-one! conn)))
|
||||||
|
|
||||||
|
;; --- Query: Team Stats
|
||||||
|
|
||||||
|
(declare retrieve-team-stats)
|
||||||
|
|
||||||
|
(s/def ::get-team-stats
|
||||||
|
(s/keys :req-un [::profile-id ::team-id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::get-team-stats
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id]}]
|
||||||
|
(with-open [conn (db/open pool)]
|
||||||
|
(check-read-permissions! conn profile-id team-id)
|
||||||
|
(retrieve-team-stats conn team-id)))
|
||||||
|
|
||||||
|
(def sql:team-stats
|
||||||
|
"select (select count(*) from project where team_id = ?) as projects,
|
||||||
|
(select count(*) from file as f join project as p on (p.id = f.project_id) where p.team_id = ?) as files")
|
||||||
|
|
||||||
|
(defn retrieve-team-stats
|
||||||
|
[conn team-id]
|
||||||
|
(db/exec-one! conn [sql:team-stats team-id team-id]))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Query: Team invitations
|
||||||
|
|
||||||
|
(s/def ::get-team-invitations
|
||||||
|
(s/keys :req-un [::profile-id ::team-id]))
|
||||||
|
|
||||||
|
(def sql:team-invitations
|
||||||
|
"select email_to as email, role, (valid_until < now()) as expired
|
||||||
|
from team_invitation where team_id = ? order by valid_until desc")
|
||||||
|
|
||||||
|
(defn get-team-invitations
|
||||||
|
[conn team-id]
|
||||||
|
(->> (db/exec! conn [sql:team-invitations team-id])
|
||||||
|
(mapv #(update % :role keyword))))
|
||||||
|
|
||||||
|
(sv/defmethod ::get-team-invitations
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id]}]
|
||||||
|
(with-open [conn (db/open pool)]
|
||||||
|
(check-read-permissions! conn profile-id team-id)
|
||||||
|
(get-team-invitations conn team-id)))
|
||||||
|
|
||||||
|
;; --- Mutation: Create Team
|
||||||
|
|
||||||
|
(declare create-team)
|
||||||
|
(declare create-project)
|
||||||
|
(declare create-project-role)
|
||||||
|
(declare ^:private create-team*)
|
||||||
|
(declare ^:private create-team-role)
|
||||||
|
(declare ^:private create-team-default-project)
|
||||||
|
|
||||||
|
(s/def ::create-team
|
||||||
|
(s/keys :req-un [::profile-id ::name]
|
||||||
|
:opt-un [::id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::create-team
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} params]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(create-team conn params)))
|
||||||
|
|
||||||
|
(defn create-team
|
||||||
|
"This is a complete team creation process, it creates the team
|
||||||
|
object and all related objects (default role and default project)."
|
||||||
|
[conn params]
|
||||||
|
(let [team (create-team* conn params)
|
||||||
|
params (assoc params
|
||||||
|
:team-id (:id team)
|
||||||
|
:role :owner)
|
||||||
|
project (create-team-default-project conn params)]
|
||||||
|
(create-team-role conn params)
|
||||||
|
(assoc team :default-project-id (:id project))))
|
||||||
|
|
||||||
|
(defn- create-team*
|
||||||
|
[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 is-default})))
|
||||||
|
|
||||||
|
(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 [project {:id (uuid/next)
|
||||||
|
:team-id team-id
|
||||||
|
:name "Drafts"
|
||||||
|
:is-default true}
|
||||||
|
project (create-project conn project)]
|
||||||
|
(create-project-role conn {:project-id (:id project)
|
||||||
|
:profile-id profile-id
|
||||||
|
:role :owner})
|
||||||
|
project))
|
||||||
|
|
||||||
|
;; NOTE: we have project creation here because there are cyclic
|
||||||
|
;; dependency between teams and projects namespaces, and the project
|
||||||
|
;; creation happens in both sides, on team creation and on simple
|
||||||
|
;; project creation, so it make sense to have this functions in this
|
||||||
|
;; namespace too.
|
||||||
|
|
||||||
|
(defn create-project
|
||||||
|
[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
|
||||||
|
:name name
|
||||||
|
:team-id team-id
|
||||||
|
:is-default is-default})))
|
||||||
|
|
||||||
|
(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))))
|
||||||
|
|
||||||
|
;; --- Mutation: Update Team
|
||||||
|
|
||||||
|
(s/def ::update-team
|
||||||
|
(s/keys :req-un [::profile-id ::name ::id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::update-team
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} {:keys [id name profile-id] :as params}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(check-edition-permissions! conn profile-id id)
|
||||||
|
(db/update! conn :team
|
||||||
|
{:name name}
|
||||||
|
{:id id})
|
||||||
|
nil))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Mutation: Leave Team
|
||||||
|
|
||||||
|
(declare role->params)
|
||||||
|
|
||||||
|
(s/def ::reassign-to ::us/uuid)
|
||||||
|
(s/def ::leave-team
|
||||||
|
(s/keys :req-un [::profile-id ::id]
|
||||||
|
:opt-un [::reassign-to]))
|
||||||
|
|
||||||
|
(defn leave-team
|
||||||
|
[conn {:keys [id profile-id reassign-to]}]
|
||||||
|
(let [perms (get-permissions conn profile-id id)
|
||||||
|
members (retrieve-team-members conn id)]
|
||||||
|
|
||||||
|
(cond
|
||||||
|
;; we can only proceed if there are more members in the team
|
||||||
|
;; besides the current profile
|
||||||
|
(<= (count members) 1)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :no-enough-members-for-leave
|
||||||
|
:context {:members (count members)})
|
||||||
|
|
||||||
|
;; if the `reassign-to` is filled and has a different value
|
||||||
|
;; than the current profile-id, we proceed to reassing the
|
||||||
|
;; owner role to profile identified by the `reassign-to`.
|
||||||
|
(and reassign-to (not= reassign-to profile-id))
|
||||||
|
(let [member (d/seek #(= reassign-to (:id %)) members)]
|
||||||
|
(when-not member
|
||||||
|
(ex/raise :type :not-found :code :member-does-not-exist))
|
||||||
|
|
||||||
|
;; unasign owner role to current profile
|
||||||
|
(db/update! conn :team-profile-rel
|
||||||
|
{:is-owner false}
|
||||||
|
{:team-id id
|
||||||
|
:profile-id profile-id})
|
||||||
|
|
||||||
|
;; assign owner role to new profile
|
||||||
|
(db/update! conn :team-profile-rel
|
||||||
|
(role->params :owner)
|
||||||
|
{:team-id id :profile-id reassign-to}))
|
||||||
|
|
||||||
|
;; and finally, if all other conditions does not match and the
|
||||||
|
;; current profile is owner, we dont allow it because there
|
||||||
|
;; must always be an owner.
|
||||||
|
(:is-owner perms)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :owner-cant-leave-team
|
||||||
|
:hint "releasing owner before leave"))
|
||||||
|
|
||||||
|
(db/delete! conn :team-profile-rel
|
||||||
|
{:profile-id profile-id
|
||||||
|
:team-id id})
|
||||||
|
|
||||||
|
nil))
|
||||||
|
|
||||||
|
|
||||||
|
(sv/defmethod ::leave-team
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [pool] :as cfg} params]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(leave-team conn params)))
|
||||||
|
|
||||||
|
;; --- Mutation: Delete Team
|
||||||
|
|
||||||
|
(s/def ::delete-team
|
||||||
|
(s/keys :req-un [::profile-id ::id]))
|
||||||
|
|
||||||
|
;; TODO: right now just don't allow delete default team, in future it
|
||||||
|
;; should raise a specific exception for signal that this action is
|
||||||
|
;; not allowed.
|
||||||
|
|
||||||
|
(sv/defmethod ::delete-team
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(let [perms (get-permissions conn profile-id id)]
|
||||||
|
(when-not (:is-owner perms)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :only-owner-can-delete-team))
|
||||||
|
|
||||||
|
(db/update! conn :team
|
||||||
|
{:deleted-at (dt/now)}
|
||||||
|
{:id id :is-default false})
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Mutation: Team Update Role
|
||||||
|
|
||||||
|
(s/def ::team-id ::us/uuid)
|
||||||
|
(s/def ::member-id ::us/uuid)
|
||||||
|
;; Temporarily disabled viewer role
|
||||||
|
;; https://tree.taiga.io/project/uxboxproject/issue/1083
|
||||||
|
;; (s/def ::role #{:owner :admin :editor :viewer})
|
||||||
|
(s/def ::role #{:owner :admin :editor})
|
||||||
|
|
||||||
|
(defn role->params
|
||||||
|
[role]
|
||||||
|
(case role
|
||||||
|
:admin {:is-owner false :is-admin true :can-edit true}
|
||||||
|
:editor {:is-owner false :is-admin false :can-edit true}
|
||||||
|
:owner {:is-owner true :is-admin true :can-edit true}
|
||||||
|
:viewer {:is-owner false :is-admin false :can-edit false}))
|
||||||
|
|
||||||
|
(defn update-team-member-role
|
||||||
|
[conn {:keys [team-id profile-id member-id role] :as params}]
|
||||||
|
;; We retrieve all team members instead of query the
|
||||||
|
;; database for a single member. This is just for
|
||||||
|
;; convenience, if this becomes a bottleneck or problematic,
|
||||||
|
;; we will change it to more efficient fetch mechanisms.
|
||||||
|
(let [perms (get-permissions conn profile-id team-id)
|
||||||
|
members (retrieve-team-members conn team-id)
|
||||||
|
member (d/seek #(= member-id (:id %)) members)
|
||||||
|
|
||||||
|
is-owner? (:is-owner perms)
|
||||||
|
is-admin? (:is-admin perms)]
|
||||||
|
|
||||||
|
;; If no member is found, just 404
|
||||||
|
(when-not member
|
||||||
|
(ex/raise :type :not-found
|
||||||
|
:code :member-does-not-exist))
|
||||||
|
|
||||||
|
;; First check if we have permissions to change roles
|
||||||
|
(when-not (or is-owner? is-admin?)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :insufficient-permissions))
|
||||||
|
|
||||||
|
;; Don't allow change role of owner member
|
||||||
|
(when (:is-owner member)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :cant-change-role-to-owner))
|
||||||
|
|
||||||
|
;; Don't allow promote to owner to admin users.
|
||||||
|
(when (and (not is-owner?) (= role :owner))
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :cant-promote-to-owner))
|
||||||
|
|
||||||
|
(let [params (role->params role)]
|
||||||
|
;; Only allow single owner on team
|
||||||
|
(when (= role :owner)
|
||||||
|
(db/update! conn :team-profile-rel
|
||||||
|
{:is-owner false}
|
||||||
|
{:team-id team-id
|
||||||
|
:profile-id profile-id}))
|
||||||
|
|
||||||
|
(db/update! conn :team-profile-rel
|
||||||
|
params
|
||||||
|
{:team-id team-id
|
||||||
|
:profile-id member-id})
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
(s/def ::update-team-member-role
|
||||||
|
(s/keys :req-un [::profile-id ::team-id ::member-id ::role]))
|
||||||
|
|
||||||
|
(sv/defmethod ::update-team-member-role
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} params]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(update-team-member-role conn params)))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Mutation: Delete Team Member
|
||||||
|
|
||||||
|
(s/def ::delete-team-member
|
||||||
|
(s/keys :req-un [::profile-id ::team-id ::member-id]))
|
||||||
|
|
||||||
|
(sv/defmethod ::delete-team-member
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [pool] :as cfg} {:keys [team-id profile-id member-id] :as params}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(let [perms (get-permissions conn profile-id team-id)]
|
||||||
|
(when-not (or (:is-owner perms)
|
||||||
|
(:is-admin perms))
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :insufficient-permissions))
|
||||||
|
|
||||||
|
(when (= member-id profile-id)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :cant-remove-yourself))
|
||||||
|
|
||||||
|
(db/delete! conn :team-profile-rel {:profile-id member-id
|
||||||
|
:team-id team-id})
|
||||||
|
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
;; --- Mutation: Update Team Photo
|
||||||
|
|
||||||
|
(declare ^:private upload-photo)
|
||||||
|
(declare ^:private update-team-photo)
|
||||||
|
|
||||||
|
(s/def ::file ::media/upload)
|
||||||
|
(s/def ::update-team-photo
|
||||||
|
(s/keys :req-un [::profile-id ::team-id ::file]))
|
||||||
|
|
||||||
|
(sv/defmethod ::update-team-photo
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[cfg {:keys [file] :as params}]
|
||||||
|
;; Validate incoming mime type
|
||||||
|
(media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"})
|
||||||
|
(let [cfg (update cfg :storage media/configure-assets-storage)]
|
||||||
|
(update-team-photo cfg params)))
|
||||||
|
|
||||||
|
(defn update-team-photo
|
||||||
|
[{:keys [pool storage executor] :as cfg} {:keys [profile-id team-id] :as params}]
|
||||||
|
(p/let [team (px/with-dispatch executor
|
||||||
|
(retrieve-team pool profile-id team-id))
|
||||||
|
photo (upload-photo cfg params)]
|
||||||
|
|
||||||
|
;; Mark object as touched for make it ellegible for tentative
|
||||||
|
;; garbage collection.
|
||||||
|
(when-let [id (:photo-id team)]
|
||||||
|
(sto/touch-object! storage id))
|
||||||
|
|
||||||
|
;; Save new photo
|
||||||
|
(db/update! pool :team
|
||||||
|
{:photo-id (:id photo)}
|
||||||
|
{:id team-id})
|
||||||
|
|
||||||
|
(assoc team :photo-id (:id photo))))
|
||||||
|
|
||||||
|
(defn upload-photo
|
||||||
|
[{:keys [storage executor climit] :as cfg} {:keys [file]}]
|
||||||
|
(letfn [(get-info [content]
|
||||||
|
(climit/with-dispatch (:process-image climit)
|
||||||
|
(media/run {:cmd :info :input content})))
|
||||||
|
|
||||||
|
(generate-thumbnail [info]
|
||||||
|
(climit/with-dispatch (:process-image climit)
|
||||||
|
(media/run {:cmd :profile-thumbnail
|
||||||
|
:format :jpeg
|
||||||
|
:quality 85
|
||||||
|
:width 256
|
||||||
|
:height 256
|
||||||
|
:input info})))
|
||||||
|
|
||||||
|
;; Function responsible of calculating cryptographyc hash of
|
||||||
|
;; the provided data.
|
||||||
|
(calculate-hash [data]
|
||||||
|
(px/with-dispatch executor
|
||||||
|
(sto/calculate-hash data)))]
|
||||||
|
|
||||||
|
(p/let [info (get-info file)
|
||||||
|
thumb (generate-thumbnail info)
|
||||||
|
hash (calculate-hash (:data thumb))
|
||||||
|
content (-> (sto/content (:data thumb) (:size thumb))
|
||||||
|
(sto/wrap-with-hash hash))]
|
||||||
|
(sto/put-object! storage {::sto/content content
|
||||||
|
::sto/deduplicate? true
|
||||||
|
:bucket "profile"
|
||||||
|
:content-type (:mtype thumb)}))))
|
||||||
|
|
||||||
|
;; --- Mutation: Create Team Invitation
|
||||||
|
|
||||||
|
(def sql:upsert-team-invitation
|
||||||
|
"insert into team_invitation(team_id, email_to, role, valid_until)
|
||||||
|
values (?, ?, ?, ?)
|
||||||
|
on conflict(team_id, email_to) do
|
||||||
|
update set role = ?, valid_until = ?, updated_at = now();")
|
||||||
|
|
||||||
|
(defn- create-invitation
|
||||||
|
[{:keys [conn sprops team profile role email] :as cfg}]
|
||||||
|
(let [member (profile/retrieve-profile-data-by-email conn email)
|
||||||
|
token-exp (dt/in-future "168h") ;; 7 days
|
||||||
|
email (str/lower email)
|
||||||
|
itoken (tokens/generate sprops
|
||||||
|
{:iss :team-invitation
|
||||||
|
:exp token-exp
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:role role
|
||||||
|
:team-id (:id team)
|
||||||
|
:member-email (:email member email)
|
||||||
|
:member-id (:id member)})
|
||||||
|
ptoken (tokens/generate sprops
|
||||||
|
{:iss :profile-identity
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:exp (dt/in-future {:days 30})})]
|
||||||
|
|
||||||
|
(when (and member (not (eml/allow-send-emails? conn member)))
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :member-is-muted
|
||||||
|
:email email
|
||||||
|
:hint "the profile has reported repeatedly as spam or has bounces"))
|
||||||
|
|
||||||
|
;; Secondly check if the invited member email is part of the global spam/bounce report.
|
||||||
|
(when (eml/has-bounce-reports? conn email)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :email-has-permanent-bounces
|
||||||
|
:email email
|
||||||
|
:hint "the email you invite has been repeatedly reported as spam or bounce"))
|
||||||
|
|
||||||
|
(when (contains? cf/flags :log-invitation-tokens)
|
||||||
|
(l/trace :hint "invitation token" :token itoken))
|
||||||
|
|
||||||
|
;; When we have email verification disabled and invitation user is
|
||||||
|
;; already present in the database, we proceed to add it to the
|
||||||
|
;; team as-is, without email roundtrip.
|
||||||
|
|
||||||
|
;; TODO: if member does not exists and email verification is
|
||||||
|
;; disabled, we should proceed to create the profile (?)
|
||||||
|
(if (and (not (contains? cf/flags :email-verification))
|
||||||
|
(some? member))
|
||||||
|
(let [params (merge {:team-id (:id team)
|
||||||
|
:profile-id (:id member)}
|
||||||
|
(role->params role))]
|
||||||
|
|
||||||
|
;; Insert the invited member to the team
|
||||||
|
(db/insert! conn :team-profile-rel params {:on-conflict-do-nothing true})
|
||||||
|
|
||||||
|
;; If profile is not yet verified, mark it as verified because
|
||||||
|
;; accepting an invitation link serves as verification.
|
||||||
|
(when-not (:is-active member)
|
||||||
|
(db/update! conn :profile
|
||||||
|
{:is-active true}
|
||||||
|
{:id (:id member)})))
|
||||||
|
(do
|
||||||
|
(db/exec-one! conn [sql:upsert-team-invitation
|
||||||
|
(:id team) (str/lower email) (name role)
|
||||||
|
token-exp (name role) token-exp])
|
||||||
|
(eml/send! {::eml/conn conn
|
||||||
|
::eml/factory eml/invite-to-team
|
||||||
|
:public-uri (:public-uri cfg)
|
||||||
|
:to email
|
||||||
|
:invited-by (:fullname profile)
|
||||||
|
:team (:name team)
|
||||||
|
:token itoken
|
||||||
|
:extra-data ptoken})))
|
||||||
|
|
||||||
|
itoken))
|
||||||
|
|
||||||
|
(s/def ::email ::us/email)
|
||||||
|
(s/def ::emails ::us/set-of-valid-emails)
|
||||||
|
(s/def ::create-team-invitations
|
||||||
|
(s/keys :req-un [::profile-id ::team-id ::role]
|
||||||
|
:opt-un [::email ::emails]))
|
||||||
|
|
||||||
|
(sv/defmethod ::create-team-invitations
|
||||||
|
"A rpc call that allow to send a single or multiple invitations to
|
||||||
|
join the team."
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id team-id email emails role] :as params}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(let [perms (get-permissions conn profile-id team-id)
|
||||||
|
profile (db/get-by-id conn :profile profile-id)
|
||||||
|
team (db/get-by-id conn :team team-id)
|
||||||
|
emails (cond-> (or emails #{}) (string? email) (conj email))]
|
||||||
|
|
||||||
|
(when-not (:is-admin perms)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :insufficient-permissions))
|
||||||
|
|
||||||
|
;; First check if the current profile is allowed to send emails.
|
||||||
|
(when-not (eml/allow-send-emails? conn profile)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :profile-is-muted
|
||||||
|
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
|
||||||
|
|
||||||
|
(let [invitations (->> emails
|
||||||
|
(map (fn [email]
|
||||||
|
(assoc cfg
|
||||||
|
:email email
|
||||||
|
:conn conn
|
||||||
|
:team team
|
||||||
|
:profile profile
|
||||||
|
:role role)))
|
||||||
|
(map create-invitation))]
|
||||||
|
(with-meta (vec invitations)
|
||||||
|
{::audit/props {:invitations (count invitations)}})))))
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Mutation: Create Team & Invite Members
|
||||||
|
|
||||||
|
(s/def ::emails ::us/set-of-valid-emails)
|
||||||
|
(s/def ::create-team-and-invitations
|
||||||
|
(s/merge ::create-team
|
||||||
|
(s/keys :req-un [::emails ::role])))
|
||||||
|
|
||||||
|
(sv/defmethod ::create-team-and-invitations
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id emails role] :as params}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(let [team (create-team conn params)
|
||||||
|
profile (db/get-by-id conn :profile profile-id)]
|
||||||
|
|
||||||
|
;; Create invitations for all provided emails.
|
||||||
|
(doseq [email emails]
|
||||||
|
(create-invitation
|
||||||
|
(assoc cfg
|
||||||
|
:conn conn
|
||||||
|
:team team
|
||||||
|
:profile profile
|
||||||
|
:email email
|
||||||
|
:role role)))
|
||||||
|
|
||||||
|
(-> team
|
||||||
|
(vary-meta assoc ::audit/props {:invitations (count emails)})
|
||||||
|
(rph/with-defer
|
||||||
|
#(when-let [collector (::audit/collector cfg)]
|
||||||
|
(audit/submit! collector
|
||||||
|
{:type "command"
|
||||||
|
:name "create-team-invitations"
|
||||||
|
:profile-id profile-id
|
||||||
|
:props {:emails emails
|
||||||
|
:role role
|
||||||
|
:profile-id profile-id
|
||||||
|
:invitations (count emails)}})))))))
|
||||||
|
|
||||||
|
;; --- Mutation: Update invitation role
|
||||||
|
|
||||||
|
(s/def ::update-team-invitation-role
|
||||||
|
(s/keys :req-un [::profile-id ::team-id ::email ::role]))
|
||||||
|
|
||||||
|
(sv/defmethod ::update-team-invitation-role
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id team-id email role] :as params}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(let [perms (get-permissions conn profile-id team-id)]
|
||||||
|
|
||||||
|
(when-not (:is-admin perms)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :insufficient-permissions))
|
||||||
|
|
||||||
|
(db/update! conn :team-invitation
|
||||||
|
{:role (name role) :updated-at (dt/now)}
|
||||||
|
{:team-id team-id :email-to (str/lower email)})
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
;; --- Mutation: Delete invitation
|
||||||
|
|
||||||
|
(s/def ::delete-team-invitation
|
||||||
|
(s/keys :req-un [::profile-id ::team-id ::email]))
|
||||||
|
|
||||||
|
(sv/defmethod ::delete-team-invitation
|
||||||
|
{::doc/added "1.17"}
|
||||||
|
[{:keys [pool] :as cfg} {:keys [profile-id team-id email] :as params}]
|
||||||
|
(db/with-atomic [conn pool]
|
||||||
|
(let [perms (get-permissions conn profile-id team-id)]
|
||||||
|
|
||||||
|
(when-not (:is-admin perms)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :insufficient-permissions))
|
||||||
|
|
||||||
|
(db/delete! conn :team-invitation
|
||||||
|
{:team-id team-id :email-to (str/lower email)})
|
||||||
|
nil)))
|
|
@ -11,9 +11,9 @@
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.http.session :as session]
|
[app.http.session :as session]
|
||||||
[app.loggers.audit :as audit]
|
[app.loggers.audit :as audit]
|
||||||
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.helpers :as rph]
|
[app.rpc.helpers :as rph]
|
||||||
[app.rpc.mutations.teams :as teams]
|
|
||||||
[app.rpc.queries.profile :as profile]
|
[app.rpc.queries.profile :as profile]
|
||||||
[app.tokens :as tokens]
|
[app.tokens :as tokens]
|
||||||
[app.tokens.spec.team-invitation :as-alias spec.team-invitation]
|
[app.tokens.spec.team-invitation :as-alias spec.team-invitation]
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.http.client :as http]
|
[app.http.client :as http]
|
||||||
[app.loggers.webhooks :as webhooks]
|
[app.loggers.webhooks :as webhooks]
|
||||||
|
[app.rpc.commands.teams :refer [check-edition-permissions! check-read-permissions!]]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.queries.teams :refer [check-edition-permissions! check-read-permissions!]]
|
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[app.worker :as-alias wrk]
|
[app.worker :as-alias wrk]
|
||||||
|
|
|
@ -15,9 +15,9 @@
|
||||||
[app.loggers.webhooks :as-alias webhooks]
|
[app.loggers.webhooks :as-alias webhooks]
|
||||||
[app.media :as media]
|
[app.media :as media]
|
||||||
[app.rpc.climit :as-alias climit]
|
[app.rpc.climit :as-alias climit]
|
||||||
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.helpers :as rph]
|
[app.rpc.helpers :as rph]
|
||||||
[app.rpc.queries.teams :as teams]
|
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
[app.http.client :as http]
|
[app.http.client :as http]
|
||||||
[app.media :as media]
|
[app.media :as media]
|
||||||
[app.rpc.climit :as climit]
|
[app.rpc.climit :as climit]
|
||||||
[app.rpc.queries.teams :as teams]
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
[app.storage.tmp :as tmp]
|
[app.storage.tmp :as tmp]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
|
|
|
@ -17,10 +17,10 @@
|
||||||
[app.media :as media]
|
[app.media :as media]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
[app.rpc.climit :as-alias climit]
|
[app.rpc.climit :as-alias climit]
|
||||||
[app.rpc.commands.auth :as cmd.auth]
|
[app.rpc.commands.auth :as auth]
|
||||||
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.helpers :as rph]
|
[app.rpc.helpers :as rph]
|
||||||
[app.rpc.mutations.teams :as teams]
|
|
||||||
[app.rpc.queries.profile :as profile]
|
[app.rpc.queries.profile :as profile]
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
[app.tokens :as tokens]
|
[app.tokens :as tokens]
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
(defn- validate-password!
|
(defn- validate-password!
|
||||||
[conn {:keys [profile-id old-password] :as params}]
|
[conn {:keys [profile-id old-password] :as params}]
|
||||||
(let [profile (db/get-by-id conn :profile profile-id)]
|
(let [profile (db/get-by-id conn :profile profile-id)]
|
||||||
(when-not (:valid (cmd.auth/verify-password old-password (:password profile)))
|
(when-not (:valid (auth/verify-password old-password (:password profile)))
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :old-password-not-match))
|
:code :old-password-not-match))
|
||||||
profile))
|
profile))
|
||||||
|
@ -119,7 +119,7 @@
|
||||||
(defn update-profile-password!
|
(defn update-profile-password!
|
||||||
[conn {:keys [id password] :as profile}]
|
[conn {:keys [id password] :as profile}]
|
||||||
(db/update! conn :profile
|
(db/update! conn :profile
|
||||||
{:password (cmd.auth/derive-password password)}
|
{:password (auth/derive-password password)}
|
||||||
{:id id}))
|
{:id id}))
|
||||||
|
|
||||||
;; --- MUTATION: Update Photo
|
;; --- MUTATION: Update Photo
|
||||||
|
@ -182,7 +182,7 @@
|
||||||
(defn- change-email-immediately
|
(defn- change-email-immediately
|
||||||
[{:keys [conn]} {:keys [profile email] :as params}]
|
[{:keys [conn]} {:keys [profile email] :as params}]
|
||||||
(when (not= email (:email profile))
|
(when (not= email (:email profile))
|
||||||
(cmd.auth/check-profile-existence! conn params))
|
(auth/check-profile-existence! conn params))
|
||||||
(db/update! conn :profile
|
(db/update! conn :profile
|
||||||
{:email email}
|
{:email email}
|
||||||
{:id (:id profile)})
|
{:id (:id profile)})
|
||||||
|
@ -201,7 +201,7 @@
|
||||||
:exp (dt/in-future {:days 30})})]
|
:exp (dt/in-future {:days 30})})]
|
||||||
|
|
||||||
(when (not= email (:email profile))
|
(when (not= email (:email profile))
|
||||||
(cmd.auth/check-profile-existence! conn params))
|
(auth/check-profile-existence! conn params))
|
||||||
|
|
||||||
(when-not (eml/allow-send-emails? conn profile)
|
(when-not (eml/allow-send-emails? conn profile)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
|
|
|
@ -7,15 +7,13 @@
|
||||||
(ns app.rpc.mutations.projects
|
(ns app.rpc.mutations.projects
|
||||||
(:require
|
(:require
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.loggers.audit :as-alias audit]
|
[app.loggers.audit :as-alias audit]
|
||||||
[app.loggers.webhooks :as-alias webhooks]
|
[app.loggers.webhooks :as-alias webhooks]
|
||||||
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.helpers :as rph]
|
[app.rpc.helpers :as rph]
|
||||||
[app.rpc.permissions :as perms]
|
|
||||||
[app.rpc.queries.projects :as proj]
|
[app.rpc.queries.projects :as proj]
|
||||||
[app.rpc.queries.teams :as teams]
|
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]))
|
||||||
|
@ -28,9 +26,7 @@
|
||||||
|
|
||||||
;; --- Mutation: Create Project
|
;; --- Mutation: Create Project
|
||||||
|
|
||||||
(declare create-project)
|
(declare create-project-profile-state)
|
||||||
(declare create-project-role)
|
|
||||||
(declare create-team-project-profile)
|
|
||||||
|
|
||||||
(s/def ::team-id ::us/uuid)
|
(s/def ::team-id ::us/uuid)
|
||||||
(s/def ::create-project
|
(s/def ::create-project
|
||||||
|
@ -43,33 +39,15 @@
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(teams/check-edition-permissions! conn profile-id team-id)
|
(teams/check-edition-permissions! conn profile-id team-id)
|
||||||
(let [project (create-project conn params)
|
(let [project (teams/create-project conn params)
|
||||||
params (assoc params
|
params (assoc params
|
||||||
:project-id (:id project)
|
:project-id (:id project)
|
||||||
:role :owner)]
|
:role :owner)]
|
||||||
(create-project-role conn params)
|
(teams/create-project-role conn params)
|
||||||
(create-team-project-profile conn params)
|
(create-project-profile-state conn params)
|
||||||
(assoc project :is-pinned true))))
|
(assoc project :is-pinned true))))
|
||||||
|
|
||||||
(defn create-project
|
(defn create-project-profile-state
|
||||||
[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
|
|
||||||
:name name
|
|
||||||
:team-id team-id
|
|
||||||
:is-default is-default})))
|
|
||||||
|
|
||||||
(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}]
|
[conn {:keys [team-id project-id profile-id] :as params}]
|
||||||
(db/insert! conn :team-project-profile-rel
|
(db/insert! conn :team-project-profile-rel
|
||||||
{:project-id project-id
|
{:project-id project-id
|
||||||
|
@ -77,7 +55,6 @@
|
||||||
:team-id team-id
|
:team-id team-id
|
||||||
:is-pinned true}))
|
:is-pinned true}))
|
||||||
|
|
||||||
|
|
||||||
;; --- Mutation: Toggle Project Pin
|
;; --- Mutation: Toggle Project Pin
|
||||||
|
|
||||||
(def ^:private
|
(def ^:private
|
||||||
|
|
|
@ -6,30 +6,19 @@
|
||||||
|
|
||||||
(ns app.rpc.mutations.teams
|
(ns app.rpc.mutations.teams
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.logging :as l]
|
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.config :as cf]
|
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.emails :as eml]
|
[app.emails :as eml]
|
||||||
[app.loggers.audit :as audit]
|
[app.loggers.audit :as audit]
|
||||||
[app.media :as media]
|
[app.media :as media]
|
||||||
[app.rpc.climit :as climit]
|
[app.rpc.commands.teams :as cmd.teams]
|
||||||
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.helpers :as rph]
|
[app.rpc.helpers :as rph]
|
||||||
[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]
|
|
||||||
[app.tokens :as tokens]
|
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]))
|
||||||
[promesa.core :as p]
|
|
||||||
[promesa.exec :as px]))
|
|
||||||
|
|
||||||
;; --- Helpers & Specs
|
;; --- Helpers & Specs
|
||||||
|
|
||||||
|
@ -39,148 +28,54 @@
|
||||||
|
|
||||||
;; --- Mutation: Create Team
|
;; --- Mutation: Create Team
|
||||||
|
|
||||||
(declare create-team)
|
(s/def ::create-team ::cmd.teams/create-team)
|
||||||
(declare create-team-entry)
|
|
||||||
(declare create-team-role)
|
|
||||||
(declare create-team-default-project)
|
|
||||||
|
|
||||||
(s/def ::create-team
|
|
||||||
(s/keys :req-un [::profile-id ::name]
|
|
||||||
:opt-un [::id]))
|
|
||||||
|
|
||||||
(sv/defmethod ::create-team
|
(sv/defmethod ::create-team
|
||||||
[{:keys [pool] :as cfg} params]
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} params]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(create-team conn params)))
|
(cmd.teams/create-team conn params)))
|
||||||
|
|
||||||
(defn create-team
|
|
||||||
"This is a complete team creation process, it creates the team
|
|
||||||
object and all related objects (default role and default project)."
|
|
||||||
[conn params]
|
|
||||||
(let [team (create-team-entry conn params)
|
|
||||||
params (assoc params
|
|
||||||
:team-id (:id team)
|
|
||||||
:role :owner)
|
|
||||||
project (create-team-default-project conn params)]
|
|
||||||
(create-team-role conn params)
|
|
||||||
(assoc team :default-project-id (:id project))))
|
|
||||||
|
|
||||||
(defn- create-team-entry
|
|
||||||
[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 is-default})))
|
|
||||||
|
|
||||||
(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 [project {:id (uuid/next)
|
|
||||||
:team-id team-id
|
|
||||||
:name "Drafts"
|
|
||||||
:is-default true}
|
|
||||||
project (projects/create-project conn project)]
|
|
||||||
(projects/create-project-role conn {:project-id (:id project)
|
|
||||||
:profile-id profile-id
|
|
||||||
:role :owner})
|
|
||||||
project))
|
|
||||||
|
|
||||||
;; --- Mutation: Update Team
|
;; --- Mutation: Update Team
|
||||||
|
|
||||||
(s/def ::update-team
|
(s/def ::update-team ::cmd.teams/update-team)
|
||||||
(s/keys :req-un [::profile-id ::name ::id]))
|
|
||||||
|
|
||||||
(sv/defmethod ::update-team
|
(sv/defmethod ::update-team
|
||||||
[{:keys [pool] :as cfg} {:keys [id name profile-id] :as params}]
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} {:keys [id name profile-id] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(teams/check-edition-permissions! conn profile-id id)
|
(cmd.teams/check-edition-permissions! conn profile-id id)
|
||||||
(db/update! conn :team
|
(db/update! conn :team
|
||||||
{:name name}
|
{:name name}
|
||||||
{:id id})
|
{:id id})
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
|
|
||||||
;; --- Mutation: Leave Team
|
;; --- Mutation: Leave Team
|
||||||
|
|
||||||
(declare role->params)
|
(s/def ::leave-team ::cmd.teams/leave-team)
|
||||||
|
|
||||||
(s/def ::reassign-to ::us/uuid)
|
|
||||||
(s/def ::leave-team
|
|
||||||
(s/keys :req-un [::profile-id ::id]
|
|
||||||
:opt-un [::reassign-to]))
|
|
||||||
|
|
||||||
(sv/defmethod ::leave-team
|
(sv/defmethod ::leave-team
|
||||||
[{:keys [pool] :as cfg} {:keys [id profile-id reassign-to]}]
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} params]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [perms (teams/get-permissions conn profile-id id)
|
(cmd.teams/leave-team conn params)))
|
||||||
members (teams/retrieve-team-members conn id)]
|
|
||||||
|
|
||||||
(cond
|
|
||||||
;; we can only proceed if there are more members in the team
|
|
||||||
;; besides the current profile
|
|
||||||
(<= (count members) 1)
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :no-enough-members-for-leave
|
|
||||||
:context {:members (count members)})
|
|
||||||
|
|
||||||
;; if the `reassign-to` is filled and has a different value
|
|
||||||
;; than the current profile-id, we proceed to reassing the
|
|
||||||
;; owner role to profile identified by the `reassign-to`.
|
|
||||||
(and reassign-to (not= reassign-to profile-id))
|
|
||||||
(let [member (d/seek #(= reassign-to (:id %)) members)]
|
|
||||||
(when-not member
|
|
||||||
(ex/raise :type :not-found :code :member-does-not-exist))
|
|
||||||
|
|
||||||
;; unasign owner role to current profile
|
|
||||||
(db/update! conn :team-profile-rel
|
|
||||||
{:is-owner false}
|
|
||||||
{:team-id id
|
|
||||||
:profile-id profile-id})
|
|
||||||
|
|
||||||
;; assign owner role to new profile
|
|
||||||
(db/update! conn :team-profile-rel
|
|
||||||
(role->params :owner)
|
|
||||||
{:team-id id :profile-id reassign-to}))
|
|
||||||
|
|
||||||
;; and finally, if all other conditions does not match and the
|
|
||||||
;; current profile is owner, we dont allow it because there
|
|
||||||
;; must always be an owner.
|
|
||||||
(:is-owner perms)
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :owner-cant-leave-team
|
|
||||||
:hint "releasing owner before leave"))
|
|
||||||
|
|
||||||
(db/delete! conn :team-profile-rel
|
|
||||||
{:profile-id profile-id
|
|
||||||
:team-id id})
|
|
||||||
|
|
||||||
nil)))
|
|
||||||
|
|
||||||
;; --- Mutation: Delete Team
|
;; --- Mutation: Delete Team
|
||||||
|
|
||||||
(s/def ::delete-team
|
(s/def ::delete-team ::cmd.teams/delete-team)
|
||||||
(s/keys :req-un [::profile-id ::id]))
|
|
||||||
|
|
||||||
;; TODO: right now just don't allow delete default team, in future it
|
|
||||||
;; should raise a specific exception for signal that this action is
|
|
||||||
;; not allowed.
|
|
||||||
|
|
||||||
(sv/defmethod ::delete-team
|
(sv/defmethod ::delete-team
|
||||||
[{:keys [pool] :as cfg} {:keys [id profile-id] :as params}]
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} {:keys [id profile-id] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [perms (teams/get-permissions conn profile-id id)]
|
(let [perms (cmd.teams/get-permissions conn profile-id id)]
|
||||||
(when-not (:is-owner perms)
|
(when-not (:is-owner perms)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :only-owner-can-delete-team))
|
:code :only-owner-can-delete-team))
|
||||||
|
|
||||||
(db/update! conn :team
|
(db/update! conn :team
|
||||||
{:deleted-at (dt/now)}
|
{:deleted-at (dt/now)}
|
||||||
{:id id :is-default false})
|
{:id id :is-default false})
|
||||||
|
@ -189,89 +84,29 @@
|
||||||
|
|
||||||
;; --- Mutation: Team Update Role
|
;; --- Mutation: Team Update Role
|
||||||
|
|
||||||
(declare retrieve-team-member)
|
(s/def ::update-team-member-role ::cmd.teams/update-team-member-role)
|
||||||
|
|
||||||
(s/def ::team-id ::us/uuid)
|
|
||||||
(s/def ::member-id ::us/uuid)
|
|
||||||
;; Temporarily disabled viewer role
|
|
||||||
;; https://tree.taiga.io/project/uxboxproject/issue/1083
|
|
||||||
;; (s/def ::role #{:owner :admin :editor :viewer})
|
|
||||||
(s/def ::role #{:owner :admin :editor})
|
|
||||||
|
|
||||||
(s/def ::update-team-member-role
|
|
||||||
(s/keys :req-un [::profile-id ::team-id ::member-id ::role]))
|
|
||||||
|
|
||||||
(sv/defmethod ::update-team-member-role
|
(sv/defmethod ::update-team-member-role
|
||||||
[{:keys [pool] :as cfg} {:keys [team-id profile-id member-id role] :as params}]
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} params]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [perms (teams/get-permissions conn profile-id team-id)
|
(cmd.teams/update-team-member-role conn params)))
|
||||||
;; We retrieve all team members instead of query the
|
|
||||||
;; database for a single member. This is just for
|
|
||||||
;; convenience, if this becomes a bottleneck or problematic,
|
|
||||||
;; we will change it to more efficient fetch mechanisms.
|
|
||||||
members (teams/retrieve-team-members conn team-id)
|
|
||||||
member (d/seek #(= member-id (:id %)) members)
|
|
||||||
|
|
||||||
is-owner? (:is-owner perms)
|
|
||||||
is-admin? (:is-admin perms)]
|
|
||||||
|
|
||||||
;; If no member is found, just 404
|
|
||||||
(when-not member
|
|
||||||
(ex/raise :type :not-found
|
|
||||||
:code :member-does-not-exist))
|
|
||||||
|
|
||||||
;; First check if we have permissions to change roles
|
|
||||||
(when-not (or is-owner? is-admin?)
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :insufficient-permissions))
|
|
||||||
|
|
||||||
;; Don't allow change role of owner member
|
|
||||||
(when (:is-owner member)
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :cant-change-role-to-owner))
|
|
||||||
|
|
||||||
;; Don't allow promote to owner to admin users.
|
|
||||||
(when (and (not is-owner?) (= role :owner))
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :cant-promote-to-owner))
|
|
||||||
|
|
||||||
(let [params (role->params role)]
|
|
||||||
;; Only allow single owner on team
|
|
||||||
(when (= role :owner)
|
|
||||||
(db/update! conn :team-profile-rel
|
|
||||||
{:is-owner false}
|
|
||||||
{:team-id team-id
|
|
||||||
:profile-id profile-id}))
|
|
||||||
|
|
||||||
(db/update! conn :team-profile-rel
|
|
||||||
params
|
|
||||||
{:team-id team-id
|
|
||||||
:profile-id member-id})
|
|
||||||
nil))))
|
|
||||||
|
|
||||||
(defn role->params
|
|
||||||
[role]
|
|
||||||
(case role
|
|
||||||
:admin {:is-owner false :is-admin true :can-edit true}
|
|
||||||
:editor {:is-owner false :is-admin false :can-edit true}
|
|
||||||
:owner {:is-owner true :is-admin true :can-edit true}
|
|
||||||
:viewer {:is-owner false :is-admin false :can-edit false}))
|
|
||||||
|
|
||||||
|
|
||||||
;; --- Mutation: Delete Team Member
|
;; --- Mutation: Delete Team Member
|
||||||
|
|
||||||
(s/def ::delete-team-member
|
(s/def ::delete-team-member ::cmd.teams/delete-team-member)
|
||||||
(s/keys :req-un [::profile-id ::team-id ::member-id]))
|
|
||||||
|
|
||||||
(sv/defmethod ::delete-team-member
|
(sv/defmethod ::delete-team-member
|
||||||
[{:keys [pool] :as cfg} {:keys [team-id profile-id member-id] :as params}]
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} {:keys [team-id profile-id member-id] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [perms (teams/get-permissions conn profile-id team-id)]
|
(let [perms (cmd.teams/get-permissions conn profile-id team-id)]
|
||||||
(when-not (or (:is-owner perms)
|
(when-not (or (:is-owner perms)
|
||||||
(:is-admin perms))
|
(:is-admin perms))
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :insufficient-permissions))
|
:code :insufficient-permissions))
|
||||||
|
|
||||||
(when (= member-id profile-id)
|
(when (= member-id profile-id)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :cant-remove-yourself))
|
:code :cant-remove-yourself))
|
||||||
|
@ -283,85 +118,27 @@
|
||||||
|
|
||||||
;; --- Mutation: Update Team Photo
|
;; --- Mutation: Update Team Photo
|
||||||
|
|
||||||
(declare ^:private upload-photo)
|
(s/def ::update-team-photo ::cmd.teams/update-team-photo)
|
||||||
(declare ^:private update-team-photo)
|
|
||||||
|
|
||||||
(s/def ::file ::media/upload)
|
|
||||||
(s/def ::update-team-photo
|
|
||||||
(s/keys :req-un [::profile-id ::team-id ::file]))
|
|
||||||
|
|
||||||
(sv/defmethod ::update-team-photo
|
(sv/defmethod ::update-team-photo
|
||||||
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
[cfg {:keys [file] :as params}]
|
[cfg {:keys [file] :as params}]
|
||||||
;; Validate incoming mime type
|
;; Validate incoming mime type
|
||||||
(media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"})
|
(media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"})
|
||||||
(let [cfg (update cfg :storage media/configure-assets-storage)]
|
(let [cfg (update cfg :storage media/configure-assets-storage)]
|
||||||
(update-team-photo cfg params)))
|
(cmd.teams/update-team-photo cfg params)))
|
||||||
|
|
||||||
(defn update-team-photo
|
|
||||||
[{:keys [pool storage executor] :as cfg} {:keys [profile-id team-id] :as params}]
|
|
||||||
(p/let [team (px/with-dispatch executor
|
|
||||||
(teams/retrieve-team pool profile-id team-id))
|
|
||||||
photo (upload-photo cfg params)]
|
|
||||||
|
|
||||||
;; Mark object as touched for make it ellegible for tentative
|
|
||||||
;; garbage collection.
|
|
||||||
(when-let [id (:photo-id team)]
|
|
||||||
(sto/touch-object! storage id))
|
|
||||||
|
|
||||||
;; Save new photo
|
|
||||||
(db/update! pool :team
|
|
||||||
{:photo-id (:id photo)}
|
|
||||||
{:id team-id})
|
|
||||||
|
|
||||||
(assoc team :photo-id (:id photo))))
|
|
||||||
|
|
||||||
(defn upload-photo
|
|
||||||
[{:keys [storage executor climit] :as cfg} {:keys [file]}]
|
|
||||||
(letfn [(get-info [content]
|
|
||||||
(climit/with-dispatch (:process-image climit)
|
|
||||||
(media/run {:cmd :info :input content})))
|
|
||||||
|
|
||||||
(generate-thumbnail [info]
|
|
||||||
(climit/with-dispatch (:process-image climit)
|
|
||||||
(media/run {:cmd :profile-thumbnail
|
|
||||||
:format :jpeg
|
|
||||||
:quality 85
|
|
||||||
:width 256
|
|
||||||
:height 256
|
|
||||||
:input info})))
|
|
||||||
|
|
||||||
;; Function responsible of calculating cryptographyc hash of
|
|
||||||
;; the provided data.
|
|
||||||
(calculate-hash [data]
|
|
||||||
(px/with-dispatch executor
|
|
||||||
(sto/calculate-hash data)))]
|
|
||||||
|
|
||||||
(p/let [info (get-info file)
|
|
||||||
thumb (generate-thumbnail info)
|
|
||||||
hash (calculate-hash (:data thumb))
|
|
||||||
content (-> (sto/content (:data thumb) (:size thumb))
|
|
||||||
(sto/wrap-with-hash hash))]
|
|
||||||
(sto/put-object! storage {::sto/content content
|
|
||||||
::sto/deduplicate? true
|
|
||||||
:bucket "profile"
|
|
||||||
:content-type (:mtype thumb)}))))
|
|
||||||
|
|
||||||
;; --- Mutation: Invite Member
|
;; --- Mutation: Invite Member
|
||||||
|
|
||||||
(declare create-team-invitation)
|
(s/def ::invite-team-member ::cmd.teams/create-team-invitations)
|
||||||
|
|
||||||
(s/def ::email ::us/email)
|
|
||||||
(s/def ::emails ::us/set-of-valid-emails)
|
|
||||||
(s/def ::invite-team-member
|
|
||||||
(s/keys :req-un [::profile-id ::team-id ::role]
|
|
||||||
:opt-un [::email ::emails]))
|
|
||||||
|
|
||||||
(sv/defmethod ::invite-team-member
|
(sv/defmethod ::invite-team-member
|
||||||
"A rpc call that allow to send a single or multiple invitations to
|
{::doc/added "1.0"
|
||||||
join the team."
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id email emails role] :as params}]
|
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id email emails role] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [perms (teams/get-permissions conn profile-id team-id)
|
(let [perms (cmd.teams/get-permissions conn profile-id team-id)
|
||||||
profile (db/get-by-id conn :profile profile-id)
|
profile (db/get-by-id conn :profile profile-id)
|
||||||
team (db/get-by-id conn :team team-id)
|
team (db/get-by-id conn :team team-id)
|
||||||
emails (cond-> (or emails #{}) (string? email) (conj email))]
|
emails (cond-> (or emails #{}) (string? email) (conj email))]
|
||||||
|
@ -384,101 +161,25 @@
|
||||||
:team team
|
:team team
|
||||||
:profile profile
|
:profile profile
|
||||||
:role role)))
|
:role role)))
|
||||||
(map create-team-invitation))]
|
(map #'cmd.teams/create-invitation))]
|
||||||
(with-meta (vec invitations)
|
(with-meta (vec invitations)
|
||||||
{::audit/props {:invitations (count invitations)}})))))
|
{::audit/props {:invitations (count invitations)}})))))
|
||||||
|
|
||||||
(def sql:upsert-team-invitation
|
|
||||||
"insert into team_invitation(team_id, email_to, role, valid_until)
|
|
||||||
values (?, ?, ?, ?)
|
|
||||||
on conflict(team_id, email_to) do
|
|
||||||
update set role = ?, valid_until = ?, updated_at = now();")
|
|
||||||
|
|
||||||
(defn- create-team-invitation
|
|
||||||
[{:keys [conn sprops team profile role email] :as cfg}]
|
|
||||||
(let [member (profile/retrieve-profile-data-by-email conn email)
|
|
||||||
token-exp (dt/in-future "168h") ;; 7 days
|
|
||||||
email (str/lower email)
|
|
||||||
itoken (tokens/generate sprops
|
|
||||||
{:iss :team-invitation
|
|
||||||
:exp token-exp
|
|
||||||
:profile-id (:id profile)
|
|
||||||
:role role
|
|
||||||
:team-id (:id team)
|
|
||||||
:member-email (:email member email)
|
|
||||||
:member-id (:id member)})
|
|
||||||
ptoken (tokens/generate sprops
|
|
||||||
{:iss :profile-identity
|
|
||||||
:profile-id (:id profile)
|
|
||||||
:exp (dt/in-future {:days 30})})]
|
|
||||||
|
|
||||||
(when (and member (not (eml/allow-send-emails? conn member)))
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :member-is-muted
|
|
||||||
:email email
|
|
||||||
:hint "the profile has reported repeatedly as spam or has bounces"))
|
|
||||||
|
|
||||||
;; Secondly check if the invited member email is part of the global spam/bounce report.
|
|
||||||
(when (eml/has-bounce-reports? conn email)
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :email-has-permanent-bounces
|
|
||||||
:email email
|
|
||||||
:hint "the email you invite has been repeatedly reported as spam or bounce"))
|
|
||||||
|
|
||||||
(when (contains? cf/flags :log-invitation-tokens)
|
|
||||||
(l/trace :hint "invitation token" :token itoken))
|
|
||||||
|
|
||||||
;; When we have email verification disabled and invitation user is
|
|
||||||
;; already present in the database, we proceed to add it to the
|
|
||||||
;; team as-is, without email roundtrip.
|
|
||||||
|
|
||||||
;; TODO: if member does not exists and email verification is
|
|
||||||
;; disabled, we should proceed to create the profile (?)
|
|
||||||
(if (and (not (contains? cf/flags :email-verification))
|
|
||||||
(some? member))
|
|
||||||
(let [params (merge {:team-id (:id team)
|
|
||||||
:profile-id (:id member)}
|
|
||||||
(role->params role))]
|
|
||||||
|
|
||||||
;; Insert the invited member to the team
|
|
||||||
(db/insert! conn :team-profile-rel params {:on-conflict-do-nothing true})
|
|
||||||
|
|
||||||
;; If profile is not yet verified, mark it as verified because
|
|
||||||
;; accepting an invitation link serves as verification.
|
|
||||||
(when-not (:is-active member)
|
|
||||||
(db/update! conn :profile
|
|
||||||
{:is-active true}
|
|
||||||
{:id (:id member)})))
|
|
||||||
(do
|
|
||||||
(db/exec-one! conn [sql:upsert-team-invitation
|
|
||||||
(:id team) (str/lower email) (name role)
|
|
||||||
token-exp (name role) token-exp])
|
|
||||||
(eml/send! {::eml/conn conn
|
|
||||||
::eml/factory eml/invite-to-team
|
|
||||||
:public-uri (:public-uri cfg)
|
|
||||||
:to email
|
|
||||||
:invited-by (:fullname profile)
|
|
||||||
:team (:name team)
|
|
||||||
:token itoken
|
|
||||||
:extra-data ptoken})))
|
|
||||||
|
|
||||||
itoken))
|
|
||||||
|
|
||||||
;; --- Mutation: Create Team & Invite Members
|
;; --- Mutation: Create Team & Invite Members
|
||||||
|
|
||||||
(s/def ::emails ::us/set-of-valid-emails)
|
(s/def ::create-team-and-invite-members ::cmd.teams/create-team-and-invitations)
|
||||||
(s/def ::create-team-and-invite-members
|
|
||||||
(s/and ::create-team (s/keys :req-un [::emails ::role])))
|
|
||||||
|
|
||||||
(sv/defmethod ::create-team-and-invite-members
|
(sv/defmethod ::create-team-and-invite-members
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id emails role] :as params}]
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} {:keys [profile-id emails role] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [team (create-team conn params)
|
(let [team (cmd.teams/create-team conn params)
|
||||||
profile (db/get-by-id conn :profile profile-id)]
|
profile (db/get-by-id conn :profile profile-id)]
|
||||||
|
|
||||||
;; Create invitations for all provided emails.
|
;; Create invitations for all provided emails.
|
||||||
(doseq [email emails]
|
(doseq [email emails]
|
||||||
(create-team-invitation
|
(#'cmd.teams/create-invitation
|
||||||
(assoc cfg
|
(assoc cfg
|
||||||
:conn conn
|
:conn conn
|
||||||
:team team
|
:team team
|
||||||
|
@ -505,9 +206,11 @@
|
||||||
(s/keys :req-un [::profile-id ::team-id ::email ::role]))
|
(s/keys :req-un [::profile-id ::team-id ::email ::role]))
|
||||||
|
|
||||||
(sv/defmethod ::update-team-invitation-role
|
(sv/defmethod ::update-team-invitation-role
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id email role] :as params}]
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id email role] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [perms (teams/get-permissions conn profile-id team-id)]
|
(let [perms (cmd.teams/get-permissions conn profile-id team-id)]
|
||||||
|
|
||||||
(when-not (:is-admin perms)
|
(when-not (:is-admin perms)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
|
@ -520,13 +223,14 @@
|
||||||
|
|
||||||
;; --- Mutation: Delete invitation
|
;; --- Mutation: Delete invitation
|
||||||
|
|
||||||
(s/def ::delete-team-invitation
|
(s/def ::delete-team-invitation ::cmd.teams/delete-team-invitation)
|
||||||
(s/keys :req-un [::profile-id ::team-id ::email]))
|
|
||||||
|
|
||||||
(sv/defmethod ::delete-team-invitation
|
(sv/defmethod ::delete-team-invitation
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id email] :as params}]
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
|
[{:keys [::db/pool] :as cfg} {:keys [profile-id team-id email] :as params}]
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [perms (teams/get-permissions conn profile-id team-id)]
|
(let [perms (cmd.teams/get-permissions conn profile-id team-id)]
|
||||||
|
|
||||||
(when-not (:is-admin perms)
|
(when-not (:is-admin perms)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc.commands.comments :as cmd.comments]
|
[app.rpc.commands.comments :as cmd.comments]
|
||||||
[app.rpc.commands.files :as cmd.files]
|
[app.rpc.commands.files :as cmd.files]
|
||||||
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.queries.teams :as teams]
|
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]))
|
||||||
|
|
||||||
|
|
|
@ -8,38 +8,38 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc.commands.files :as cmd.files]
|
[app.rpc.commands.files :as files]
|
||||||
[app.rpc.commands.search :as cmd.search]
|
[app.rpc.commands.search :as search]
|
||||||
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.rpc.helpers :as rph]
|
[app.rpc.helpers :as rph]
|
||||||
[app.rpc.queries.projects :as projects]
|
[app.rpc.queries.projects :as projects]
|
||||||
[app.rpc.queries.teams :as teams]
|
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]))
|
||||||
|
|
||||||
;; --- Query: Project Files
|
;; --- Query: Project Files
|
||||||
|
|
||||||
(s/def ::project-files ::cmd.files/get-project-files)
|
(s/def ::project-files ::files/get-project-files)
|
||||||
|
|
||||||
(sv/defmethod ::project-files
|
(sv/defmethod ::project-files
|
||||||
{::doc/added "1.1"
|
{::doc/added "1.0"
|
||||||
::doc/deprecated "1.17"}
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(projects/check-read-permissions! conn profile-id project-id)
|
(projects/check-read-permissions! conn profile-id project-id)
|
||||||
(cmd.files/get-project-files conn project-id)))
|
(files/get-project-files conn project-id)))
|
||||||
|
|
||||||
;; --- Query: File (By ID)
|
;; --- Query: File (By ID)
|
||||||
|
|
||||||
(s/def ::components-v2 ::us/boolean)
|
(s/def ::components-v2 ::us/boolean)
|
||||||
(s/def ::file
|
(s/def ::file
|
||||||
(s/and ::cmd.files/get-file
|
(s/and ::files/get-file
|
||||||
(s/keys :opt-un [::components-v2])))
|
(s/keys :opt-un [::components-v2])))
|
||||||
|
|
||||||
(defn get-file
|
(defn get-file
|
||||||
[conn id features]
|
[conn id features]
|
||||||
(let [file (cmd.files/get-file conn id features)
|
(let [file (files/get-file conn id features)
|
||||||
thumbs (cmd.files/get-object-thumbnails conn id)]
|
thumbs (files/get-object-thumbnails conn id)]
|
||||||
(assoc file :thumbnails thumbs)))
|
(assoc file :thumbnails thumbs)))
|
||||||
|
|
||||||
(sv/defmethod ::file
|
(sv/defmethod ::file
|
||||||
|
@ -48,19 +48,19 @@
|
||||||
::doc/deprecated "1.17"}
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id id features components-v2] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id id features components-v2] :as params}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(let [perms (cmd.files/get-permissions pool profile-id id)
|
(let [perms (files/get-permissions pool profile-id id)
|
||||||
;; BACKWARD COMPATIBILTY with the components-v2 parameter
|
;; BACKWARD COMPATIBILTY with the components-v2 parameter
|
||||||
features (cond-> (or features #{})
|
features (cond-> (or features #{})
|
||||||
components-v2 (conj "components/v2"))]
|
components-v2 (conj "components/v2"))]
|
||||||
|
|
||||||
(cmd.files/check-read-permissions! perms)
|
(files/check-read-permissions! perms)
|
||||||
(-> (get-file conn id features)
|
(-> (get-file conn id features)
|
||||||
(assoc :permissions perms)))))
|
(assoc :permissions perms)))))
|
||||||
|
|
||||||
;; --- QUERY: page
|
;; --- QUERY: page
|
||||||
|
|
||||||
(s/def ::page
|
(s/def ::page
|
||||||
(s/and ::cmd.files/get-page
|
(s/and ::files/get-page
|
||||||
(s/keys :opt-un [::components-v2])))
|
(s/keys :opt-un [::components-v2])))
|
||||||
|
|
||||||
(sv/defmethod ::page
|
(sv/defmethod ::page
|
||||||
|
@ -77,18 +77,18 @@
|
||||||
::doc/deprecated "1.17"}
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id features components-v2] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id file-id features components-v2] :as params}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(cmd.files/check-read-permissions! conn profile-id file-id)
|
(files/check-read-permissions! conn profile-id file-id)
|
||||||
(let [;; BACKWARD COMPATIBILTY with the components-v2 parameter
|
(let [;; BACKWARD COMPATIBILTY with the components-v2 parameter
|
||||||
features (cond-> (or features #{})
|
features (cond-> (or features #{})
|
||||||
components-v2 (conj "components/v2"))
|
components-v2 (conj "components/v2"))
|
||||||
params (assoc params :features features)]
|
params (assoc params :features features)]
|
||||||
|
|
||||||
(cmd.files/get-page conn params))))
|
(files/get-page conn params))))
|
||||||
|
|
||||||
;; --- QUERY: file-data-for-thumbnail
|
;; --- QUERY: file-data-for-thumbnail
|
||||||
|
|
||||||
(s/def ::file-data-for-thumbnail
|
(s/def ::file-data-for-thumbnail
|
||||||
(s/and ::cmd.files/get-file-data-for-thumbnail
|
(s/and ::files/get-file-data-for-thumbnail
|
||||||
(s/keys :opt-un [::components-v2])))
|
(s/keys :opt-un [::components-v2])))
|
||||||
|
|
||||||
(sv/defmethod ::file-data-for-thumbnail
|
(sv/defmethod ::file-data-for-thumbnail
|
||||||
|
@ -98,18 +98,18 @@
|
||||||
::doc/deprecated "1.17"}
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id features components-v2] :as props}]
|
[{:keys [pool] :as cfg} {:keys [profile-id file-id features components-v2] :as props}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(cmd.files/check-read-permissions! conn profile-id file-id)
|
(files/check-read-permissions! conn profile-id file-id)
|
||||||
(let [;; BACKWARD COMPATIBILTY with the components-v2 parameter
|
(let [;; BACKWARD COMPATIBILTY with the components-v2 parameter
|
||||||
features (cond-> (or features #{})
|
features (cond-> (or features #{})
|
||||||
components-v2 (conj "components/v2"))
|
components-v2 (conj "components/v2"))
|
||||||
file (cmd.files/get-file conn file-id features)]
|
file (files/get-file conn file-id features)]
|
||||||
{:file-id file-id
|
{:file-id file-id
|
||||||
:revn (:revn file)
|
:revn (:revn file)
|
||||||
:page (cmd.files/get-file-data-for-thumbnail conn file)})))
|
:page (files/get-file-data-for-thumbnail conn file)})))
|
||||||
|
|
||||||
;; --- Query: Shared Library Files
|
;; --- Query: Shared Library Files
|
||||||
|
|
||||||
(s/def ::team-shared-files ::cmd.files/get-team-shared-files)
|
(s/def ::team-shared-files ::files/get-team-shared-files)
|
||||||
|
|
||||||
(sv/defmethod ::team-shared-files
|
(sv/defmethod ::team-shared-files
|
||||||
{::doc/added "1.3"
|
{::doc/added "1.3"
|
||||||
|
@ -117,37 +117,37 @@
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(teams/check-read-permissions! conn profile-id team-id)
|
(teams/check-read-permissions! conn profile-id team-id)
|
||||||
(cmd.files/get-team-shared-files conn params)))
|
(files/get-team-shared-files conn params)))
|
||||||
|
|
||||||
|
|
||||||
;; --- Query: File Libraries used by a File
|
;; --- Query: File Libraries used by a File
|
||||||
|
|
||||||
(s/def ::file-libraries ::cmd.files/get-file-libraries)
|
(s/def ::file-libraries ::files/get-file-libraries)
|
||||||
|
|
||||||
(sv/defmethod ::file-libraries
|
(sv/defmethod ::file-libraries
|
||||||
{::doc/added "1.3"
|
{::doc/added "1.3"
|
||||||
::doc/deprecated "1.17"}
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id features] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id file-id features] :as params}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(cmd.files/check-read-permissions! conn profile-id file-id)
|
(files/check-read-permissions! conn profile-id file-id)
|
||||||
(cmd.files/get-file-libraries conn file-id features)))
|
(files/get-file-libraries conn file-id features)))
|
||||||
|
|
||||||
|
|
||||||
;; --- Query: Files that use this File library
|
;; --- Query: Files that use this File library
|
||||||
|
|
||||||
(s/def ::library-using-files ::cmd.files/get-library-file-references)
|
(s/def ::library-using-files ::files/get-library-file-references)
|
||||||
|
|
||||||
(sv/defmethod ::library-using-files
|
(sv/defmethod ::library-using-files
|
||||||
{::doc/added "1.13"
|
{::doc/added "1.13"
|
||||||
::doc/deprecated "1.17"}
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
|
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(cmd.files/check-read-permissions! conn profile-id file-id)
|
(files/check-read-permissions! conn profile-id file-id)
|
||||||
(cmd.files/get-library-file-references conn file-id)))
|
(files/get-library-file-references conn file-id)))
|
||||||
|
|
||||||
;; --- QUERY: team-recent-files
|
;; --- QUERY: team-recent-files
|
||||||
|
|
||||||
(s/def ::team-recent-files ::cmd.files/get-team-recent-files)
|
(s/def ::team-recent-files ::files/get-team-recent-files)
|
||||||
|
|
||||||
(sv/defmethod ::team-recent-files
|
(sv/defmethod ::team-recent-files
|
||||||
{::doc/added "1.0"
|
{::doc/added "1.0"
|
||||||
|
@ -155,30 +155,30 @@
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
|
[{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(teams/check-read-permissions! conn profile-id team-id)
|
(teams/check-read-permissions! conn profile-id team-id)
|
||||||
(cmd.files/get-team-recent-files conn team-id)))
|
(files/get-team-recent-files conn team-id)))
|
||||||
|
|
||||||
|
|
||||||
;; --- QUERY: get file thumbnail
|
;; --- QUERY: get file thumbnail
|
||||||
|
|
||||||
(s/def ::file-thumbnail ::cmd.files/get-file-thumbnail)
|
(s/def ::file-thumbnail ::files/get-file-thumbnail)
|
||||||
|
|
||||||
(sv/defmethod ::file-thumbnail
|
(sv/defmethod ::file-thumbnail
|
||||||
{::doc/added "1.13"
|
{::doc/added "1.13"
|
||||||
::doc/deprecated "1.17"}
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool]} {:keys [profile-id file-id revn]}]
|
[{:keys [pool]} {:keys [profile-id file-id revn]}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(cmd.files/check-read-permissions! conn profile-id file-id)
|
(files/check-read-permissions! conn profile-id file-id)
|
||||||
(-> (cmd.files/get-file-thumbnail conn file-id revn)
|
(-> (files/get-file-thumbnail conn file-id revn)
|
||||||
(rph/with-http-cache cmd.files/long-cache-duration))))
|
(rph/with-http-cache files/long-cache-duration))))
|
||||||
|
|
||||||
|
|
||||||
;; --- QUERY: search files
|
;; --- QUERY: search files
|
||||||
|
|
||||||
(s/def ::search-files ::cmd.search/search-files)
|
(s/def ::search-files ::search/search-files)
|
||||||
|
|
||||||
(sv/defmethod ::search-files
|
(sv/defmethod ::search-files
|
||||||
{::doc/added "1.0"
|
{::doc/added "1.0"
|
||||||
::doc/deprecated "1.17"}
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool]} {:keys [search-term] :as params}]
|
[{:keys [pool]} {:keys [search-term] :as params}]
|
||||||
(when search-term
|
(when search-term
|
||||||
(cmd.search/search-files pool params)))
|
(search/search-files pool params)))
|
||||||
|
|
|
@ -9,13 +9,14 @@
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc.commands.files :as files]
|
[app.rpc.commands.files :as files]
|
||||||
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.queries.projects :as projects]
|
[app.rpc.queries.projects :as projects]
|
||||||
[app.rpc.queries.teams :as teams]
|
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]))
|
||||||
|
|
||||||
;; --- Query: Team Font Variants
|
;; --- Query: Team Font Variants
|
||||||
|
|
||||||
|
;; FIXME: PLEASE RIGHT NOW
|
||||||
;; TODO: deprecated, should be removed on 1.7.x
|
;; TODO: deprecated, should be removed on 1.7.x
|
||||||
|
|
||||||
(s/def ::team-id ::us/uuid)
|
(s/def ::team-id ::us/uuid)
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.permissions :as perms]
|
[app.rpc.permissions :as perms]
|
||||||
[app.rpc.queries.teams :as teams]
|
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]))
|
||||||
|
|
||||||
|
|
|
@ -6,244 +6,82 @@
|
||||||
|
|
||||||
(ns app.rpc.queries.teams
|
(ns app.rpc.queries.teams
|
||||||
(:require
|
(:require
|
||||||
[app.common.exceptions :as ex]
|
|
||||||
[app.common.spec :as us]
|
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc.permissions :as perms]
|
[app.rpc.commands.teams :as cmd.teams]
|
||||||
[app.rpc.queries.profile :as profile]
|
[app.rpc.doc :as-alias doc]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[clojure.spec.alpha :as s]))
|
[clojure.spec.alpha :as s]))
|
||||||
|
|
||||||
;; --- Team Edition Permissions
|
|
||||||
|
|
||||||
(def ^:private sql:team-permissions
|
|
||||||
"select tpr.is_owner,
|
|
||||||
tpr.is_admin,
|
|
||||||
tpr.can_edit
|
|
||||||
from team_profile_rel as tpr
|
|
||||||
join team as t on (t.id = tpr.team_id)
|
|
||||||
where tpr.profile_id = ?
|
|
||||||
and tpr.team_id = ?
|
|
||||||
and t.deleted_at is null")
|
|
||||||
|
|
||||||
(defn get-permissions
|
|
||||||
[conn profile-id team-id]
|
|
||||||
(let [rows (db/exec! conn [sql:team-permissions profile-id team-id])
|
|
||||||
is-owner (boolean (some :is-owner rows))
|
|
||||||
is-admin (boolean (some :is-admin rows))
|
|
||||||
can-edit (boolean (some :can-edit rows))]
|
|
||||||
(when (seq rows)
|
|
||||||
{:is-owner is-owner
|
|
||||||
:is-admin (or is-owner is-admin)
|
|
||||||
:can-edit (or is-owner is-admin can-edit)
|
|
||||||
:can-read true})))
|
|
||||||
|
|
||||||
(def has-edit-permissions?
|
|
||||||
(perms/make-edition-predicate-fn get-permissions))
|
|
||||||
|
|
||||||
(def has-read-permissions?
|
|
||||||
(perms/make-read-predicate-fn get-permissions))
|
|
||||||
|
|
||||||
(def check-edition-permissions!
|
|
||||||
(perms/make-check-fn has-edit-permissions?))
|
|
||||||
|
|
||||||
(def check-read-permissions!
|
|
||||||
(perms/make-check-fn has-read-permissions?))
|
|
||||||
|
|
||||||
;; --- Query: Teams
|
;; --- Query: Teams
|
||||||
|
|
||||||
(declare retrieve-teams)
|
(s/def ::teams ::cmd.teams/get-teams)
|
||||||
|
|
||||||
(s/def ::profile-id ::us/uuid)
|
|
||||||
(s/def ::teams
|
|
||||||
(s/keys :req-un [::profile-id]))
|
|
||||||
|
|
||||||
(sv/defmethod ::teams
|
(sv/defmethod ::teams
|
||||||
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id]}]
|
[{:keys [pool] :as cfg} {:keys [profile-id]}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(retrieve-teams conn profile-id)))
|
(cmd.teams/retrieve-teams conn profile-id)))
|
||||||
|
|
||||||
(def sql:teams
|
|
||||||
"select t.*,
|
|
||||||
tp.is_owner,
|
|
||||||
tp.is_admin,
|
|
||||||
tp.can_edit,
|
|
||||||
(t.id = ?) as is_default
|
|
||||||
from team_profile_rel as tp
|
|
||||||
join team as t on (t.id = tp.team_id)
|
|
||||||
where t.deleted_at is null
|
|
||||||
and tp.profile_id = ?
|
|
||||||
order by tp.created_at asc")
|
|
||||||
|
|
||||||
(defn process-permissions
|
|
||||||
[team]
|
|
||||||
(let [is-owner (:is-owner team)
|
|
||||||
is-admin (:is-admin team)
|
|
||||||
can-edit (:can-edit team)
|
|
||||||
permissions {:type :membership
|
|
||||||
:is-owner is-owner
|
|
||||||
:is-admin (or is-owner is-admin)
|
|
||||||
:can-edit (or is-owner is-admin can-edit)}]
|
|
||||||
(-> team
|
|
||||||
(dissoc :is-owner :is-admin :can-edit)
|
|
||||||
(assoc :permissions permissions))))
|
|
||||||
|
|
||||||
(defn retrieve-teams
|
|
||||||
[conn profile-id]
|
|
||||||
(let [defaults (profile/retrieve-additional-data conn profile-id)]
|
|
||||||
(->> (db/exec! conn [sql:teams (:default-team-id defaults) profile-id])
|
|
||||||
(mapv process-permissions))))
|
|
||||||
|
|
||||||
;; --- Query: Team (by ID)
|
;; --- Query: Team (by ID)
|
||||||
|
|
||||||
(declare retrieve-team)
|
(s/def ::team ::cmd.teams/get-team)
|
||||||
|
|
||||||
(s/def ::id ::us/uuid)
|
|
||||||
(s/def ::team
|
|
||||||
(s/keys :req-un [::profile-id ::id]))
|
|
||||||
|
|
||||||
(sv/defmethod ::team
|
(sv/defmethod ::team
|
||||||
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id id]}]
|
[{:keys [pool] :as cfg} {:keys [profile-id id]}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(retrieve-team conn profile-id id)))
|
(cmd.teams/retrieve-team conn profile-id id)))
|
||||||
|
|
||||||
(defn retrieve-team
|
|
||||||
[conn profile-id team-id]
|
|
||||||
(let [defaults (profile/retrieve-additional-data conn profile-id)
|
|
||||||
sql (str "WITH teams AS (" sql:teams ") SELECT * FROM teams WHERE id=?")
|
|
||||||
result (db/exec-one! conn [sql (:default-team-id defaults) profile-id team-id])]
|
|
||||||
(when-not result
|
|
||||||
(ex/raise :type :not-found
|
|
||||||
:code :team-does-not-exist))
|
|
||||||
(process-permissions result)))
|
|
||||||
|
|
||||||
|
|
||||||
;; --- Query: Team Members
|
;; --- Query: Team Members
|
||||||
|
|
||||||
(declare retrieve-team-members)
|
(s/def ::team-members ::cmd.teams/get-team-members)
|
||||||
|
|
||||||
(s/def ::team-id ::us/uuid)
|
|
||||||
(s/def ::team-members
|
|
||||||
(s/keys :req-un [::profile-id ::team-id]))
|
|
||||||
|
|
||||||
(sv/defmethod ::team-members
|
(sv/defmethod ::team-members
|
||||||
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
|
[{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(check-read-permissions! conn profile-id team-id)
|
(cmd.teams/check-read-permissions! conn profile-id team-id)
|
||||||
(retrieve-team-members conn team-id)))
|
(cmd.teams/retrieve-team-members conn team-id)))
|
||||||
|
|
||||||
(def sql:team-members
|
|
||||||
"select tp.*,
|
|
||||||
p.id,
|
|
||||||
p.email,
|
|
||||||
p.fullname as name,
|
|
||||||
p.fullname as fullname,
|
|
||||||
p.photo_id,
|
|
||||||
p.is_active
|
|
||||||
from team_profile_rel as tp
|
|
||||||
join profile as p on (p.id = tp.profile_id)
|
|
||||||
where tp.team_id = ?")
|
|
||||||
|
|
||||||
(defn retrieve-team-members
|
|
||||||
[conn team-id]
|
|
||||||
(db/exec! conn [sql:team-members team-id]))
|
|
||||||
|
|
||||||
|
|
||||||
;; --- Query: Team Users
|
;; --- Query: Team Users
|
||||||
|
(s/def ::team-users ::cmd.teams/get-team-users)
|
||||||
(declare retrieve-users)
|
|
||||||
(declare retrieve-team-for-file)
|
|
||||||
|
|
||||||
(s/def ::file-id ::us/uuid)
|
|
||||||
(s/def ::team-users
|
|
||||||
(s/and (s/keys :req-un [::profile-id]
|
|
||||||
:opt-un [::team-id ::file-id])
|
|
||||||
#(or (:team-id %) (:file-id %))))
|
|
||||||
|
|
||||||
(sv/defmethod ::team-users
|
(sv/defmethod ::team-users
|
||||||
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id file-id]}]
|
[{:keys [pool] :as cfg} {:keys [profile-id team-id file-id]}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(if team-id
|
(if team-id
|
||||||
(do
|
(do
|
||||||
(check-read-permissions! conn profile-id team-id)
|
(cmd.teams/check-read-permissions! conn profile-id team-id)
|
||||||
(retrieve-users conn team-id))
|
(cmd.teams/retrieve-users conn team-id))
|
||||||
(let [{team-id :id} (retrieve-team-for-file conn file-id)]
|
(let [{team-id :id} (cmd.teams/retrieve-team-for-file conn file-id)]
|
||||||
(check-read-permissions! conn profile-id team-id)
|
(cmd.teams/check-read-permissions! conn profile-id team-id)
|
||||||
(retrieve-users conn team-id)))))
|
(cmd.teams/retrieve-users conn team-id)))))
|
||||||
|
|
||||||
;; This is a similar query to team members but can contain more data
|
|
||||||
;; because some user can be explicitly added to project or file (not
|
|
||||||
;; implemented in UI)
|
|
||||||
|
|
||||||
(def sql:team-users
|
|
||||||
"select pf.id, pf.fullname, pf.photo_id
|
|
||||||
from profile as pf
|
|
||||||
inner join team_profile_rel as tpr on (tpr.profile_id = pf.id)
|
|
||||||
where tpr.team_id = ?
|
|
||||||
union
|
|
||||||
select pf.id, pf.fullname, pf.photo_id
|
|
||||||
from profile as pf
|
|
||||||
inner join project_profile_rel as ppr on (ppr.profile_id = pf.id)
|
|
||||||
inner join project as p on (ppr.project_id = p.id)
|
|
||||||
where p.team_id = ?
|
|
||||||
union
|
|
||||||
select pf.id, pf.fullname, pf.photo_id
|
|
||||||
from profile as pf
|
|
||||||
inner join file_profile_rel as fpr on (fpr.profile_id = pf.id)
|
|
||||||
inner join file as f on (fpr.file_id = f.id)
|
|
||||||
inner join project as p on (f.project_id = p.id)
|
|
||||||
where p.team_id = ?")
|
|
||||||
|
|
||||||
(def sql:team-by-file
|
|
||||||
"select p.team_id as id
|
|
||||||
from project as p
|
|
||||||
join file as f on (p.id = f.project_id)
|
|
||||||
where f.id = ?")
|
|
||||||
|
|
||||||
(defn retrieve-users
|
|
||||||
[conn team-id]
|
|
||||||
(db/exec! conn [sql:team-users team-id team-id team-id]))
|
|
||||||
|
|
||||||
(defn retrieve-team-for-file
|
|
||||||
[conn file-id]
|
|
||||||
(->> [sql:team-by-file file-id]
|
|
||||||
(db/exec-one! conn)))
|
|
||||||
|
|
||||||
;; --- Query: Team Stats
|
;; --- Query: Team Stats
|
||||||
|
|
||||||
(declare retrieve-team-stats)
|
(s/def ::team-stats ::cmd.teams/get-team-stats)
|
||||||
|
|
||||||
(s/def ::team-stats
|
|
||||||
(s/keys :req-un [::profile-id ::team-id]))
|
|
||||||
|
|
||||||
(sv/defmethod ::team-stats
|
(sv/defmethod ::team-stats
|
||||||
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
|
[{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(check-read-permissions! conn profile-id team-id)
|
(cmd.teams/check-read-permissions! conn profile-id team-id)
|
||||||
(retrieve-team-stats conn team-id)))
|
(cmd.teams/retrieve-team-stats conn team-id)))
|
||||||
|
|
||||||
(def sql:team-stats
|
|
||||||
"select (select count(*) from project where team_id = ?) as projects,
|
|
||||||
(select count(*) from file as f join project as p on (p.id = f.project_id) where p.team_id = ?) as files")
|
|
||||||
|
|
||||||
(defn retrieve-team-stats
|
|
||||||
[conn team-id]
|
|
||||||
(db/exec-one! conn [sql:team-stats team-id team-id]))
|
|
||||||
|
|
||||||
|
|
||||||
;; --- Query: Team invitations
|
;; --- Query: Team invitations
|
||||||
|
|
||||||
(s/def ::team-id ::us/uuid)
|
(s/def ::team-invitations ::cmd.teams/get-team-invitations)
|
||||||
(s/def ::team-invitations
|
|
||||||
(s/keys :req-un [::profile-id ::team-id]))
|
|
||||||
|
|
||||||
(def sql:team-invitations
|
|
||||||
"select email_to as email, role, (valid_until < now()) as expired
|
|
||||||
from team_invitation where team_id = ? order by valid_until desc")
|
|
||||||
|
|
||||||
(sv/defmethod ::team-invitations
|
(sv/defmethod ::team-invitations
|
||||||
|
{::doc/added "1.0"
|
||||||
|
::doc/deprecated "1.17"}
|
||||||
[{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
|
[{:keys [pool] :as cfg} {:keys [profile-id team-id]}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(check-read-permissions! conn profile-id team-id)
|
(cmd.teams/check-read-permissions! conn profile-id team-id)
|
||||||
(->> (db/exec! conn [sql:team-invitations team-id])
|
(cmd.teams/get-team-invitations conn team-id)))
|
||||||
(mapv #(update % :role keyword)))))
|
|
||||||
|
|
|
@ -17,14 +17,13 @@
|
||||||
[app.main :as main]
|
[app.main :as main]
|
||||||
[app.media]
|
[app.media]
|
||||||
[app.migrations]
|
[app.migrations]
|
||||||
[app.rpc.helpers :as rph]
|
|
||||||
[app.rpc.commands.auth :as cmd.auth]
|
[app.rpc.commands.auth :as cmd.auth]
|
||||||
[app.rpc.commands.files :as files]
|
[app.rpc.commands.files :as files]
|
||||||
[app.rpc.commands.files.create :as files.create]
|
[app.rpc.commands.files.create :as files.create]
|
||||||
[app.rpc.commands.files.update :as files.update]
|
[app.rpc.commands.files.update :as files.update]
|
||||||
|
[app.rpc.commands.teams :as teams]
|
||||||
|
[app.rpc.helpers :as rph]
|
||||||
[app.rpc.mutations.profile :as profile]
|
[app.rpc.mutations.profile :as profile]
|
||||||
[app.rpc.mutations.projects :as projects]
|
|
||||||
[app.rpc.mutations.teams :as teams]
|
|
||||||
[app.util.blob :as blob]
|
[app.util.blob :as blob]
|
||||||
[app.util.services :as sv]
|
[app.util.services :as sv]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
|
@ -172,7 +171,7 @@
|
||||||
(->> (merge {:id (mk-uuid "project" i)
|
(->> (merge {:id (mk-uuid "project" i)
|
||||||
:name (str "project" i)}
|
:name (str "project" i)}
|
||||||
params)
|
params)
|
||||||
(#'projects/create-project conn)))))
|
(#'teams/create-project conn)))))
|
||||||
|
|
||||||
(defn create-file*
|
(defn create-file*
|
||||||
([i params]
|
([i params]
|
||||||
|
@ -254,7 +253,7 @@
|
||||||
([params] (create-project-role* *pool* params))
|
([params] (create-project-role* *pool* params))
|
||||||
([pool {:keys [project-id profile-id role] :or {role :owner}}]
|
([pool {:keys [project-id profile-id role] :or {role :owner}}]
|
||||||
(with-open [conn (db/open pool)]
|
(with-open [conn (db/open pool)]
|
||||||
(#'projects/create-project-role conn {:project-id project-id
|
(#'teams/create-project-role conn {:project-id project-id
|
||||||
:profile-id profile-id
|
:profile-id profile-id
|
||||||
:role role}))))
|
:role role}))))
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(let [team-id (:current-team-id state)]
|
(let [team-id (:current-team-id state)]
|
||||||
(->> (rp/query! :team-members {:team-id team-id})
|
(->> (rp/cmd! :get-team-members {:team-id team-id})
|
||||||
(rx/map team-members-fetched))))))
|
(rx/map team-members-fetched))))))
|
||||||
|
|
||||||
;; --- EVENT: fetch-team-stats
|
;; --- EVENT: fetch-team-stats
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(let [team-id (:current-team-id state)]
|
(let [team-id (:current-team-id state)]
|
||||||
(->> (rp/query! :team-stats {:team-id team-id})
|
(->> (rp/cmd! :get-team-stats {:team-id team-id})
|
||||||
(rx/map team-stats-fetched))))))
|
(rx/map team-stats-fetched))))))
|
||||||
|
|
||||||
;; --- EVENT: fetch-team-invitations
|
;; --- EVENT: fetch-team-invitations
|
||||||
|
@ -146,7 +146,7 @@
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(let [team-id (:current-team-id state)]
|
(let [team-id (:current-team-id state)]
|
||||||
(->> (rp/query! :team-invitations {:team-id team-id})
|
(->> (rp/cmd! :get-team-invitations {:team-id team-id})
|
||||||
(rx/map team-invitations-fetched))))))
|
(rx/map team-invitations-fetched))))))
|
||||||
|
|
||||||
;; --- EVENT: fetch-team-webhooks
|
;; --- EVENT: fetch-team-webhooks
|
||||||
|
@ -384,14 +384,13 @@
|
||||||
(let [{:keys [on-success on-error]
|
(let [{:keys [on-success on-error]
|
||||||
:or {on-success identity
|
:or {on-success identity
|
||||||
on-error rx/throw}} (meta params)]
|
on-error rx/throw}} (meta params)]
|
||||||
(->> (rp/mutation! :create-team {:name name})
|
(->> (rp/cmd! :create-team {:name name})
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/map team-created)
|
(rx/map team-created)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
|
||||||
;; --- EVENT: create-team-with-invitations
|
;; --- EVENT: create-team-with-invitations
|
||||||
|
|
||||||
|
|
||||||
(defn create-team-with-invitations
|
(defn create-team-with-invitations
|
||||||
[{:keys [name emails role] :as params}]
|
[{:keys [name emails role] :as params}]
|
||||||
(us/assert! ::us/string name)
|
(us/assert! ::us/string name)
|
||||||
|
@ -404,7 +403,7 @@
|
||||||
params {:name name
|
params {:name name
|
||||||
:emails #{emails}
|
:emails #{emails}
|
||||||
:role role}]
|
:role role}]
|
||||||
(->> (rp/mutation! :create-team-and-invite-members params)
|
(->> (rp/cmd! :create-team-and-invitations params)
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/map team-created)
|
(rx/map team-created)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
@ -421,7 +420,7 @@
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
(->> (rp/mutation! :update-team params)
|
(->> (rp/cmd! :update-team params)
|
||||||
(rx/ignore)))))
|
(rx/ignore)))))
|
||||||
|
|
||||||
(defn update-team-photo
|
(defn update-team-photo
|
||||||
|
@ -440,7 +439,7 @@
|
||||||
(->> (rx/of file)
|
(->> (rx/of file)
|
||||||
(rx/map di/validate-file)
|
(rx/map di/validate-file)
|
||||||
(rx/map prepare)
|
(rx/map prepare)
|
||||||
(rx/mapcat #(rp/mutation :update-team-photo %))
|
(rx/mapcat #(rp/cmd! :update-team-photo %))
|
||||||
(rx/do on-success)
|
(rx/do on-success)
|
||||||
(rx/map du/fetch-teams)
|
(rx/map du/fetch-teams)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
@ -454,7 +453,7 @@
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(let [team-id (:current-team-id state)
|
(let [team-id (:current-team-id state)
|
||||||
params (assoc params :team-id team-id)]
|
params (assoc params :team-id team-id)]
|
||||||
(->> (rp/mutation! :update-team-member-role params)
|
(->> (rp/cmd! :update-team-member-role params)
|
||||||
(rx/mapcat (fn [_]
|
(rx/mapcat (fn [_]
|
||||||
(rx/of (fetch-team-members)
|
(rx/of (fetch-team-members)
|
||||||
(du/fetch-teams)))))))))
|
(du/fetch-teams)))))))))
|
||||||
|
@ -467,7 +466,7 @@
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(let [team-id (:current-team-id state)
|
(let [team-id (:current-team-id state)
|
||||||
params (assoc params :team-id team-id)]
|
params (assoc params :team-id team-id)]
|
||||||
(->> (rp/mutation! :delete-team-member params)
|
(->> (rp/cmd! :delete-team-member params)
|
||||||
(rx/mapcat (fn [_]
|
(rx/mapcat (fn [_]
|
||||||
(rx/of (fetch-team-members)
|
(rx/of (fetch-team-members)
|
||||||
(du/fetch-teams)))))))))
|
(du/fetch-teams)))))))))
|
||||||
|
@ -487,7 +486,7 @@
|
||||||
params (cond-> {:id team-id}
|
params (cond-> {:id team-id}
|
||||||
(uuid? reassign-to)
|
(uuid? reassign-to)
|
||||||
(assoc :reassign-to reassign-to))]
|
(assoc :reassign-to reassign-to))]
|
||||||
(->> (rp/mutation! :leave-team params)
|
(->> (rp/cmd! :leave-team params)
|
||||||
(rx/tap #(tm/schedule on-success))
|
(rx/tap #(tm/schedule on-success))
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
|
||||||
|
@ -506,7 +505,7 @@
|
||||||
:or {on-success identity
|
:or {on-success identity
|
||||||
on-error rx/throw}} (meta params)
|
on-error rx/throw}} (meta params)
|
||||||
params (dissoc params :resend?)]
|
params (dissoc params :resend?)]
|
||||||
(->> (rp/mutation! :invite-team-member params)
|
(->> (rp/cmd! :create-team-invitations params)
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
|
||||||
|
@ -524,7 +523,7 @@
|
||||||
(let [{:keys [on-success on-error]
|
(let [{:keys [on-success on-error]
|
||||||
:or {on-success identity
|
:or {on-success identity
|
||||||
on-error rx/throw}} (meta params)]
|
on-error rx/throw}} (meta params)]
|
||||||
(->> (rp/mutation! :update-team-invitation-role params)
|
(->> (rp/cmd! :update-team-invitation-role params)
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
|
||||||
|
@ -538,7 +537,7 @@
|
||||||
(let [{:keys [on-success on-error]
|
(let [{:keys [on-success on-error]
|
||||||
:or {on-success identity
|
:or {on-success identity
|
||||||
on-error rx/throw}} (meta params)]
|
on-error rx/throw}} (meta params)]
|
||||||
(->> (rp/mutation! :delete-team-invitation params)
|
(->> (rp/cmd! :delete-team-invitation params)
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
|
||||||
|
@ -608,7 +607,7 @@
|
||||||
(let [{:keys [on-success on-error]
|
(let [{:keys [on-success on-error]
|
||||||
:or {on-success identity
|
:or {on-success identity
|
||||||
on-error rx/throw}} (meta params)]
|
on-error rx/throw}} (meta params)]
|
||||||
(->> (rp/mutation! :delete-team {:id id})
|
(->> (rp/cmd! :delete-team {:id id})
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
(ptk/reify ::fetch-teams
|
(ptk/reify ::fetch-teams
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
(->> (rp/query! :teams)
|
(->> (rp/cmd! :get-teams)
|
||||||
(rx/map teams-fetched)))))
|
(rx/map teams-fetched)))))
|
||||||
|
|
||||||
;; --- EVENT: fetch-profile
|
;; --- EVENT: fetch-profile
|
||||||
|
@ -446,7 +446,7 @@
|
||||||
(ptk/reify ::fetch-team-users
|
(ptk/reify ::fetch-team-users
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
(->> (rp/query! :team-users {:team-id team-id})
|
(->> (rp/cmd! :get-team-users {:team-id team-id})
|
||||||
(rx/map #(partial fetched %)))))))
|
(rx/map #(partial fetched %)))))))
|
||||||
|
|
||||||
(defn fetch-file-comments-users
|
(defn fetch-file-comments-users
|
||||||
|
|
|
@ -265,7 +265,7 @@
|
||||||
(->> (rx/zip (rp/cmd! :get-file {:id file-id :features features})
|
(->> (rx/zip (rp/cmd! :get-file {:id file-id :features features})
|
||||||
(rp/cmd! :get-file-object-thumbnails {:file-id file-id})
|
(rp/cmd! :get-file-object-thumbnails {:file-id file-id})
|
||||||
(rp/query! :project {:id project-id})
|
(rp/query! :project {:id project-id})
|
||||||
(rp/query! :team-users {:file-id file-id})
|
(rp/cmd! :get-team-users {:file-id file-id})
|
||||||
(rp/cmd! :get-profiles-for-file-comments {:file-id file-id :share-id share-id}))
|
(rp/cmd! :get-profiles-for-file-comments {:file-id file-id :share-id share-id}))
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
(rx/map (partial bundle-fetched features))
|
(rx/map (partial bundle-fetched features))
|
||||||
|
|
|
@ -17,6 +17,11 @@
|
||||||
(derive :get-file-libraries ::query)
|
(derive :get-file-libraries ::query)
|
||||||
(derive :get-file-fragment ::query)
|
(derive :get-file-fragment ::query)
|
||||||
(derive :search-files ::query)
|
(derive :search-files ::query)
|
||||||
|
(derive :get-teams ::query)
|
||||||
|
(derive :get-team-users ::query)
|
||||||
|
(derive :get-team-members ::query)
|
||||||
|
(derive :get-team-stats ::query)
|
||||||
|
(derive :get-team-invitations ::query)
|
||||||
|
|
||||||
(defn handle-response
|
(defn handle-response
|
||||||
[{:keys [status body] :as response}]
|
[{:keys [status body] :as response}]
|
||||||
|
|
Loading…
Add table
Reference in a new issue