♻️ Refactor dashboard (add teams)

This commit is contained in:
Andrey Antukh 2020-09-25 14:51:21 +02:00 committed by Alonso Torres
parent 47d347f357
commit b3252ec2b2
52 changed files with 1842 additions and 1421 deletions

View file

@ -2,12 +2,21 @@
;; 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) 2019 Andrey Antukh <niwi@niwi.nz>
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.db
(:require
[clojure.spec.alpha :as s]
[app.common.exceptions :as ex]
[app.config :as cfg]
[app.metrics :as mtx]
[app.util.data :as data]
[app.util.time :as dt]
[app.util.transit :as t]
[clojure.data.json :as json]
[clojure.spec.alpha :as s]
[clojure.string :as str]
[clojure.tools.logging :as log]
[lambdaisland.uri :refer [uri]]
@ -17,19 +26,13 @@
[next.jdbc.optional :as jdbc-opt]
[next.jdbc.result-set :as jdbc-rs]
[next.jdbc.sql :as jdbc-sql]
[next.jdbc.sql.builder :as jdbc-bld]
[app.common.exceptions :as ex]
[app.config :as cfg]
[app.metrics :as mtx]
[app.util.time :as dt]
[app.util.transit :as t]
[app.util.data :as data])
[next.jdbc.sql.builder :as jdbc-bld])
(:import
org.postgresql.util.PGobject
org.postgresql.util.PGInterval
com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory
com.zaxxer.hikari.HikariConfig
com.zaxxer.hikari.HikariDataSource))
com.zaxxer.hikari.HikariDataSource
com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory
org.postgresql.util.PGInterval
org.postgresql.util.PGobject))
(def initsql
(str "SET statement_timeout = 10000;\n"

View file

@ -98,6 +98,16 @@
{:name "0027-mod-file-table-ignore-sync"
:fn (mg/resource "app/migrations/sql/0027-mod-file-table-ignore-sync.sql")}
{:name "0028-add-team-project-profile-rel-table"
:fn (mg/resource "app/migrations/sql/0028-add-team-project-profile-rel-table.sql")}
{:name "0029-del-project-profile-rel-indexes"
:fn (mg/resource "app/migrations/sql/0029-del-project-profile-rel-indexes.sql")}
{:name "0030-mod-file-table-add-missing-index"
:fn (mg/resource "app/migrations/sql/0030-mod-file-table-add-missing-index.sql")}
]})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -78,7 +78,6 @@ VALUES ('00000000-0000-0000-0000-000000000000'::uuid,
true);
CREATE TABLE team_profile_rel (
team_id uuid NOT NULL REFERENCES team(id) ON DELETE CASCADE,
profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE RESTRICT,

View file

@ -28,9 +28,6 @@ CREATE TABLE project_profile_rel (
PRIMARY KEY (profile_id, project_id)
);
COMMENT ON TABLE project_profile_rel
IS 'Relation between projects and profiles (NM)';
CREATE INDEX project_profile_rel__profile_id__idx
ON project_profile_rel(profile_id);

View file

@ -0,0 +1,12 @@
CREATE TABLE team_project_profile_rel (
team_id uuid NOT NULL REFERENCES team(id) ON DELETE CASCADE,
profile_id uuid NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
project_id uuid NOT NULL REFERENCES project(id) ON DELETE CASCADE,
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
is_pinned boolean NOT NULL DEFAULT false,
PRIMARY KEY (team_id, profile_id, project_id)
);

View file

@ -0,0 +1,4 @@
--- Drop duplicate indexes
DROP INDEX IF EXISTS project_profile_rel__project_id__idx;
DROP INDEX IF EXISTS project_profile_rel__profile_id__idx;

View file

@ -0,0 +1 @@
CREATE INDEX IF NOT EXISTS file__project_id__idx ON file (project_id);

View file

@ -61,6 +61,7 @@
(declare create-project)
(declare create-project-profile)
(declare create-team-project-profile)
(s/def ::team-id ::us/uuid)
(s/def ::create-project
@ -70,9 +71,11 @@
(sm/defmutation ::create-project
[params]
(db/with-atomic [conn db/pool]
(let [proj (create-project conn params)]
(create-project-profile conn (assoc params :project-id (:id proj)))
proj)))
(let [proj (create-project conn params)
params (assoc params :project-id (:id proj))]
(create-project-profile conn params)
(create-team-project-profile conn params)
(assoc proj :is-pinned true))))
(defn create-project
[conn {:keys [id profile-id team-id name default?] :as params}]
@ -93,8 +96,31 @@
:is-admin true
:can-edit true}))
(defn create-team-project-profile
[conn {:keys [team-id project-id profile-id] :as params}]
(db/insert! conn :team-project-profile-rel
{:project-id project-id
:profile-id profile-id
:team-id team-id
:is-pinned true}))
;; --- Mutation: Toggle Project Pin
(s/def ::is-pinned ::us/boolean)
(s/def ::toggle-project-pin
(s/keys :req-un [::profile-id ::id ::team-id ::is-pinned]))
(sm/defmutation ::toggle-project-pin
[{:keys [id profile-id team-id is-pinned] :as params}]
(db/with-atomic [conn db/pool]
(db/update! conn :team-project-profile-rel
{:is-pinned is-pinned}
{:profile-id profile-id
:project-id id
:team-id team-id})
nil))
;; --- Mutation: Rename Project
(declare rename-project)

View file

@ -15,6 +15,7 @@
[app.common.uuid :as uuid]
[app.db :as db]
[app.services.mutations :as sm]
[app.services.mutations.projects :as projects]
[app.util.blob :as blob]))
;; --- Helpers & Specs
@ -27,6 +28,7 @@
(declare create-team)
(declare create-team-profile)
(declare create-team-default-project)
(s/def ::create-team
(s/keys :req-un [::profile-id ::name]
@ -35,8 +37,10 @@
(sm/defmutation ::create-team
[params]
(db/with-atomic [conn db/pool]
(let [team (create-team conn params)]
(create-team-profile conn (assoc params :team-id (:id team)))
(let [team (create-team conn params)
params (assoc params :team-id (:id team))]
(create-team-profile conn params)
(create-team-default-project conn params)
team)))
(defn create-team
@ -57,3 +61,11 @@
:is-owner true
:is-admin true
:can-edit true}))
(defn create-team-default-project
[conn {:keys [team-id profile-id] :as params}]
(let [proj (projects/create-project conn {:team-id team-id
:name "Drafts"
:default? true})]
(projects/create-project-profile conn {:project-id (:id proj)
:profile-id profile-id})))

View file

@ -53,16 +53,16 @@
(def ^:private sql:default-team-and-project
"select t.id
from team as t
inner join team_profile_rel as tpr on (tpr.team_id = t.id)
where tpr.profile_id = ?
and tpr.is_owner is true
inner join team_profile_rel as tp on (tp.team_id = t.id)
where tp.profile_id = ?
and tp.is_owner is true
and t.is_default is true
union all
select p.id
from project as p
inner join project_profile_rel as tpr on (tpr.project_id = p.id)
where tpr.profile_id = ?
and tpr.is_owner is true
inner join project_profile_rel as tp on (tp.project_id = p.id)
where tp.profile_id = ?
and tp.is_owner is true
and p.is_default is true")
(defn retrieve-additional-data

View file

@ -60,7 +60,6 @@
(s/def ::team-id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::projects
(s/keys :req-un [::profile-id ::team-id]))
@ -68,31 +67,37 @@
[{:keys [profile-id team-id]}]
(with-open [conn (db/open)]
(teams/check-read-permissions! conn profile-id team-id)
(retrieve-projects conn team-id)))
(retrieve-projects conn profile-id team-id)))
(def sql:projects
"select p.*,
tpp.is_pinned,
(select count(*) from file as f
where f.project_id = p.id
and deleted_at is null)
where f.project_id = p.id
and deleted_at is null) as count
from project as p
left join team_project_profile_rel as tpp
on (tpp.project_id = p.id and
tpp.team_id = p.team_id and
tpp.profile_id = ?)
where p.team_id = ?
and p.deleted_at is null
order by p.modified_at desc")
(defn retrieve-projects
[conn team-id]
(db/exec! conn [sql:projects team-id]))
[conn profile-id team-id]
(db/exec! conn [sql:projects profile-id team-id]))
;; --- Query: Projec by ID
(s/def ::project-id ::us/uuid)
(s/def ::project-by-id
(s/keys :req-un [::profile-id ::project-id]))
;; --- Query: Project
(sq/defquery ::project-by-id
[{:keys [profile-id project-id]}]
(s/def ::id ::us/uuid)
(s/def ::project
(s/keys :req-un [::profile-id ::id]))
(sq/defquery ::project
[{:keys [profile-id id]}]
(with-open [conn (db/open)]
(let [project (db/get-by-id conn :project project-id)]
(let [project (db/get-by-id conn :project id)]
(check-edition-permissions! conn profile-id project)
project)))

View file

@ -18,19 +18,17 @@
[app.services.queries.projects :as projects :refer [retrieve-projects]]
[app.services.queries.files :refer [decode-row-xf]]))
(def sql:project-recent-files
"select f.*
from file as f
where f.project_id = ?
and f.deleted_at is null
order by f.modified_at desc
limit 5")
(defn recent-by-project
[conn profile-id project]
(let [project-id (:id project)]
(projects/check-edition-permissions! conn profile-id project)
(into [] decode-row-xf (db/exec! conn [sql:project-recent-files project-id]))))
(def sql:recent-files
"with recent_files as (
select f.*, row_number() over w as row_num
from file as f
join project as p on (p.id = f.project_id)
where p.team_id = ?
and p.deleted_at is null
window w as (partition by f.project_id order by f.modified_at desc)
order by f.modified_at desc
)
select * from recent_files where row_num <= 6;")
(s/def ::team-id ::us/uuid)
(s/def ::profile-id ::us/uuid)
@ -42,9 +40,7 @@
[{:keys [profile-id team-id]}]
(with-open [conn (db/open)]
(teams/check-read-permissions! conn profile-id team-id)
(->> (retrieve-projects conn team-id)
;; Retrieve for each proyect the 5 more recent files
(map (partial recent-by-project conn profile-id))
;; Change the structure so it's a map with project-id as keys
(flatten)
(group-by :project-id))))
(let [files (db/exec! conn [sql:recent-files team-id])]
(into [] decode-row-xf files))))

View file

@ -15,6 +15,7 @@
[app.common.uuid :as uuid]
[app.db :as db]
[app.services.queries :as sq]
[app.services.queries.profile :as profile]
[app.util.blob :as blob]))
;; --- Team Edition Permissions
@ -43,3 +44,54 @@
(when-not row
(ex/raise :type :validation
:code :not-authorized))))
;; --- Query: Teams
(declare retrieve-teams)
(s/def ::profile-id ::us/uuid)
(s/def ::teams
(s/keys :req-un [::profile-id]))
(sq/defquery ::teams
[{:keys [profile-id]}]
(with-open [conn (db/open)]
(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 t.created_at asc")
(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])))
;; --- Query: Projec by ID
(declare retrieve-team-projects)
(declare retrieve-team)
(s/def ::id ::us/uuid)
(s/def ::team
(s/keys :req-un [::profile-id ::id]))
(sq/defquery ::team
[{:keys [profile-id id]}]
(with-open [conn (db/open)]
(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=?")]
(db/exec-one! conn [sql (:default-team-id defaults) profile-id team-id])))