diff --git a/backend/resources/migrations/0002.users.sql b/backend/resources/migrations/0002.users.sql
index 8833f7821e..cbf4c7d111 100644
--- a/backend/resources/migrations/0002.users.sql
+++ b/backend/resources/migrations/0002.users.sql
@@ -10,10 +10,11 @@ CREATE TABLE users (
email text NOT NULL,
photo text NOT NULL,
password text NOT NULL,
- metadata bytea NOT NULL
+
+ metadata bytea NULL DEFAULT NULL
);
-CREATE TABLE IF NOT EXISTS user_storage (
+CREATE TABLE IF NOT EXISTS user_attrs (
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
@@ -25,7 +26,7 @@ CREATE TABLE IF NOT EXISTS user_storage (
PRIMARY KEY (key, user_id)
);
-CREATE TABLE user_tokens (
+CREATE TABLE IF NOT EXISTS tokens (
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token text NOT NULL,
@@ -35,7 +36,7 @@ CREATE TABLE user_tokens (
PRIMARY KEY (token, user_id)
);
-CREATE TABLE sessions (
+CREATE TABLE IF NOT EXISTS sessions (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
@@ -56,17 +57,19 @@ VALUES ('00000000-0000-0000-0000-000000000000'::uuid,
'!',
'{}');
-CREATE UNIQUE INDEX users_username_idx
- ON users USING btree (username)
+CREATE UNIQUE INDEX users__username__idx
+ ON users (username)
WHERE deleted_at is null;
-CREATE UNIQUE INDEX users_email_idx
- ON users USING btree (email)
+CREATE UNIQUE INDEX users__email__idx
+ ON users (email)
WHERE deleted_at is null;
-CREATE TRIGGER users_modified_at_tgr BEFORE UPDATE ON users
+CREATE TRIGGER users__modified_at__tgr
+BEFORE UPDATE ON users
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
-CREATE TRIGGER user_storage_modified_at_tgr BEFORE UPDATE ON user_storage
+CREATE TRIGGER user_attrs__modified_at__tgr
+BEFORE UPDATE ON user_attrs
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
diff --git a/backend/resources/migrations/0003.projects.sql b/backend/resources/migrations/0003.projects.sql
index 561d3c1305..42808f8728 100644
--- a/backend/resources/migrations/0003.projects.sql
+++ b/backend/resources/migrations/0003.projects.sql
@@ -8,43 +8,150 @@ CREATE TABLE IF NOT EXISTS projects (
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
deleted_at timestamptz DEFAULT NULL,
- name text NOT NULL
+ name text NOT NULL,
+ metadata bytea NULL DEFAULT NULL
);
-CREATE TABLE IF NOT EXISTS projects_users (
+CREATE TABLE IF NOT EXISTS project_users (
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
- role text NOT NULL,
+ can_edit boolean DEFAULT false,
PRIMARY KEY (user_id, project_id)
);
+CREATE TABLE IF NOT EXISTS project_files (
+ id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
+ user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
+
+ name text NOT NULL,
+
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
+ modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
+ deleted_at timestamptz DEFAULT NULL,
+
+ metadata bytea NULL DEFAULT NULL
+);
+
+CREATE TABLE IF NOT EXISTS project_file_users (
+ file_id uuid NOT NULL REFERENCES project_files(id) ON DELETE CASCADE,
+ user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
+ modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
+
+ can_edit boolean DEFAULT false,
+
+ PRIMARY KEY (user_id, file_id)
+);
+
+CREATE TABLE IF NOT EXISTS project_pages (
+ id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
+
+ user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ file_id uuid NOT NULL REFERENCES project_files(id) ON DELETE CASCADE,
+
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
+ modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
+ deleted_at timestamptz DEFAULT NULL,
+
+ version bigint NOT NULL,
+ ordering smallint NOT NULL,
+
+ name text NOT NULL,
+ data bytea NOT NULL,
+ metadata bytea NULL DEFAULT NULL
+);
+
+CREATE TABLE IF NOT EXISTS project_page_history (
+ id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
+
+ user_id uuid NULL REFERENCES users(id) ON DELETE SET NULL,
+ page_id uuid NOT NULL REFERENCES project_pages(id) ON DELETE CASCADE,
+
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
+ modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
+ version bigint NOT NULL DEFAULT 0,
+
+ pinned bool NOT NULL DEFAULT false,
+ label text NOT NULL DEFAULT '',
+
+ data bytea NOT NULL
+);
+
-- Indexes
-CREATE INDEX projects_user_idx ON projects(user_id);
-CREATE INDEX projects_users_user_id_idx ON projects_users(project_id);
-CREATE INDEX projects_users_project_id_idx ON projects_users(user_id);
+CREATE INDEX projects__user_id__idx ON projects(user_id);
+
+CREATE INDEX project_files__user_id__idx ON project_files(user_id);
+CREATE INDEX project_files__project_id__idx ON project_files(project_id);
+
+CREATE INDEX project_pages__user_id__idx ON project_pages(user_id);
+CREATE INDEX project_pages__file_id__idx ON project_pages(file_id);
+
+CREATE INDEX project_page_history__page_id__idx ON project_page_history(page_id);
+CREATE INDEX project_page_history__user_id__idx ON project_page_history(user_id);
-- Triggers
CREATE OR REPLACE FUNCTION handle_project_insert()
RETURNS TRIGGER AS $$
BEGIN
- INSERT INTO projects_users (user_id, project_id, role)
- VALUES (NEW.user_id, NEW.id, 'owner');
+ INSERT INTO project_users (user_id, project_id, can_edit)
+ VALUES (NEW.user_id, NEW.id, true);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION handle_page_update()
+ RETURNS TRIGGER AS $pagechange$
+ DECLARE
+ current_dt timestamptz := clock_timestamp();
+ proj_id uuid;
+ BEGIN
+ UPDATE project_files
+ SET modified_at = current_dt
+ WHERE id = OLD.file_id
+ RETURNING project_id
+ INTO STRICT proj_id;
+
+ --- Update projects modified_at attribute when a
+ --- page of that project is modified.
+ UPDATE projects
+ SET modified_at = current_dt
+ WHERE id = proj_id;
+
+ RETURN NEW;
+ END;
+$pagechange$ LANGUAGE plpgsql;
+
CREATE TRIGGER projects_on_insert_tgr
AFTER INSERT ON projects
FOR EACH ROW EXECUTE PROCEDURE handle_project_insert();
-CREATE TRIGGER projects_modified_at_tgr
+CREATE TRIGGER pages__on_update__tgr
+BEFORE UPDATE ON project_pages
+ FOR EACH ROW EXECUTE PROCEDURE handle_page_update();
+
+
+CREATE TRIGGER projects__modified_at__tgr
BEFORE UPDATE ON projects
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
+
+CREATE TRIGGER project_files__modified_at__tgr
+BEFORE UPDATE ON project_files
+ FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
+
+CREATE TRIGGER project_pages__modified_at__tgr
+BEFORE UPDATE ON project_pages
+ FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
+
+CREATE TRIGGER project_page_history__modified_at__tgr
+BEFORE UPDATE ON project_page_history
+ FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
diff --git a/backend/resources/migrations/0005.emails.sql b/backend/resources/migrations/0004.emails.sql
similarity index 100%
rename from backend/resources/migrations/0005.emails.sql
rename to backend/resources/migrations/0004.emails.sql
diff --git a/backend/resources/migrations/0004.pages.sql b/backend/resources/migrations/0004.pages.sql
deleted file mode 100644
index a867a700c5..0000000000
--- a/backend/resources/migrations/0004.pages.sql
+++ /dev/null
@@ -1,65 +0,0 @@
--- Tables
-
-CREATE TABLE IF NOT EXISTS pages (
- id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
-
- user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
- project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
-
- created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
- modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
- deleted_at timestamptz DEFAULT NULL,
-
- version bigint NOT NULL,
- ordering smallint NOT NULL,
-
- name text NOT NULL,
- data bytea NOT NULL,
- metadata bytea NOT NULL
-);
-
-CREATE TABLE IF NOT EXISTS pages_history (
- id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
-
- user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
- page_id uuid NOT NULL REFERENCES pages(id) ON DELETE CASCADE,
-
- created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
- modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
- version bigint NOT NULL DEFAULT 0,
-
- pinned bool NOT NULL DEFAULT false,
- label text NOT NULL DEFAULT '',
- data bytea NOT NULL,
- metadata bytea NOT NULL
-);
-
--- Indexes
-
-CREATE INDEX pages_project_idx ON pages(project_id);
-CREATE INDEX pages_user_idx ON pages(user_id);
-CREATE INDEX pages_history_page_idx ON pages_history(page_id);
-CREATE INDEX pages_history_user_idx ON pages_history(user_id);
-
--- Triggers
-
-CREATE OR REPLACE FUNCTION handle_page_update()
- RETURNS TRIGGER AS $pagechange$
- BEGIN
- --- Update projects modified_at attribute when a
- --- page of that project is modified.
- UPDATE projects SET modified_at = clock_timestamp()
- WHERE id = OLD.project_id;
-
- RETURN NEW;
- END;
-$pagechange$ LANGUAGE plpgsql;
-
-CREATE TRIGGER page_on_update_tgr BEFORE UPDATE ON pages
- FOR EACH ROW EXECUTE PROCEDURE handle_page_update();
-
-CREATE TRIGGER pages_modified_at_tgr BEFORE UPDATE ON pages
- FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
-
-CREATE TRIGGER pages_history_modified_at_tgr BEFORE UPDATE ON pages
- FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
diff --git a/backend/resources/migrations/0006.images.sql b/backend/resources/migrations/0005.images.sql
similarity index 64%
rename from backend/resources/migrations/0006.images.sql
rename to backend/resources/migrations/0005.images.sql
index ca6dea90f8..b97c91004b 100644
--- a/backend/resources/migrations/0006.images.sql
+++ b/backend/resources/migrations/0005.images.sql
@@ -1,6 +1,6 @@
-- Tables
-CREATE TABLE IF NOT EXISTS images_collections (
+CREATE TABLE IF NOT EXISTS image_collections (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@@ -22,7 +22,7 @@ CREATE TABLE IF NOT EXISTS images (
width int NOT NULL,
height int NOT NULL,
mimetype text NOT NULL,
- collection_id uuid REFERENCES images_collections(id)
+ collection_id uuid REFERENCES image_collections(id)
ON DELETE SET NULL
DEFAULT NULL,
name text NOT NULL,
@@ -31,20 +31,17 @@ CREATE TABLE IF NOT EXISTS images (
-- Indexes
-CREATE INDEX images_collections_user_idx
- ON images_collections (user_id);
-
-CREATE INDEX images_collection_idx
- ON images (collection_id);
-
-CREATE INDEX images_user_idx
- ON images (user_id);
+CREATE INDEX image_collections__user_id__idx ON image_collections (user_id);
+CREATE INDEX images__collection_id__idx ON images (collection_id);
+CREATE INDEX images__user_id__idx ON images (user_id);
-- Triggers
-CREATE TRIGGER images_collections_modified_at_tgr BEFORE UPDATE ON images_collections
+CREATE TRIGGER image_collections__modified_at__tgr
+BEFORE UPDATE ON image_collections
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
-CREATE TRIGGER images_modified_at_tgr BEFORE UPDATE ON images
- FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
+CREATE TRIGGER images__modified_at__tgr
+BEFORE UPDATE ON images
+ FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
diff --git a/backend/resources/migrations/0007.icons.sql b/backend/resources/migrations/0006.icons.sql
similarity index 59%
rename from backend/resources/migrations/0007.icons.sql
rename to backend/resources/migrations/0006.icons.sql
index 12bbbf5e09..476ce70b85 100644
--- a/backend/resources/migrations/0007.icons.sql
+++ b/backend/resources/migrations/0006.icons.sql
@@ -1,6 +1,6 @@
-- Tables
-CREATE TABLE IF NOT EXISTS icons_collections (
+CREATE TABLE IF NOT EXISTS icon_collections (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@@ -23,27 +23,23 @@ CREATE TABLE IF NOT EXISTS icons (
content text NOT NULL,
metadata bytea NOT NULL,
- collection_id uuid REFERENCES icons_collections(id)
+ collection_id uuid REFERENCES icon_collections(id)
ON DELETE SET NULL
DEFAULT NULL
);
-- Indexes
-CREATE INDEX icon_colections_user_idx
- ON icons_collections (user_id);
-
-CREATE INDEX icons_user_idx
- ON icons (user_id);
-
-CREATE INDEX icons_collection_idx
- ON icons (collection_id);
+CREATE INDEX icon_colections__user_id__idx ON icon_collections (user_id);
+CREATE INDEX icons__user_id__idx ON icons(user_id);
+CREATE INDEX icons__collection_id__idx ON icons(collection_id);
-- Triggers
-CREATE TRIGGER icons_collections_modified_at_tgr BEFORE UPDATE ON icons_collections
- FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
-
-CREATE TRIGGER icons_modified_at_tgr BEFORE UPDATE ON icons
- FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
+CREATE TRIGGER icon_collections__modified_at__tgr
+BEFORE UPDATE ON icon_collections
+ FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
+CREATE TRIGGER icons__modified_at__tgr
+BEFORE UPDATE ON icons
+ FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
diff --git a/backend/src/uxbox/config.clj b/backend/src/uxbox/config.clj
index 20f8eb01d2..1133e319f5 100644
--- a/backend/src/uxbox/config.clj
+++ b/backend/src/uxbox/config.clj
@@ -50,7 +50,7 @@
:email-reply-to (lookup-env env :uxbox-email-reply-to "no-reply@uxbox.io")
:email-from (lookup-env env :uxbox-email-from "no-reply@uxbox.io")
- :smtp-host (lookup-env env :uxbox-smtp-host "smtp")
+ :smtp-host (lookup-env env :uxbox-smtp-host "localhost")
:smtp-port (lookup-env env :uxbox-smtp-port 25)
:smtp-user (lookup-env env :uxbox-smtp-user nil)
:smtp-password (lookup-env env :uxbox-smtp-password nil)
diff --git a/backend/src/uxbox/db.clj b/backend/src/uxbox/db.clj
index a421437e1e..84d92e0261 100644
--- a/backend/src/uxbox/db.clj
+++ b/backend/src/uxbox/db.clj
@@ -13,6 +13,7 @@
[uxbox.config :as cfg]
[uxbox.core :refer [system]]
[uxbox.util.data :as data]
+ [uxbox.util.exceptions :as ex]
[uxbox.util.pgsql :as pg]
[vertx.core :as vx])
(:import io.vertx.core.buffer.Buffer))
@@ -33,17 +34,22 @@
:start (create-pool cfg/config system))
(defmacro with-atomic
- [& args]
- `(pg/with-atomic ~@args))
+ [bindings & args]
+ `(pg/with-atomic ~bindings (p/do! ~@args)))
(def row-xfm
(comp (map pg/row->map)
(map data/normalize-attrs)))
(defmacro query
- [& args]
- `(pg/query ~@args {:xfm row-xfm}))
-
+ [conn sql]
+ `(-> (pg/query ~conn ~sql {:xfm row-xfm})
+ (p/catch' (fn [err#]
+ (ex/raise :type :database-error
+ :cause err#)))))
(defmacro query-one
- [& args]
- `(pg/query-one ~@args {:xfm row-xfm}))
+ [conn sql]
+ `(-> (pg/query-one ~conn ~sql {:xfm row-xfm})
+ (p/catch' (fn [err#]
+ (ex/raise :type :database-error
+ :cause err#)))))
diff --git a/backend/src/uxbox/fixtures.clj b/backend/src/uxbox/fixtures.clj
index 857dc27128..1d78710596 100644
--- a/backend/src/uxbox/fixtures.clj
+++ b/backend/src/uxbox/fixtures.clj
@@ -7,6 +7,7 @@
(ns uxbox.fixtures
"A initial fixtures."
(:require
+ [clojure.tools.logging :as log]
[buddy.hashers :as hashers]
[mount.core :as mount]
[promesa.core :as p]
@@ -20,26 +21,27 @@
(defn- mk-uuid
[prefix & args]
- (uuid/namespaced uuid/oid (apply str prefix args)))
+ (uuid/namespaced uuid/oid (apply str prefix (interpose "-" args))))
;; --- Users creation
(def create-user-sql
- "insert into users (id, fullname, username, email, password, metadata, photo)
- values ($1, $2, $3, $4, $5, $6, $7)
+ "insert into users (id, fullname, username, email, password, photo)
+ values ($1, $2, $3, $4, $5, $6)
returning *;")
+(def password (hashers/encrypt "123123"))
+
(defn create-user
- [conn i]
- (println "create user" i)
- (db/query-one conn [create-user-sql
- (mk-uuid "user" i)
- (str "User " i)
- (str "user" i)
- (str "user" i ".test@uxbox.io")
- (hashers/encrypt "123123")
- (blob/encode {})
- ""]))
+ [conn user-index]
+ (log/info "create user" user-index)
+ (let [sql create-user-sql
+ id (mk-uuid "user" user-index)
+ fullname (str "User " user-index)
+ username (str "user" user-index)
+ email (str "user" user-index ".test@uxbox.io")
+ photo ""]
+ (db/query-one conn [sql id fullname username email password photo])))
;; --- Projects creation
@@ -49,29 +51,46 @@
returning *;")
(defn create-project
- [conn [pjid uid]]
- (println "create project" pjid "(for user=" uid ")")
- (db/query-one conn [create-project-sql
- (mk-uuid "project" pjid uid)
- (mk-uuid "user" uid)
- (str "sample project " pjid)]))
+ [conn [project-index user-index]]
+ (log/info "create project" user-index project-index)
+ (let [sql create-project-sql
+ id (mk-uuid "project" project-index user-index)
+ user-id (mk-uuid "user" user-index)
+ name (str "sample project " project-index)]
+ (db/query-one conn [sql id user-id name])))
-;; --- Pages creation
+;; --- Create Page Files
+
+(def create-file-sql
+ "insert into project_files (id, user_id, project_id, name)
+ values ($1, $2, $3, $4) returning id")
+
+(defn create-file
+ [conn [file-index project-index user-index]]
+ (log/info "create page file" user-index project-index file-index)
+ (let [sql create-file-sql
+ id (mk-uuid "page-file" file-index project-index user-index)
+ user-id (mk-uuid "user" user-index)
+ project-id (mk-uuid "project" project-index user-index)
+ name (str "Sample file " file-index)]
+ (db/query-one conn [sql id user-id project-id name])))
+
+;; --- Create Pages
(def create-page-sql
- "insert into pages (id, user_id, project_id, name,
+ "insert into project_pages (id, user_id, file_id, name,
version, ordering, data, metadata)
values ($1, $2, $3, $4, $5, $6, $7, $8)
returning id;")
(def create-page-history-sql
- "insert into pages_history (page_id, user_id, version, data, metadata)
- values ($1, $2, $3, $4, $5)
+ "insert into project_page_history (page_id, user_id, version, data)
+ values ($1, $2, $3, $4)
returning id;")
(defn create-page
- [conn [pjid paid uid]]
- (println "create page" paid "(for project=" pjid ", user=" uid ")")
+ [conn [page-index file-index project-index user-index]]
+ (log/info "create page" user-index project-index file-index page-index)
(let [canvas {:id (mk-uuid "canvas" 1)
:name "Canvas-1"
:type :canvas
@@ -82,29 +101,61 @@
data {:shapes []
:canvas [(:id canvas)]
:shapes-by-id {(:id canvas) canvas}}
+
+ sql1 create-page-sql
+ sql2 create-page-history-sql
+
+ id (mk-uuid "page" page-index file-index project-index user-index)
+ user-id (mk-uuid "user" user-index)
+ file-id (mk-uuid "page-file" file-index project-index user-index)
+ name (str "page " page-index)
+ version 0
+ ordering page-index
data (blob/encode data)
mdata (blob/encode {})]
(p/do!
- (db/query-one conn [create-page-sql
- (mk-uuid "page" pjid paid uid)
- (mk-uuid "user" uid)
- (mk-uuid "project" pjid uid)
- (str "page " paid)
- 0
- paid
- data
- mdata])
- (db/query-one conn [create-page-history-sql
- (mk-uuid "page" pjid paid uid)
- (mk-uuid "user" uid)
- 0
- data
- mdata]))))
+ (db/query-one conn [sql1 id user-id file-id name version ordering data mdata])
+ #_(db/query-one conn [sql2 id user-id version data]))))
+(def preset-small
+ {:users 50
+ :projects 5
+ :files 5
+ :pages 3})
-(def num-users 5)
-(def num-projects 5)
-(def num-pages 5)
+(def preset-medium
+ {:users 500
+ :projects 20
+ :files 5
+ :pages 3})
+
+(def preset-big
+ {:users 5000
+ :projects 50
+ :files 5
+ :pages 4})
+
+(defn run
+ [opts]
+ (db/with-atomic [conn db/pool]
+ (p/do!
+ (p/run! #(create-user conn %) (range (:users opts)))
+ (p/run! #(create-project conn %)
+ (for [user-index (range (:users opts))
+ project-index (range (:projects opts))]
+ [project-index user-index]))
+ (p/run! #(create-file conn %)
+ (for [user-index (range (:users opts))
+ project-index (range (:projects opts))
+ file-index (range (:files opts))]
+ [file-index project-index user-index]))
+ (p/run! #(create-page conn %)
+ (for [user-index (range (:users opts))
+ project-index (range (:projects opts))
+ file-index (range (:files opts))
+ page-index (range (:pages opts))]
+ [page-index file-index project-index user-index]))
+ (p/promise nil))))
(defn -main
[& args]
@@ -115,18 +166,12 @@
#'uxbox.db/pool
#'uxbox.migrations/migrations})
(mount/start))
- @(db/with-atomic [conn db/pool]
- (p/do!
- (p/run! #(create-user conn %) (range num-users))
- (p/run! #(create-project conn %)
- (for [uid (range num-users)
- pjid (range num-projects)]
- [pjid uid]))
- (p/run! #(create-page conn %)
- (for [pjid(range num-projects)
- paid (range num-pages)
- uid (range num-users)]
- [pjid paid uid]))
- (p/promise 1)))
+ (let [preset (case (first args)
+ (nil "small") preset-small
+ "medium" preset-medium
+ "big" preset-big
+ preset-small)]
+ (log/info "Using preset:" (pr-str preset))
+ (deref (run preset)))
(finally
(mount/stop))))
diff --git a/backend/src/uxbox/http/errors.clj b/backend/src/uxbox/http/errors.clj
index 6143716672..0863e1ae1a 100644
--- a/backend/src/uxbox/http/errors.clj
+++ b/backend/src/uxbox/http/errors.clj
@@ -8,6 +8,7 @@
"A errors handling for the http server."
(:require
[clojure.tools.logging :as log]
+ [cuerdas.core :as str]
[io.aviso.exception :as e]))
(defmulti handle-exception
@@ -16,9 +17,18 @@
(defmethod handle-exception :validation
[err req]
- (let [response (ex-data err)]
- {:status 400
- :body response}))
+ (let [header (get-in req [:headers "accept"])
+ response (ex-data err)]
+ (cond
+ (and (str/starts-with? header "text/html")
+ (= :spec-validation (:code response)))
+ {:status 400
+ :headers {"content-type" "text/html"}
+ :body (str "
" (:explain response) "
\n")}
+
+ :else
+ {:status 400
+ :body response})))
(defmethod handle-exception :not-found
[err req]
@@ -26,6 +36,10 @@
{:status 404
:body response}))
+(defmethod handle-exception :service-error
+ [err req]
+ (handle-exception (.getCause err) req))
+
(defmethod handle-exception :parse
[err req]
{:status 400
diff --git a/backend/src/uxbox/migrations.clj b/backend/src/uxbox/migrations.clj
index 098d42cf82..22de8de269 100644
--- a/backend/src/uxbox/migrations.clj
+++ b/backend/src/uxbox/migrations.clj
@@ -26,18 +26,15 @@
{:desc "Initial projects tables"
:name "0003-projects"
:fn (mg/resource "migrations/0003.projects.sql")}
- {:desc "Initial pages tables"
- :name "0004-pages"
- :fn (mg/resource "migrations/0004.pages.sql")}
{:desc "Initial emails related tables"
- :name "0005-emails"
- :fn (mg/resource "migrations/0005.emails.sql")}
+ :name "0004-emails"
+ :fn (mg/resource "migrations/0004.emails.sql")}
{:desc "Initial images tables"
- :name "0006-images"
- :fn (mg/resource "migrations/0006.images.sql")}
+ :name "0005-images"
+ :fn (mg/resource "migrations/0005.images.sql")}
{:desc "Initial icons tables"
- :name "0007-icons"
- :fn (mg/resource "migrations/0007.icons.sql")}
+ :name "0006-icons"
+ :fn (mg/resource "migrations/0006.icons.sql")}
]})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/backend/src/uxbox/services/init.clj b/backend/src/uxbox/services/init.clj
index e3a846d2d3..bda9a1af85 100644
--- a/backend/src/uxbox/services/init.clj
+++ b/backend/src/uxbox/services/init.clj
@@ -13,20 +13,22 @@
[]
(require 'uxbox.services.queries.icons)
(require 'uxbox.services.queries.images)
- (require 'uxbox.services.queries.pages)
- (require 'uxbox.services.queries.profiles)
(require 'uxbox.services.queries.projects)
- (require 'uxbox.services.queries.user-storage))
+ (require 'uxbox.services.queries.project-files)
+ (require 'uxbox.services.queries.project-pages)
+ (require 'uxbox.services.queries.users)
+ (require 'uxbox.services.queries.user-attrs))
(defn- load-mutation-services
[]
- (require 'uxbox.services.mutations.auth)
(require 'uxbox.services.mutations.icons)
(require 'uxbox.services.mutations.images)
(require 'uxbox.services.mutations.projects)
- (require 'uxbox.services.mutations.pages)
- (require 'uxbox.services.mutations.profiles)
- (require 'uxbox.services.mutations.user-storage))
+ (require 'uxbox.services.mutations.project-files)
+ (require 'uxbox.services.mutations.project-pages)
+ (require 'uxbox.services.mutations.auth)
+ (require 'uxbox.services.mutations.users)
+ (require 'uxbox.services.mutations.user-attrs))
(defstate query-services
:start (load-query-services))
diff --git a/backend/src/uxbox/services/mutations.clj b/backend/src/uxbox/services/mutations.clj
index f62a08d898..6bfa98c50c 100644
--- a/backend/src/uxbox/services/mutations.clj
+++ b/backend/src/uxbox/services/mutations.clj
@@ -6,11 +6,13 @@
(ns uxbox.services.mutations
(:require
- [uxbox.util.dispatcher :as uds]))
+ [uxbox.util.dispatcher :as uds]
+ [uxbox.util.exceptions :as ex]))
(uds/defservice handle
{:dispatch-by ::type
:interceptors [uds/spec-interceptor
+ uds/wrap-errors
#_logging-interceptor
#_context-interceptor]})
diff --git a/backend/src/uxbox/services/mutations/pages.clj b/backend/src/uxbox/services/mutations/pages.clj
deleted file mode 100644
index a70fe71254..0000000000
--- a/backend/src/uxbox/services/mutations/pages.clj
+++ /dev/null
@@ -1,174 +0,0 @@
-;; 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) 2019 Andrey Antukh
-
-(ns uxbox.services.mutations.pages
- (:require
- [clojure.spec.alpha :as s]
- [promesa.core :as p]
- [uxbox.db :as db]
- [uxbox.util.spec :as us]
- [uxbox.services.mutations :as sm]
- [uxbox.services.util :as su]
- [uxbox.services.queries.pages :refer [decode-row]]
- [uxbox.util.sql :as sql]
- [uxbox.util.blob :as blob]
- [uxbox.util.uuid :as uuid]))
-
-;; --- Helpers & Specs
-
-;; TODO: validate `:data` and `:metadata`
-
-(s/def ::id ::us/uuid)
-(s/def ::name ::us/string)
-(s/def ::data any?)
-(s/def ::user ::us/uuid)
-(s/def ::project-id ::us/uuid)
-(s/def ::metadata any?)
-(s/def ::ordering ::us/number)
-
-;; --- Mutation: Create Page
-
-(declare create-page)
-
-(s/def ::create-page
- (s/keys :req-un [::data ::user ::project-id ::name ::metadata]
- :opt-un [::id]))
-
-(sm/defmutation ::create-page
- [params]
- (create-page db/pool params))
-
-(defn create-page
- [conn {:keys [id user project-id name ordering data metadata] :as params}]
- (let [sql "insert into pages (id, user_id, project_id, name,
- ordering, data, metadata, version)
- values ($1, $2, $3, $4, $5, $6, $7, 0)
- returning *"
- id (or id (uuid/next))
- data (blob/encode data)
- mdata (blob/encode metadata)]
- (-> (db/query-one db/pool [sql id user project-id name ordering data mdata])
- (p/then' decode-row))))
-
-;; --- Mutation: Update Page
-
-(s/def ::update-page
- (s/keys :req-un [::data ::user ::project-id ::name ::data ::metadata ::id]))
-
-(letfn [(select-for-update [conn id]
- (let [sql "select p.id, p.version
- from pages as p
- where p.id = $1
- and deleted_at is null
- for update;"]
- (-> (db/query-one conn [sql id])
- (p/then' su/raise-not-found-if-nil))))
-
- (update-page [conn {:keys [id name version data metadata user]}]
- (let [sql "update pages
- set name = $1,
- version = $2,
- data = $3,
- metadata = $4
- where id = $5
- and user_id = $6"]
- (-> (db/query-one conn [sql name version data metadata id user])
- (p/then' su/constantly-nil))))
-
- (update-history [conn {:keys [user id version data metadata]}]
- (let [sql "insert into pages_history (user_id, page_id, version, data, metadata)
- values ($1, $2, $3, $4, $5)"]
- (-> (db/query-one conn [sql user id version data metadata])
- (p/then' su/constantly-nil))))]
-
- (sm/defmutation ::update-page
- [{:keys [id data metadata] :as params}]
- (db/with-atomic [conn db/pool]
- (-> (select-for-update conn id)
- (p/then (fn [{:keys [id version]}]
- (let [data (blob/encode data)
- mdata (blob/encode metadata)
- version (inc version)
- params (assoc params
- :id id
- :version version
- :data data
- :metadata mdata)]
- (p/do! (update-page conn params)
- (update-history conn params)
- (select-keys params [:id :version])))))))))
-
-;; --- Mutation: Rename Page
-
-(s/def ::rename-page
- (s/keys :req-un [::id ::name ::user]))
-
-(sm/defmutation ::rename-page
- [{:keys [id name user]}]
- (let [sql "update pages
- set name = $3
- where id = $1
- and user_id = $2
- and deleted_at is null"]
- (-> (db/query-one db/pool [sql id user name])
- (p/then su/constantly-nil))))
-
-;; --- Mutation: Update Page Metadata
-
-(s/def ::update-page-metadata
- (s/keys :req-un [::user ::project-id ::name ::metadata ::id]))
-
-(sm/defmutation ::update-page-metadata
- [{:keys [id user project-id name metadata]}]
- (let [sql "update pages
- set name = $3,
- metadata = $4
- where id = $1
- and user_id = $2
- and deleted_at is null
- returning *"
- mdata (blob/encode metadata)]
- (-> (db/query-one db/pool [sql id user name mdata])
- (p/then' decode-row))))
-
-;; --- Mutation: Delete Page
-
-(s/def ::delete-page
- (s/keys :req-un [::user ::id]))
-
-(sm/defmutation ::delete-page
- [{:keys [id user]}]
- (let [sql "update pages
- set deleted_at = clock_timestamp()
- where id = $1
- and user_id = $2
- and deleted_at is null
- returning id"]
- (-> (db/query-one db/pool [sql id user])
- (p/then su/raise-not-found-if-nil)
- (p/then su/constantly-nil))))
-
-;; ;; --- Update Page History
-
-;; (defn update-page-history
-;; [conn {:keys [user id label pinned]}]
-;; (let [sqlv (sql/update-page-history {:user user
-;; :id id
-;; :label label
-;; :pinned pinned})]
-;; (some-> (db/fetch-one conn sqlv)
-;; (decode-row))))
-
-;; (s/def ::label ::us/string)
-;; (s/def ::update-page-history
-;; (s/keys :req-un [::user ::id ::pinned ::label]))
-
-;; (sm/defmutation :update-page-history
-;; {:doc "Update page history"
-;; :spec ::update-page-history}
-;; [params]
-;; (with-open [conn (db/connection)]
-;; (update-page-history conn params)))
diff --git a/backend/src/uxbox/services/mutations/project_files.clj b/backend/src/uxbox/services/mutations/project_files.clj
new file mode 100644
index 0000000000..521aaf97b7
--- /dev/null
+++ b/backend/src/uxbox/services/mutations/project_files.clj
@@ -0,0 +1,142 @@
+;; 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) 2019 Andrey Antukh
+
+(ns uxbox.services.mutations.project-files
+ (:require
+ [clojure.spec.alpha :as s]
+ [promesa.core :as p]
+ [uxbox.db :as db]
+ [uxbox.util.spec :as us]
+ [uxbox.services.mutations :as sm]
+ [uxbox.services.mutations.projects :as proj]
+ [uxbox.services.util :as su]
+ [uxbox.util.exceptions :as ex]
+ [uxbox.util.blob :as blob]
+ [uxbox.util.uuid :as uuid]))
+
+;; --- Helpers & Specs
+
+(s/def ::id ::us/uuid)
+(s/def ::name ::us/string)
+(s/def ::user ::us/uuid)
+(s/def ::project-id ::us/uuid)
+
+;; --- Permissions Checks
+
+;; A query that returns all (not-equal) user assignations for a
+;; requested file (project level and file level).
+
+;; Is important having the condition of user_id in the join and not in
+;; where clause because we need all results independently if value is
+;; true, false or null; with that, the empty result means there are no
+;; file found.
+
+(def ^:private sql:file-permissions
+ "select pf.id,
+ pfu.can_edit as can_edit
+ from project_files as pf
+ left join project_file_users as pfu
+ on (pfu.file_id = pf.id and pfu.user_id = $1)
+ where pf.id = $2
+ union all
+ select pf.id,
+ pu.can_edit as can_edit
+ from project_files as pf
+ left join project_users as pu
+ on (pf.project_id = pu.project_id and pu.user_id = $1)
+ where pf.id = $2")
+
+(defn check-edition-permissions!
+ [conn user file-id]
+ (-> (db/query conn [sql:file-permissions user file-id])
+ (p/then' seq)
+ (p/then' su/raise-not-found-if-nil)
+ (p/then' (fn [rows]
+ (when-not (some :can-edit rows)
+ (ex/raise :type :validation
+ :code :not-authorized))))))
+
+;; --- Mutation: Create Project
+
+(declare create-file)
+(declare create-page)
+
+(s/def ::create-project-file
+ (s/keys :req-un [::user ::name ::project-id]
+ :opt-un [::id]))
+
+(sm/defmutation ::create-project-file
+ [{:keys [user project-id] :as params}]
+ (db/with-atomic [conn db/pool]
+ (proj/check-edition-permissions! conn user project-id)
+ (p/let [file (create-file conn params)]
+ (create-page conn (assoc params :file-id (:id file)))
+ file)))
+
+(defn create-file
+ [conn {:keys [id user name project-id] :as params}]
+ (let [id (or id (uuid/next))
+ sql "insert into project_files (id, user_id, project_id, name)
+ values ($1, $2, $3, $4) returning *"]
+ (db/query-one conn [sql id user project-id name])))
+
+(defn- create-page
+ "Creates an initial page for the file."
+ [conn {:keys [user file-id] :as params}]
+ (let [id (uuid/next)
+ name "Page 1"
+ data (blob/encode {})
+ sql "insert into project_pages (id, user_id, file_id, name, version,
+ ordering, data)
+ values ($1, $2, $3, $4, 0, 1, $5) returning id"]
+ (db/query-one conn [sql id user file-id name data])))
+
+;; --- Mutation: Update Project
+
+(declare update-file)
+
+(s/def ::update-project-file
+ (s/keys :req-un [::user ::name ::id]))
+
+(sm/defmutation ::update-project-file
+ [{:keys [id user] :as params}]
+ (db/with-atomic [conn db/pool]
+ (check-edition-permissions! conn user id)
+ (update-file conn params)))
+
+(defn- update-file
+ [conn {:keys [id name user] :as params}]
+ (let [sql "update project_files
+ set name = $2
+ where id = $1
+ and deleted_at is null"]
+ (-> (db/query-one conn [sql id name])
+ (p/then' su/constantly-nil))))
+
+;; --- Mutation: Delete Project
+
+(declare delete-file)
+
+(s/def ::delete-project-file
+ (s/keys :req-un [::id ::user]))
+
+(sm/defmutation ::delete-project-file
+ [{:keys [id user] :as params}]
+ (db/with-atomic [conn db/pool]
+ (check-edition-permissions! conn user id)
+ (delete-file conn params)))
+
+(def ^:private sql:delete-file
+ "update project_files
+ set deleted_at = clock_timestamp()
+ where id = $1
+ and deleted_at is null")
+
+(defn delete-file
+ [conn {:keys [id] :as params}]
+ (let [sql sql:delete-file]
+ (-> (db/query-one conn [sql id])
+ (p/then' su/constantly-nil))))
diff --git a/backend/src/uxbox/services/mutations/project_pages.clj b/backend/src/uxbox/services/mutations/project_pages.clj
new file mode 100644
index 0000000000..579b53855c
--- /dev/null
+++ b/backend/src/uxbox/services/mutations/project_pages.clj
@@ -0,0 +1,190 @@
+;; 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) 2019 Andrey Antukh
+
+(ns uxbox.services.mutations.project-pages
+ (:require
+ [clojure.spec.alpha :as s]
+ [promesa.core :as p]
+ [uxbox.db :as db]
+ [uxbox.services.mutations :as sm]
+ [uxbox.services.mutations.project-files :as files]
+ [uxbox.services.queries.project-pages :refer [decode-row]]
+ [uxbox.services.util :as su]
+ [uxbox.util.blob :as blob]
+ [uxbox.util.spec :as us]
+ [uxbox.util.sql :as sql]
+ [uxbox.util.uuid :as uuid]))
+
+;; --- Helpers & Specs
+
+;; TODO: validate `:data` and `:metadata`
+
+(s/def ::id ::us/uuid)
+(s/def ::name ::us/string)
+(s/def ::data any?)
+(s/def ::user ::us/uuid)
+(s/def ::project-id ::us/uuid)
+(s/def ::metadata any?)
+(s/def ::ordering ::us/number)
+
+;; --- Mutation: Create Page
+
+(declare create-page)
+
+(s/def ::create-project-page
+ (s/keys :req-un [::user ::file-id ::name ::ordering ::metadata ::data]
+ :opt-un [::id]))
+
+(sm/defmutation ::create-project-page
+ [{:keys [user file-id] :as params}]
+ (db/with-atomic [conn db/pool]
+ (files/check-edition-permissions! conn user file-id)
+ (create-page conn params)))
+
+(defn create-page
+ [conn {:keys [id user file-id name ordering data metadata] :as params}]
+ (let [sql "insert into project_pages (id, user_id, file_id, name,
+ ordering, data, metadata, version)
+ values ($1, $2, $3, $4, $5, $6, $7, 0)
+ returning *"
+ id (or id (uuid/next))
+ data (blob/encode data)
+ mdata (blob/encode metadata)]
+ (-> (db/query-one conn [sql id user file-id name ordering data mdata])
+ (p/then' decode-row))))
+
+;; --- Mutation: Update Page
+
+(declare select-page-for-update)
+(declare update-page)
+(declare update-history)
+
+(s/def ::update-project-page-data
+ (s/keys :req-un [::id ::user ::data]))
+
+(sm/defmutation ::update-project-page-data
+ [{:keys [id user data] :as params}]
+ (db/with-atomic [conn db/pool]
+ (p/let [{:keys [version file-id]} (select-page-for-update conn id)]
+ (files/check-edition-permissions! conn user file-id)
+ (let [data (blob/encode data)
+ version (inc version)
+ params (assoc params :id id :data data :version version)]
+ (p/do! (update-page conn params)
+ (update-history conn params)
+ (select-keys params [:id :version]))))))
+
+(defn- select-page-for-update
+ [conn id]
+ (let [sql "select p.id, p.version, p.file_id
+ from project_pages as p
+ where p.id = $1
+ and deleted_at is null
+ for update;"]
+ (-> (db/query-one conn [sql id])
+ (p/then' su/raise-not-found-if-nil))))
+
+(defn- update-page
+ [conn {:keys [id name version data metadata]}]
+ (let [sql "update project_pages
+ set version = $1,
+ data = $2
+ where id = $3"]
+ (-> (db/query-one conn [sql version data id])
+ (p/then' su/constantly-nil))))
+
+(defn- update-history
+ [conn {:keys [user id version data]}]
+ (let [sql "insert into project_page_history (user_id, page_id, version, data)
+ values ($1, $2, $3, $4)"]
+ (-> (db/query-one conn [sql user id version data])
+ (p/then' su/constantly-nil))))
+
+;; --- Mutation: Rename Page
+
+(declare rename-page)
+
+(s/def ::rename-project-page
+ (s/keys :req-un [::id ::name ::user]))
+
+(sm/defmutation ::rename-project-page
+ [{:keys [id name user]}]
+ (db/with-atomic [conn db/pool]
+ (p/let [page (select-page-for-update conn id)]
+ (files/check-edition-permissions! conn user (:file-id page))
+ (rename-page conn (assoc page :name name)))))
+
+(defn- rename-page
+ [conn {:keys [id name] :as params}]
+ (let [sql "update project_pages
+ set name = $2
+ where id = $1
+ and deleted_at is null"]
+ (-> (db/query-one db/pool [sql id name])
+ (p/then su/constantly-nil))))
+
+;; --- Mutation: Update Page Metadata
+
+;; (s/def ::update-page-metadata
+;; (s/keys :req-un [::user ::project-id ::name ::metadata ::id]))
+
+;; (sm/defmutation ::update-page-metadata
+;; [{:keys [id user project-id name metadata]}]
+;; (let [sql "update pages
+;; set name = $3,
+;; metadata = $4
+;; where id = $1
+;; and user_id = $2
+;; and deleted_at is null
+;; returning *"
+;; mdata (blob/encode metadata)]
+;; (-> (db/query-one db/pool [sql id user name mdata])
+;; (p/then' decode-row))))
+
+;; --- Mutation: Delete Page
+
+(declare delete-page)
+
+(s/def ::delete-project-page
+ (s/keys :req-un [::user ::id]))
+
+(sm/defmutation ::delete-project-page
+ [{:keys [id user]}]
+ (db/with-atomic [conn db/pool]
+ (p/let [page (select-page-for-update conn id)]
+ (files/check-edition-permissions! conn user (:file-id page))
+ (delete-page conn id))))
+
+(defn- delete-page
+ [conn id]
+ (let [sql "update project_pages
+ set deleted_at = clock_timestamp()
+ where id = $1
+ and deleted_at is null"]
+ (-> (db/query-one conn [sql id])
+ (p/then su/constantly-nil))))
+
+;; --- Update Page History
+
+;; (defn update-page-history
+;; [conn {:keys [user id label pinned]}]
+;; (let [sqlv (sql/update-page-history {:user user
+;; :id id
+;; :label label
+;; :pinned pinned})]
+;; (some-> (db/fetch-one conn sqlv)
+;; (decode-row))))
+
+;; (s/def ::label ::us/string)
+;; (s/def ::update-page-history
+;; (s/keys :req-un [::user ::id ::pinned ::label]))
+
+;; (sm/defmutation :update-page-history
+;; {:doc "Update page history"
+;; :spec ::update-page-history}
+;; [params]
+;; (with-open [conn (db/connection)]
+;; (update-page-history conn params)))
diff --git a/backend/src/uxbox/services/mutations/projects.clj b/backend/src/uxbox/services/mutations/projects.clj
index 1a408c6c2a..ea67552669 100644
--- a/backend/src/uxbox/services/mutations/projects.clj
+++ b/backend/src/uxbox/services/mutations/projects.clj
@@ -13,6 +13,7 @@
[uxbox.services.mutations :as sm]
[uxbox.services.util :as su]
[uxbox.util.blob :as blob]
+ [uxbox.util.exceptions :as ex]
[uxbox.util.uuid :as uuid]))
;; --- Helpers & Specs
@@ -22,6 +23,27 @@
(s/def ::token ::us/string)
(s/def ::user ::us/uuid)
+;; --- Permissions Checks
+
+(def ^:private sql:project-permissions
+ "select p.id,
+ pu.can_edit as can_edit
+ from projects as p
+ inner join project_users as pu
+ on (pu.project_id = p.id)
+ where pu.user_id = $1
+ and p.id = $2
+ for update of p;")
+
+(defn check-edition-permissions!
+ [conn user project-id]
+ (-> (db/query-one conn [sql:project-permissions user project-id])
+ (p/then' su/raise-not-found-if-nil)
+ (p/then' (fn [{:keys [id can-edit] :as proj}]
+ (when-not can-edit
+ (ex/raise :type :validation
+ :code :not-authorized))))))
+
;; --- Mutation: Create Project
(declare create-project)
@@ -31,11 +53,9 @@
:opt-un [::id]))
(sm/defmutation ::create-project
- [{:keys [id user name] :as params}]
- (let [id (or id (uuid/next))
- sql "insert into projects (id, user_id, name)
- values ($1, $2, $3) returning *"]
- (db/query-one db/pool [sql id user name])))
+ [params]
+ (db/with-atomic [conn db/pool]
+ (create-project conn params)))
(defn create-project
[conn {:keys [id user name] :as params}]
@@ -46,32 +66,49 @@
;; --- Mutation: Update Project
+(declare update-project)
+
(s/def ::update-project
(s/keys :req-un [::user ::name ::id]))
(sm/defmutation ::update-project
- [{:keys [id name user] :as params}]
+ [{:keys [id user] :as params}]
+ (db/with-atomic [conn db/pool]
+ (check-edition-permissions! conn user id)
+ (update-project conn params)))
+
+(defn update-project
+ [conn {:keys [id name user] :as params}]
(let [sql "update projects
set name = $3
where id = $1
and user_id = $2
and deleted_at is null
returning *"]
- (db/query-one db/pool [sql id user name])))
+ (db/query-one conn [sql id user name])))
;; --- Mutation: Delete Project
+(declare delete-project)
+
(s/def ::delete-project
(s/keys :req-un [::id ::user]))
(sm/defmutation ::delete-project
[{:keys [id user] :as params}]
- (let [sql "update projects
- set deleted_at = clock_timestamp()
- where id = $1
- and user_id = $2
- and deleted_at is null
- returning id"]
- (-> (db/query-one db/pool [sql id user])
- (p/then' su/raise-not-found-if-nil)
+ (db/with-atomic [conn db/pool]
+ (check-edition-permissions! conn user id)
+ (delete-project conn params)))
+
+(def ^:private sql:delete-project
+ "update projects
+ set deleted_at = clock_timestamp()
+ where id = $1
+ and deleted_at is null
+ returning id")
+
+(defn delete-project
+ [conn {:keys [id user] :as params}]
+ (let [sql sql:delete-project]
+ (-> (db/query-one conn [sql id])
(p/then' su/constantly-nil))))
diff --git a/backend/src/uxbox/services/mutations/user_storage.clj b/backend/src/uxbox/services/mutations/user_attrs.clj
similarity index 75%
rename from backend/src/uxbox/services/mutations/user_storage.clj
rename to backend/src/uxbox/services/mutations/user_attrs.clj
index 9328fbb5c2..9d459ebb6f 100644
--- a/backend/src/uxbox/services/mutations/user_storage.clj
+++ b/backend/src/uxbox/services/mutations/user_attrs.clj
@@ -4,14 +4,14 @@
;;
;; Copyright (c) 2019 Andrey Antukh
-(ns uxbox.services.mutations.user-storage
+(ns uxbox.services.mutations.user-attrs
(:require
[clojure.spec.alpha :as s]
[promesa.core :as p]
[uxbox.db :as db]
[uxbox.services.mutations :as sm]
[uxbox.services.util :as su]
- [uxbox.services.queries.user-storage :refer [decode-row]]
+ [uxbox.services.queries.user-attrs :refer [decode-row]]
[uxbox.util.blob :as blob]
[uxbox.util.spec :as us]))
@@ -21,12 +21,12 @@
(s/def ::key ::us/string)
(s/def ::val any?)
-(s/def ::upsert-user-storage-entry
+(s/def ::upsert-user-attr
(s/keys :req-un [::key ::val ::user]))
-(sm/defmutation ::upsert-user-storage-entry
+(sm/defmutation ::upsert-user-attr
[{:keys [key val user] :as params}]
- (let [sql "insert into user_storage (key, val, user_id)
+ (let [sql "insert into user_attrs (key, val, user_id)
values ($1, $2, $3)
on conflict (user_id, key)
do update set val = $2"
@@ -36,12 +36,12 @@
;; --- Delete KVStore
-(s/def ::delete-user-storage-entry
+(s/def ::delete-user-attr
(s/keys :req-un [::key ::user]))
-(sm/defmutation ::delete-user-storage-entry
+(sm/defmutation ::delete-user-attr
[{:keys [user key] :as params}]
- (let [sql "delete from user_storage
+ (let [sql "delete from user_attrs
where user_id = $2
and key = $1"]
(-> (db/query-one db/pool [sql key user])
diff --git a/backend/src/uxbox/services/mutations/profiles.clj b/backend/src/uxbox/services/mutations/users.clj
similarity index 94%
rename from backend/src/uxbox/services/mutations/profiles.clj
rename to backend/src/uxbox/services/mutations/users.clj
index af221e7db9..308775ff93 100644
--- a/backend/src/uxbox/services/mutations/profiles.clj
+++ b/backend/src/uxbox/services/mutations/users.clj
@@ -4,7 +4,7 @@
;;
;; Copyright (c) 2016 Andrey Antukh
-(ns uxbox.services.mutations.profiles
+(ns uxbox.services.mutations.users
(:require
[buddy.hashers :as hashers]
[clojure.spec.alpha :as s]
@@ -19,10 +19,10 @@
[uxbox.media :as media]
[uxbox.services.mutations :as sm]
[uxbox.services.util :as su]
- [uxbox.services.queries.profiles :refer [get-profile
- decode-profile-row
- strip-private-attrs
- resolve-thumbnail]]
+ [uxbox.services.queries.users :refer [get-profile
+ decode-profile-row
+ strip-private-attrs
+ resolve-thumbnail]]
[uxbox.util.blob :as blob]
[uxbox.util.exceptions :as ex]
[uxbox.util.spec :as us]
@@ -56,7 +56,7 @@
and id != $1
) as val"]
(p/let [res1 (db/query-one conn [sql1 id username])
- res2 (db/query-one conn [sql2 id email])]
+ res2 (db/query-one conn [sql2 id email])]
(when (:val res1)
(ex/raise :type :validation
:code ::username-already-exists))
@@ -83,9 +83,7 @@
(s/def ::update-profile
(s/keys :req-un [::id ::username ::email ::fullname ::metadata]))
-(sm/defmutation :update-profile
- {:doc "Update self profile."
- :spec ::update-profile}
+(sm/defmutation ::update-profile
[params]
(db/with-atomic [conn db/pool]
(-> (p/resolved params)
@@ -134,9 +132,7 @@
(def valid-image-types?
#{"image/jpeg", "image/png", "image/webp"})
-(sm/defmutation :update-profile-photo
- {:doc "Update profile photo."
- :spec ::update-profile-photo}
+(sm/defmutation ::update-profile-photo
[{:keys [user file] :as params}]
(letfn [(store-photo [{:keys [name path] :as upload}]
(let [filename (fs/name name)
@@ -149,16 +145,17 @@
set photo = $1
where id = $2
and deleted_at is null
- returning *"]
+ returning id, photo"]
(-> (db/query-one db/pool [sql (str path) user])
(p/then' su/raise-not-found-if-nil)
- (p/then' strip-private-attrs)
+ ;; (p/then' strip-private-attrs)
(p/then resolve-thumbnail))))]
(when-not (valid-image-types? (:mtype file))
(ex/raise :type :validation
:code :image-type-not-allowed
:hint "Seems like you are uploading an invalid image."))
+
(-> (store-photo file)
(p/then update-user-photo))))
diff --git a/backend/src/uxbox/services/queries.clj b/backend/src/uxbox/services/queries.clj
index bfa48b6d2c..97639d2c05 100644
--- a/backend/src/uxbox/services/queries.clj
+++ b/backend/src/uxbox/services/queries.clj
@@ -11,6 +11,7 @@
(uds/defservice handle
{:dispatch-by ::type
:interceptors [uds/spec-interceptor
+ uds/wrap-errors
#_logging-interceptor
#_context-interceptor]})
diff --git a/backend/src/uxbox/services/queries/pages.clj b/backend/src/uxbox/services/queries/pages.clj
deleted file mode 100644
index d432d4bcee..0000000000
--- a/backend/src/uxbox/services/queries/pages.clj
+++ /dev/null
@@ -1,92 +0,0 @@
-;; 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) 2019 Andrey Antukh
-
-(ns uxbox.services.queries.pages
- (:require
- [clojure.spec.alpha :as s]
- [promesa.core :as p]
- [uxbox.db :as db]
- [uxbox.services.queries :as sq]
- [uxbox.util.blob :as blob]
- [uxbox.util.spec :as us]
- [uxbox.util.sql :as sql]))
-
-;; --- Helpers & Specs
-
-(declare decode-row)
-
-(s/def ::id ::us/uuid)
-(s/def ::user ::us/uuid)
-(s/def ::project-id ::us/uuid)
-
-;; --- Query: Pages by Project
-
-(s/def ::pages-by-project
- (s/keys :req-un [::user ::project-id]))
-
-(sq/defquery ::pages-by-project
- [{:keys [user project-id] :as params}]
- (let [sql "select pg.*,
- pg.data,
- pg.metadata
- from pages as pg
- where pg.user_id = $2
- and pg.project_id = $1
- and pg.deleted_at is null
- order by pg.created_at asc;"]
- (-> (db/query db/pool [sql project-id user])
- (p/then #(mapv decode-row %)))))
-
-;; --- Query: Page by Id
-
-(s/def ::page
- (s/keys :req-un [::user ::id]))
-
-(sq/defquery ::page
- [{:keys [user id] :as params}]
- (let [sql "select pg.*,
- pg.data,
- pg.metadata
- from pages as pg
- where pg.user_id = $2
- and pg.id = $1
- and pg.deleted_at is null"]
- (-> (db/query-one db/pool [sql id user])
- (p/then' decode-row))))
-
-;; --- Query: Page History
-
-(s/def ::page-id ::us/uuid)
-(s/def ::max ::us/integer)
-(s/def ::pinned ::us/boolean)
-(s/def ::since ::us/integer)
-
-(s/def ::page-history
- (s/keys :req-un [::page-id ::user]
- :opt-un [::max ::pinned ::since]))
-
-(sq/defquery ::page-history
- [{:keys [page-id user since max pinned] :or {since Long/MAX_VALUE max 10}}]
- (let [sql (-> (sql/from ["pages_history" "ph"])
- (sql/select "ph.*")
- (sql/where ["ph.user_id = ?" user]
- ["ph.page_id = ?" page-id]
- ["ph.version < ?" since]
- (when pinned
- ["ph.pinned = ?" true]))
- (sql/order "ph.version desc")
- (sql/limit max))]
- (-> (db/query db/pool (sql/fmt sql))
- (p/then (partial mapv decode-row)))))
-
-;; --- Helpers
-
-(defn decode-row
- [{:keys [data metadata] :as row}]
- (when row
- (cond-> row
- data (assoc :data (blob/decode data))
- metadata (assoc :metadata (blob/decode metadata)))))
diff --git a/backend/src/uxbox/services/queries/project_files.clj b/backend/src/uxbox/services/queries/project_files.clj
new file mode 100644
index 0000000000..1e3effa590
--- /dev/null
+++ b/backend/src/uxbox/services/queries/project_files.clj
@@ -0,0 +1,55 @@
+;; 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) 2019 Andrey Antukh
+
+(ns uxbox.services.queries.project-files
+ (:require
+ [clojure.spec.alpha :as s]
+ [promesa.core :as p]
+ [uxbox.db :as db]
+ [uxbox.services.queries :as sq]
+ [uxbox.util.blob :as blob]
+ [uxbox.util.spec :as us]))
+
+(declare decode-row)
+
+;; --- Helpers & Specs
+
+(s/def ::id ::us/uuid)
+(s/def ::name ::us/string)
+(s/def ::project-id ::us/uuid)
+(s/def ::user ::us/uuid)
+
+;; --- Query: Project Files
+
+(def ^:private sql:project-files
+ "select pf.*,
+ array_agg(pp.id) as pages
+ from project_files as pf
+ inner join projects as p on (pf.project_id = p.id)
+ inner join project_users as pu on (p.id = pu.project_id)
+ left join project_pages as pp on (pf.id = pp.file_id)
+ where pu.user_id = $1
+ and pu.project_id = $2
+ and pu.can_edit = true
+ group by pf.id
+ order by pf.created_at asc;")
+
+(s/def ::project-files
+ (s/keys :req-un [::user ::project-id]))
+
+(sq/defquery ::project-files
+ [{:keys [user project-id] :as params}]
+ (-> (db/query db/pool [sql:project-files user project-id])
+ (p/then' (partial mapv decode-row))))
+
+;; --- Helpers
+
+(defn decode-row
+ [{:keys [metadata pages] :as row}]
+ (when row
+ (cond-> row
+ pages (assoc :pages (vec (remove nil? pages)))
+ metadata (assoc :metadata (blob/decode metadata)))))
diff --git a/backend/src/uxbox/services/queries/project_pages.clj b/backend/src/uxbox/services/queries/project_pages.clj
new file mode 100644
index 0000000000..3ba860c5f5
--- /dev/null
+++ b/backend/src/uxbox/services/queries/project_pages.clj
@@ -0,0 +1,145 @@
+;; 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) 2019 Andrey Antukh
+
+(ns uxbox.services.queries.project-pages
+ (:require
+ [clojure.spec.alpha :as s]
+ [promesa.core :as p]
+ [uxbox.db :as db]
+ [uxbox.services.queries :as sq]
+ [uxbox.services.util :as su]
+ [uxbox.util.blob :as blob]
+ [uxbox.util.spec :as us]
+ [uxbox.util.sql :as sql]))
+
+;; --- Helpers & Specs
+
+(declare decode-row)
+
+(s/def ::id ::us/uuid)
+(s/def ::user ::us/uuid)
+(s/def ::project-id ::us/uuid)
+(s/def ::file-id ::us/uuid)
+
+(def ^:private sql:generic-project-pages
+ "select pp.*
+ from project_pages as pp
+ inner join project_files as pf on (pf.id = pp.file_id)
+ inner join projects as p on (p.id = pf.project_id)
+ left join project_users as pu on (pu.project_id = p.id)
+ left join project_file_users as pfu on (pfu.file_id = pf.id)
+ where ((pfu.user_id = $1 and pfu.can_edit = true) or
+ (pu.user_id = $1 and pu.can_edit = true))
+ order by pp.created_at")
+
+;; --- Query: Project Pages (By File ID)
+
+(def ^:private sql:project-pages
+ (str "with pages as (" sql:generic-project-pages ")"
+ " select * from pages where file_id = $2"))
+
+;; (defn project-pages-sql
+;; [user]
+;; (-> (sql/from ["project_pages" "pp"])
+;; (sql/join ["project_files" "pf"] "pf.id = pp.file_id")
+;; (sql/join ["projects" "p"] "p.id = pf.project_id")
+;; (sql/ljoin ["project_users", "pu"] "pu.project_id = p.id")
+;; (sql/ljoin ["project_file_users", "pfu"] "pfu.file_id = pf.id")
+;; (sql/select "pp.*")
+;; (sql/where ["((pfu.user_id = ? and pfu.can_edit = true) or
+;; (pu.user_id = ? and pu.can_edit = true))" user user])
+;; (sql/order "pp.created_at")))
+
+;; (let [sql (-> (project-pages-sql user)
+;; (sql/where ["pp.file_id = ?" file-id])
+;; (sql/fmt))]
+;; (-> (db/query db/pool sql)
+;; (p/then #(mapv decode-row %)))))
+
+(s/def ::project-pages
+ (s/keys :req-un [::user ::file-id]))
+
+(sq/defquery ::project-pages
+ [{:keys [user file-id] :as params}]
+ (let [sql sql:project-pages]
+ (-> (db/query db/pool [sql user file-id])
+ (p/then #(mapv decode-row %)))))
+
+;; --- Query: Project Page (By ID)
+
+(def ^:private sql:project-page
+ (str "with pages as (" sql:generic-project-pages ")"
+ " select * from pages where id = $2"))
+
+(defn retrieve-page
+ [conn {:keys [user id] :as params}]
+ (let [sql sql:project-page]
+ (-> (db/query-one conn [sql user id])
+ (p/then' su/raise-not-found-if-nil)
+ (p/then' decode-row))))
+
+(s/def ::project-page
+ (s/keys :req-un [::user ::id]))
+
+(sq/defquery ::project-page
+ [{:keys [user id] :as params}]
+ (retrieve-page db/pool params))
+
+;; --- Query: Project Page History (by Page ID)
+
+;; (def ^:private sql:generic-page-history
+;; "select pph.*
+;; from project_page_history as pph
+;; where pph.page_id = $2
+;; and pph.version < $3
+;; order by pph.version < desc")
+
+;; (def ^:private sql:page-history
+;; (str "with history as (" sql:generic-page-history ")"
+;; " select * from history limit $4"))
+
+;; (def ^:private sql:pinned-page-history
+;; (str "with history as (" sql:generic-page-history ")"
+;; " select * from history where pinned = true limit $4"))
+
+(s/def ::page-id ::us/uuid)
+(s/def ::max ::us/integer)
+(s/def ::pinned ::us/boolean)
+(s/def ::since ::us/integer)
+
+(s/def ::page-history
+ (s/keys :req-un [::page-id ::user]
+ :opt-un [::max ::pinned ::since]))
+
+(defn retrieve-page-history
+ [{:keys [page-id user since max pinned] :or {since Long/MAX_VALUE max 10}}]
+ (let [sql (-> (sql/from ["pages_history" "ph"])
+ (sql/select "ph.*")
+ (sql/where ["ph.user_id = ?" user]
+ ["ph.page_id = ?" page-id]
+ ["ph.version < ?" since]
+ (when pinned
+ ["ph.pinned = ?" true]))
+ (sql/order "ph.version desc")
+ (sql/limit max))]
+ (-> (db/query db/pool (sql/fmt sql))
+ (p/then (partial mapv decode-row)))))
+
+(sq/defquery ::page-history
+ [{:keys [page-id user] :as params}]
+ (db/with-atomic [conn db/pool]
+ (p/do! (retrieve-page conn {:id page-id :user user})
+ (retrieve-page-history conn params))))
+
+
+;; --- Helpers
+
+(defn decode-row
+ [{:keys [data metadata] :as row}]
+ (when row
+ (cond-> row
+ data (assoc :data (blob/decode data))
+ metadata (assoc :metadata (blob/decode metadata)))))
diff --git a/backend/src/uxbox/services/queries/projects.clj b/backend/src/uxbox/services/queries/projects.clj
index c3cfa47f6a..392b227edb 100644
--- a/backend/src/uxbox/services/queries/projects.clj
+++ b/backend/src/uxbox/services/queries/projects.clj
@@ -13,6 +13,8 @@
[uxbox.util.blob :as blob]
[uxbox.util.spec :as us]))
+(declare decode-row)
+
;; --- Helpers & Specs
(s/def ::id ::us/uuid)
@@ -22,19 +24,26 @@
;; --- Query: Projects
+;; (def ^:private projects-sql
+;; "select distinct on (p.id, p.created_at)
+;; p.*,
+;; array_agg(pg.id) over (
+;; partition by p.id
+;; order by pg.created_at
+;; range between unbounded preceding and unbounded following
+;; ) as pages
+;; from projects as p
+;; left join pages as pg
+;; on (pg.project_id = p.id)
+;; where p.user_id = $1
+;; order by p.created_at asc")
+
(def ^:private projects-sql
- "select distinct on (p.id, p.created_at)
- p.*,
- array_agg(pg.id) over (
- partition by p.id
- order by pg.created_at
- range between unbounded preceding and unbounded following
- ) as pages
- from projects as p
- left join pages as pg
- on (pg.project_id = p.id)
- where p.user_id = $1
- order by p.created_at asc")
+ "select p.*
+ from project_users as pu
+ inner join projects as p on (p.id = pu.project_id)
+ where pu.can_edit = true
+ and pu.user_id = $1;")
(s/def ::projects
(s/keys :req-un [::user]))
@@ -42,5 +51,12 @@
(sq/defquery ::projects
[{:keys [user] :as params}]
(-> (db/query db/pool [projects-sql user])
- (p/then (fn [rows]
- (mapv #(update % :pages vec) rows)))))
+ (p/then' (partial mapv decode-row))))
+
+;; --- Helpers
+
+(defn decode-row
+ [{:keys [metadata] :as row}]
+ (when row
+ (cond-> row
+ metadata (assoc :metadata (blob/decode metadata)))))
diff --git a/backend/src/uxbox/services/queries/user_storage.clj b/backend/src/uxbox/services/queries/user_attrs.clj
similarity index 85%
rename from backend/src/uxbox/services/queries/user_storage.clj
rename to backend/src/uxbox/services/queries/user_attrs.clj
index 49a316959a..4498e2a46a 100644
--- a/backend/src/uxbox/services/queries/user_storage.clj
+++ b/backend/src/uxbox/services/queries/user_attrs.clj
@@ -4,7 +4,7 @@
;;
;; Copyright (c) 2019 Andrey Antukh
-(ns uxbox.services.queries.user-storage
+(ns uxbox.services.queries.user-attrs
(:require
[clojure.spec.alpha :as s]
[promesa.core :as p]
@@ -20,13 +20,13 @@
(cond-> row
val (assoc :val (blob/decode val)))))
-(s/def ::user-storage-entry
+(s/def ::user-attr
(s/keys :req-un [::key ::user]))
-(sq/defquery ::user-storage-entry
+(sq/defquery ::user-attr
[{:keys [key user]}]
(let [sql "select kv.*
- from user_storage as kv
+ from user_attrs as kv
where kv.user_id = $2
and kv.key = $1"]
(-> (db/query-one db/pool [sql key user])
diff --git a/backend/src/uxbox/services/queries/profiles.clj b/backend/src/uxbox/services/queries/users.clj
similarity index 94%
rename from backend/src/uxbox/services/queries/profiles.clj
rename to backend/src/uxbox/services/queries/users.clj
index 52c72a7b5d..55982c859b 100644
--- a/backend/src/uxbox/services/queries/profiles.clj
+++ b/backend/src/uxbox/services/queries/users.clj
@@ -4,7 +4,7 @@
;;
;; Copyright (c) 2016 Andrey Antukh
-(ns uxbox.services.queries.profiles
+(ns uxbox.services.queries.users
(:require
[clojure.spec.alpha :as s]
[promesa.core :as p]
@@ -51,9 +51,7 @@
(s/def ::profile
(s/keys :req-un [::user]))
-(sq/defquery :profile
- {:doc "Retrieve the user profile."
- :spec ::profile}
+(sq/defquery ::profile
[{:keys [user] :as params}]
(-> (get-profile db/pool user)
(p/then' strip-private-attrs)))
diff --git a/backend/src/uxbox/util/dispatcher.clj b/backend/src/uxbox/util/dispatcher.clj
index 75b6fc3fe6..db9588b540 100644
--- a/backend/src/uxbox/util/dispatcher.clj
+++ b/backend/src/uxbox/util/dispatcher.clj
@@ -28,7 +28,7 @@
IDispatcher
(add [this key f metadata]
(.put ^Map reg key (MapEntry/create f metadata))
- nil)
+ this)
clojure.lang.IDeref
(deref [_]
@@ -56,7 +56,7 @@
(defn dispatcher?
[v]
- (instance? Dispatcher v))
+ (instance? IDispatcher v))
(defmacro defservice
[sname {:keys [dispatch-by interceptors]}]
@@ -118,5 +118,16 @@
:code :spec-validation
:explain (with-out-str
(expound/printer data))
- :data data))))
+ :data (::s/problems data)))))
data)))})
+
+(def wrap-errors
+ {:error
+ (fn [data]
+ (let [error (:error data)
+ mdata (meta (:request data))]
+ (assoc data :error (ex/error :type :service-error
+ :name (:spec mdata)
+ :cause error))))})
+
+
diff --git a/backend/src/uxbox/util/exceptions.clj b/backend/src/uxbox/util/exceptions.clj
index 70db97db58..b39a9e8ce0 100644
--- a/backend/src/uxbox/util/exceptions.clj
+++ b/backend/src/uxbox/util/exceptions.clj
@@ -12,7 +12,7 @@
(s/def ::code keyword?)
(s/def ::mesage string?)
(s/def ::hint string?)
-(s/def ::cause #(instance? Exception %))
+(s/def ::cause #(instance? Throwable %))
(s/def ::error-params
(s/keys :req-un [::type]
:opt-un [::code
diff --git a/backend/src/uxbox/util/http.clj b/backend/src/uxbox/util/http.clj
index 3fd4b45619..4cbc1fb78b 100644
--- a/backend/src/uxbox/util/http.clj
+++ b/backend/src/uxbox/util/http.clj
@@ -4,213 +4,5 @@
;;
;; Copyright (c) 2019 Andrey Antukh
-(ns uxbox.util.http)
-
-(defn response
- "Create a response instance."
- ([body] (response body 200 {}))
- ([body status] (response body status {}))
- ([body status headers] {:body body :status status :headers headers}))
-
-(defn response?
- [resp]
- (and (map? resp)
- (integer? (:status resp))
- (map? (:headers resp))))
-
-(defn continue
- ([body] (response body 100))
- ([body headers] (response body 100 headers)))
-
-(defn ok
- "HTTP 200 OK
- Should be used to indicate nonspecific success. Must not be used to
- communicate errors in the response body.
-
- In most cases, 200 is the code the client hopes to see. It indicates that
- the REST API successfully carried out whatever action the client requested,
- and that no more specific code in the 2xx series is appropriate. Unlike
- the 204 status code, a 200 response should include a response body."
- ([body] (response body 200))
- ([body headers] (response body 200 headers)))
-
-(defn created
- "HTTP 201 Created
- Must be used to indicate successful resource creation.
-
- A REST API responds with the 201 status code whenever a collection creates,
- or a store adds, a new resource at the client's request. There may also be
- times when a new resource is created as a result of some controller action,
- in which case 201 would also be an appropriate response."
- ([location] (response "" 201 {"location" location}))
- ([location body] (response body 201 {"location" location}))
- ([location body headers] (response body 201 (merge headers {"location" location}))))
-
-(defn accepted
- "HTTP 202 Accepted
- Must be used to indicate successful start of an asynchronous action.
-
- A 202 response indicates that the client's request will be handled
- asynchronously. This response status code tells the client that the request
- appears valid, but it still may have problems once it's finally processed.
- A 202 response is typically used for actions that take a long while to
- process."
- ([body] (response body 202))
- ([body headers] (response body 202 headers)))
-
-(defn no-content
- "HTTP 204 No Content
- Should be used when the response body is intentionally empty.
-
- The 204 status code is usually sent out in response to a PUT, POST, or
- DELETE request, when the REST API declines to send back any status message
- or representation in the response message's body. An API may also send 204
- in conjunction with a GET request to indicate that the requested resource
- exists, but has no state representation to include in the body."
- ([] (response "" 204))
- ([headers] (response "" 204 headers)))
-
-(defn moved-permanently
- "301 Moved Permanently
- Should be used to relocate resources.
-
- The 301 status code indicates that the REST API's resource model has been
- significantly redesigned and a new permanent URI has been assigned to the
- client's requested resource. The REST API should specify the new URI in
- the response's Location header."
- ([location] (response "" 301 {"location" location}))
- ([location body] (response body 301 {"location" location}))
- ([location body headers] (response body 301 (merge headers {"location" location}))))
-
-(defn found
- "HTTP 302 Found
- Should not be used.
-
- The intended semantics of the 302 response code have been misunderstood
- by programmers and incorrectly implemented in programs since version 1.0
- of the HTTP protocol.
- The confusion centers on whether it is appropriate for a client to always
- automatically issue a follow-up GET request to the URI in response's
- Location header, regardless of the original request's method. For the
- record, the intent of 302 is that this automatic redirect behavior only
- applies if the client's original request used either the GET or HEAD
- method.
-
- To clear things up, HTTP 1.1 introduced status codes 303 (\"See Other\")
- and 307 (\"Temporary Redirect\"), either of which should be used
- instead of 302."
- ([location] (response "" 302 {"location" location}))
- ([location body] (response body 302 {"location" location}))
- ([location body headers] (response body 302 (merge headers {"location" location}))))
-
-(defn see-other
- "HTTP 303 See Other
- Should be used to refer the client to a different URI.
-
- A 303 response indicates that a controller resource has finished its work,
- but instead of sending a potentially unwanted response body, it sends the
- client the URI of a response resource. This can be the URI of a temporary
- status message, or the URI to some already existing, more permanent,
- resource.
- Generally speaking, the 303 status code allows a REST API to send a
- reference to a resource without forcing the client to download its state.
- Instead, the client may send a GET request to the value of the Location
- header."
- ([location] (response "" 303 {"location" location}))
- ([location body] (response body 303 {"location" location}))
- ([location body headers] (response body 303 (merge headers {"location" location}))))
-
-(defn temporary-redirect
- "HTTP 307 Temporary Redirect
- Should be used to tell clients to resubmit the request to another URI.
-
- HTTP/1.1 introduced the 307 status code to reiterate the originally
- intended semantics of the 302 (\"Found\") status code. A 307 response
- indicates that the REST API is not going to process the client's request.
- Instead, the client should resubmit the request to the URI specified by
- the response message's Location header.
-
- A REST API can use this status code to assign a temporary URI to the
- client's requested resource. For example, a 307 response can be used to
- shift a client request over to another host."
- ([location] (response "" 307 {"location" location}))
- ([location body] (response body 307 {"location" location}))
- ([location body headers] (response body 307 (merge headers {"location" location}))))
-
-(defn bad-request
- "HTTP 400 Bad Request
- May be used to indicate nonspecific failure.
-
- 400 is the generic client-side error status, used when no other 4xx error
- code is appropriate."
- ([body] (response body 400))
- ([body headers] (response body 400 headers)))
-
-(defn unauthorized
- "HTTP 401 Unauthorized
- Must be used when there is a problem with the client credentials.
-
- A 401 error response indicates that the client tried to operate on a
- protected resource without providing the proper authorization. It may have
- provided the wrong credentials or none at all."
- ([body] (response body 401))
- ([body headers] (response body 401 headers)))
-
-(defn forbidden
- "HTTP 403 Forbidden
- Should be used to forbid access regardless of authorization state.
-
- A 403 error response indicates that the client's request is formed
- correctly, but the REST API refuses to honor it. A 403 response is not a
- case of insufficient client credentials; that would be 401 (\"Unauthorized\").
- REST APIs use 403 to enforce application-level permissions. For example, a
- client may be authorized to interact with some, but not all of a REST API's
- resources. If the client attempts a resource interaction that is outside of
- its permitted scope, the REST API should respond with 403."
- ([body] (response body 403))
- ([body headers] (response body 403 headers)))
-
-(defn not-found
- "HTTP 404 Not Found
- Must be used when a client's URI cannot be mapped to a resource.
-
- The 404 error status code indicates that the REST API can't map the
- client's URI to a resource."
- ([body] (response body 404))
- ([body headers] (response body 404 headers)))
-
-(defn method-not-allowed
- ([body] (response body 405))
- ([body headers] (response body 405 headers)))
-
-(defn not-acceptable
- ([body] (response body 406))
- ([body headers] (response body 406 headers)))
-
-(defn conflict
- ([body] (response body 409))
- ([body headers] (response body 409 headers)))
-
-(defn gone
- ([body] (response body 410))
- ([body headers] (response body 410 headers)))
-
-(defn precondition-failed
- ([body] (response body 412))
- ([body headers] (response body 412 headers)))
-
-(defn unsupported-mediatype
- ([body] (response body 415))
- ([body headers] (response body 415 headers)))
-
-(defn too-many-requests
- ([body] (response body 429))
- ([body headers] (response body 429 headers)))
-
-(defn internal-server-error
- ([body] (response body 500))
- ([body headers] (response body 500 headers)))
-
-(defn not-implemented
- ([body] (response body 501))
- ([body headers] (response body 501 headers)))
+(ns uxbox.util.http
+ "Http related helpers.")
diff --git a/backend/src/uxbox/util/sql.clj b/backend/src/uxbox/util/sql.clj
index 44bef679d4..48b4cab582 100644
--- a/backend/src/uxbox/util/sql.clj
+++ b/backend/src/uxbox/util/sql.clj
@@ -157,7 +157,9 @@
(into rp p)
(first n)
(rest n)))
- [(str prefix (str/join join-with rs) suffix) rp]))))
+ (if (empty? rs)
+ ["" []]
+ [(str prefix (str/join join-with rs) suffix) rp])))))
(defn- process-param-tokens
[sql]
@@ -168,7 +170,7 @@
(def ^:private select-formatters
[#(format-exprs (::select %) {:prefix "SELECT "})
#(format-exprs (::from %) {:prefix "FROM "})
- #(format-exprs (::join %))
+ #(format-exprs (::join %) {:join-with " "})
#(format-exprs (::where %) {:prefix "WHERE ("
:join-with ") AND ("
:suffix ")"})
diff --git a/backend/test/uxbox/tests/helpers.clj b/backend/test/uxbox/tests/helpers.clj
index 8e75991c4f..ea2bda3165 100644
--- a/backend/test/uxbox/tests/helpers.clj
+++ b/backend/test/uxbox/tests/helpers.clj
@@ -6,9 +6,10 @@
[cuerdas.core :as str]
[mount.core :as mount]
[datoteka.storages :as st]
- [uxbox.services.mutations.profiles :as profiles]
+ [uxbox.services.mutations.users :as users]
[uxbox.services.mutations.projects :as projects]
- [uxbox.services.mutations.pages :as pages]
+ [uxbox.services.mutations.project-files :as files]
+ [uxbox.services.mutations.project-pages :as pages]
[uxbox.fixtures :as fixtures]
[uxbox.migrations]
[uxbox.media]
@@ -24,6 +25,8 @@
#'uxbox.config/secret
#'uxbox.core/system
#'uxbox.db/pool
+ #'uxbox.services.init/query-services
+ #'uxbox.services.init/mutation-services
#'uxbox.migrations/migrations
#'uxbox.media/assets-storage
#'uxbox.media/media-storage
@@ -64,39 +67,44 @@
(defn create-user
[conn i]
- (profiles/create-profile conn {:id (mk-uuid "user" i)
- :fullname (str "User " i)
- :username (str "user" i)
- :email (str "user" i ".test@uxbox.io")
- :password "123123"
- :metadata {}}))
+ (users/create-profile conn {:id (mk-uuid "user" i)
+ :fullname (str "User " i)
+ :username (str "user" i)
+ :email (str "user" i ".test@uxbox.io")
+ :password "123123"
+ :metadata {}}))
(defn create-project
[conn user-id i]
(projects/create-project conn {:id (mk-uuid "project" i)
:user user-id
+ :version 1
:name (str "sample project " i)}))
-(defn create-page
- [conn uid pid i]
+
+(defn create-project-file
+ [conn user-id project-id i]
+ (files/create-file conn {:id (mk-uuid "project-file" i)
+ :user user-id
+ :project-id project-id
+ :name (str "sample project file" i)}))
+
+
+(defn create-project-page
+ [conn user-id file-id i]
(pages/create-page conn {:id (mk-uuid "page" i)
- :user uid
- :project-id pid
+ :user user-id
+ :file-id file-id
:name (str "page" i)
- :data {:shapes []}
+ :ordering i
+ :data {}
:metadata {}}))
(defn handle-error
[err]
- (cond
- (instance? clojure.lang.ExceptionInfo err)
- (ex-data err)
-
- (instance? java.util.concurrent.ExecutionException err)
+ (if (instance? java.util.concurrent.ExecutionException err)
(handle-error (.getCause err))
-
- :else
- [err nil]))
+ err))
(defmacro try-on
[expr]
@@ -126,21 +134,28 @@
{:error (handle-error e#)
:result nil})))
+(defn print-error!
+ [error]
+ (let [data (ex-data error)]
+ (cond
+ (= :spec-validation (:code data))
+ (println (:explain data))
+
+ :else
+ (.printStackTrace error))))
+
(defn print-result!
[{:keys [error result]}]
(if error
(do
(println "====> START ERROR")
- (if (= :spec-validation (:code error))
- (s/explain-out (:data error))
- (prn error))
+ (print-error! error)
(println "====> END ERROR"))
(do
(println "====> START RESPONSE")
(prn result)
(println "====> END RESPONSE"))))
-
(defn exception?
[v]
(instance? Throwable v))
@@ -154,6 +169,11 @@
(let [data (ex-data e)]
(= type (:type data))))
+(defn ex-of-code?
+ [e code]
+ (let [data (ex-data e)]
+ (= code (:code data))))
+
(defn ex-with-code?
[e code]
(let [data (ex-data e)]
diff --git a/backend/test/uxbox/tests/test_emails.clj b/backend/test/uxbox/tests/test_emails.clj
index 9e853e9748..248ddb71c7 100644
--- a/backend/test/uxbox/tests/test_emails.clj
+++ b/backend/test/uxbox/tests/test_emails.clj
@@ -25,20 +25,20 @@
(t/is (contains? result :reply-to))
(t/is (vector? (:body result)))))
-(t/deftest email-sending-and-sendmail-job
- (let [res @(emails/send! emails/register {:to "example@uxbox.io" :name "foo"})]
- (t/is (nil? res)))
- (with-mock mock
- {:target 'uxbox.jobs.sendmail/impl-sendmail
- :return (p/resolved nil)}
+;; (t/deftest email-sending-and-sendmail-job
+;; (let [res @(emails/send! emails/register {:to "example@uxbox.io" :name "foo"})]
+;; (t/is (nil? res)))
+;; (with-mock mock
+;; {:target 'uxbox.jobs.sendmail/impl-sendmail
+;; :return (p/resolved nil)}
- (let [res @(uxbox.jobs.sendmail/send-emails {})]
- (t/is (= 1 res))
- (t/is (:called? @mock))
- (t/is (= 1 (:call-count @mock))))
+;; (let [res @(uxbox.jobs.sendmail/send-emails {})]
+;; (t/is (= 1 res))
+;; (t/is (:called? @mock))
+;; (t/is (= 1 (:call-count @mock))))
- (let [res @(uxbox.jobs.sendmail/send-emails {})]
- (t/is (= 0 res))
- (t/is (:called? @mock))
- (t/is (= 1 (:call-count @mock))))))
+;; (let [res @(uxbox.jobs.sendmail/send-emails {})]
+;; (t/is (= 0 res))
+;; (t/is (:called? @mock))
+;; (t/is (= 1 (:call-count @mock))))))
diff --git a/backend/test/uxbox/tests/test_services_auth.clj b/backend/test/uxbox/tests/test_services_auth.clj
index 36ef172144..35ac36b80d 100644
--- a/backend/test/uxbox/tests/test_services_auth.clj
+++ b/backend/test/uxbox/tests/test_services_auth.clj
@@ -24,9 +24,14 @@
:scope "foobar"}
out (th/try-on! (sm/handle event))]
;; (th/print-result! out)
- (t/is (map? (:error out)))
- (t/is (= (get-in out [:error :type]) :validation))
- (t/is (= (get-in out [:error :code]) :uxbox.services.mutations.auth/wrong-credentials))))
+ (let [error (:error out)]
+ (t/is (th/ex-info? error))
+ (t/is (th/ex-of-type? error :service-error)))
+
+ (let [error (ex-cause (:error out))]
+ (t/is (th/ex-info? error))
+ (t/is (th/ex-of-type? error :validation))
+ (t/is (th/ex-of-code? error :uxbox.services.mutations.auth/wrong-credentials)))))
(t/deftest success-auth
(let [user @(th/create-user db/pool 1)
diff --git a/backend/test/uxbox/tests/test_services_pages.clj b/backend/test/uxbox/tests/test_services_pages.clj
deleted file mode 100644
index b1a5ba9202..0000000000
--- a/backend/test/uxbox/tests/test_services_pages.clj
+++ /dev/null
@@ -1,144 +0,0 @@
-(ns uxbox.tests.test-services-pages
- (:require
- [clojure.spec.alpha :as s]
- [clojure.test :as t]
- [promesa.core :as p]
- [uxbox.db :as db]
- [uxbox.http :as http]
- [uxbox.services.mutations :as sm]
- [uxbox.services.queries :as sq]
- [uxbox.tests.helpers :as th]))
-
-(t/use-fixtures :once th/state-init)
-(t/use-fixtures :each th/database-reset)
-
-(t/deftest mutation-create-page
- (let [user @(th/create-user db/pool 1)
- proj @(th/create-project db/pool (:id user) 1)
- data {::sm/type :create-page
- :data {:shapes []}
- :metadata {}
- :project-id (:id proj)
- :name "test page"
- :user (:id user)}
- res (th/try-on! (sm/handle data))]
- (t/is (nil? (:error res)))
- (t/is (uuid? (get-in res [:result :id])))
- (let [rsp (:result res)]
- (t/is (= (:user data) (:user-id rsp)))
- (t/is (= (:name data) (:name rsp)))
- (t/is (= (:data data) (:data rsp)))
- (t/is (= (:metadata data) (:metadata rsp))))))
-
-(t/deftest mutation-update-page
- (let [user @(th/create-user db/pool 1)
- proj @(th/create-project db/pool (:id user) 1)
- page @(th/create-page db/pool (:id user) (:id proj) 1)
- data {::sm/type :update-page
- :id (:id page)
- :data {:shapes [1 2 3]}
- :metadata {:foo 2}
- :project-id (:id proj)
- :name "test page"
- :user (:id user)}
- res (th/try-on! (sm/handle data))]
-
- ;; (th/print-result! res)
-
- (t/is (nil? (:error res)))
- (t/is (= (:id data) (get-in res [:result :id])))
- #_(t/is (= (:user data) (get-in res [:result :user-id])))
- #_(t/is (= (:name data) (get-in res [:result :name])))
- #_(t/is (= (:data data) (get-in res [:result :data])))
- #_(t/is (= (:metadata data) (get-in res [:result :metadata])))))
-
-(t/deftest mutation-update-page-metadata
- (let [user @(th/create-user db/pool 1)
- proj @(th/create-project db/pool (:id user) 1)
- page @(th/create-page db/pool (:id user) (:id proj) 1)
- data {::sm/type :update-page-metadata
- :id (:id page)
- :metadata {:foo 2}
- :project-id (:id proj)
- :name "test page"
- :user (:id user)}
- res (th/try-on! (sm/handle data))]
-
- ;; (th/print-result! res)
- (t/is (nil? (:error res)))
- (t/is (= (:id data) (get-in res [:result :id])))
- (t/is (= (:user data) (get-in res [:result :user-id])))
- (t/is (= (:name data) (get-in res [:result :name])))
- (t/is (= (:metadata data) (get-in res [:result :metadata])))))
-
-(t/deftest mutation-delete-page
- (let [user @(th/create-user db/pool 1)
- proj @(th/create-project db/pool (:id user) 1)
- page @(th/create-page db/pool (:id user) (:id proj) 1)
- data {::sm/type :delete-page
- :id (:id page)
- :user (:id user)}
- res (th/try-on! (sm/handle data))]
-
- ;; (th/print-result! res)
- (t/is (nil? (:error res)))
- (t/is (nil? (:result res)))))
-
-(t/deftest query-pages-by-project
- (let [user @(th/create-user db/pool 1)
- proj @(th/create-project db/pool (:id user) 1)
- page @(th/create-page db/pool (:id user) (:id proj) 1)
- data {::sq/type :pages-by-project
- :project-id (:id proj)
- :user (:id user)}
- res (th/try-on! (sq/handle data))]
-
- ;; (th/print-result! res)
- (t/is (nil? (:error res)))
- (t/is (vector? (:result res)))
- (t/is (= 1 (count (:result res))))
- (t/is (= "page1" (get-in res [:result 0 :name])))
- (t/is (:id proj) (get-in res [:result 0 :project-id]))))
-
-;; (t/deftest http-page-history-update
-;; (with-open [conn (db/connection)]
-;; (let [user (th/create-user conn 1)
-;; proj (uspr/create-project conn {:user (:id user) :name "proj1"})
-;; data {:id (uuid/random)
-;; :user (:id user)
-;; :project (:id proj)
-;; :version 0
-;; :data "1"
-;; :metadata "2"
-;; :name "page1"
-;; :width 200
-;; :height 200
-;; :layout "mobil"}
-;; page (uspg/create-page conn data)]
-
-;; (dotimes [i 10]
-;; (let [page (uspg/get-page-by-id conn (:id data))]
-;; (uspg/update-page conn (assoc page :data (str i)))))
-
-;; ;; Check inserted history
-;; (let [sql (str "SELECT * FROM pages_history "
-;; " WHERE page=? ORDER BY created_at DESC")
-;; result (sc/fetch conn [sql (:id data)])
-;; item (first result)]
-
-;; (th/with-server {:handler @http/app}
-;; (let [uri (str th/+base-url+
-;; "/api/pages/" (:id page)
-;; "/history/" (:id item))
-;; params {:body {:label "test" :pinned true}}
-;; [status data] (th/http-put user uri params)]
-;; ;; (println "RESPONSE:" status data)
-;; (t/is (= 200 status))
-;; (t/is (= (:id data) (:id item))))))
-
-;; (let [sql (str "SELECT * FROM pages_history "
-;; " WHERE page=? AND pinned = true "
-;; " ORDER BY created_at DESC")
-;; result (sc/fetch-one conn [sql (:id data)])]
-;; (t/is (= "test" (:label result)))
-;; (t/is (= true (:pinned result)))))))
diff --git a/backend/test/uxbox/tests/test_services_project_files.clj b/backend/test/uxbox/tests/test_services_project_files.clj
new file mode 100644
index 0000000000..c3ab27ddb5
--- /dev/null
+++ b/backend/test/uxbox/tests/test_services_project_files.clj
@@ -0,0 +1,76 @@
+(ns uxbox.tests.test-services-project-files
+ (:require
+ [clojure.test :as t]
+ [promesa.core :as p]
+ [uxbox.db :as db]
+ [uxbox.http :as http]
+ [uxbox.services.mutations :as sm]
+ [uxbox.services.queries :as sq]
+ [uxbox.tests.helpers :as th]))
+
+(t/use-fixtures :once th/state-init)
+(t/use-fixtures :each th/database-reset)
+
+(t/deftest query-project-files
+ (let [user @(th/create-user db/pool 2)
+ proj @(th/create-project db/pool (:id user) 1)
+ pf @(th/create-project-file db/pool (:id user) (:id proj) 1)
+ pp @(th/create-project-page db/pool (:id user) (:id pf) 1)
+ data {::sq/type :project-files
+ :user (:id user)
+ :project-id (:id proj)}
+ out (th/try-on! (sq/handle data))]
+ ;; (th/print-result! out)
+ (t/is (nil? (:error out)))
+ (t/is (= 1 (count (:result out))))
+ (t/is (= (:id pf) (get-in out [:result 0 :id])))
+ (t/is (= (:id proj) (get-in out [:result 0 :project-id])))
+ (t/is (= (:name pf) (get-in out [:result 0 :name])))
+ (t/is (= [(:id pp)] (get-in out [:result 0 :pages])))))
+
+(t/deftest mutation-create-project-file
+ (let [user @(th/create-user db/pool 1)
+ proj @(th/create-project db/pool (:id user) 1)
+ data {::sm/type :create-project-file
+ :user (:id user)
+ :name "test file"
+ :project-id (:id proj)}
+ out (th/try-on! (sm/handle data))
+ ]
+ ;; (th/print-result! out)
+ (t/is (nil? (:error out)))
+ (t/is (= (:name data) (get-in out [:result :name])))
+ #_(t/is (= (:project-id data) (get-in out [:result :project-id])))))
+
+(t/deftest mutation-update-project-file
+ (let [user @(th/create-user db/pool 1)
+ proj @(th/create-project db/pool (:id user) 1)
+ pf @(th/create-project-file db/pool (:id user) (:id proj) 1)
+ data {::sm/type :update-project-file
+ :id (:id pf)
+ :name "new file name"
+ :user (:id user)}
+ out (th/try-on! (sm/handle data))]
+ ;; (th/print-result! out)
+ ;; TODO: check the result
+ (t/is (nil? (:error out)))
+ (t/is (nil? (:result out)))))
+
+(t/deftest mutation-delete-project-file
+ (let [user @(th/create-user db/pool 1)
+ proj @(th/create-project db/pool (:id user) 1)
+ pf @(th/create-project-file db/pool (:id user) (:id proj) 1)
+ data {::sm/type :delete-project-file
+ :id (:id pf)
+ :user (:id user)}
+ out (th/try-on! (sm/handle data))]
+ ;; (th/print-result! out)
+ (t/is (nil? (:error out)))
+ (t/is (nil? (:result out)))
+
+ (let [sql "select * from project_files
+ where project_id=$1 and deleted_at is null"
+ res @(db/query db/pool [sql (:id proj)])]
+ (t/is (empty? res)))))
+
+;; ;; TODO: add permisions related tests
diff --git a/backend/test/uxbox/tests/test_services_project_pages.clj b/backend/test/uxbox/tests/test_services_project_pages.clj
new file mode 100644
index 0000000000..3133c7824a
--- /dev/null
+++ b/backend/test/uxbox/tests/test_services_project_pages.clj
@@ -0,0 +1,84 @@
+(ns uxbox.tests.test-services-project-pages
+ (:require
+ [clojure.spec.alpha :as s]
+ [clojure.test :as t]
+ [promesa.core :as p]
+ [uxbox.db :as db]
+ [uxbox.http :as http]
+ [uxbox.services.mutations :as sm]
+ [uxbox.services.queries :as sq]
+ [uxbox.tests.helpers :as th]))
+
+(t/use-fixtures :once th/state-init)
+(t/use-fixtures :each th/database-reset)
+
+(t/deftest query-project-pages
+ (let [user @(th/create-user db/pool 1)
+ proj @(th/create-project db/pool (:id user) 1)
+ file @(th/create-project-file db/pool (:id user) (:id proj) 1)
+ page @(th/create-project-page db/pool (:id user) (:id file) 1)
+ data {::sq/type :project-pages
+ :file-id (:id file)
+ :user (:id user)}
+ out (th/try-on! (sq/handle data))]
+ ;; (th/print-result! out)
+ (t/is (nil? (:error out)))
+ (t/is (vector? (:result out)))
+ (t/is (= 1 (count (:result out))))
+ (t/is (= "page1" (get-in out [:result 0 :name])))
+ (t/is (:id file) (get-in out [:result 0 :file-id]))))
+
+(t/deftest mutation-create-project-page
+ (let [user @(th/create-user db/pool 1)
+ proj @(th/create-project db/pool (:id user) 1)
+ pf @(th/create-project-file db/pool (:id user) (:id proj) 1)
+
+ data {::sm/type :create-project-page
+ :data {}
+ :metadata {}
+ :file-id (:id pf)
+ :ordering 1
+ :name "test page"
+ :user (:id user)}
+ out (th/try-on! (sm/handle data))]
+ ;; (th/print-result! out)
+ (t/is (nil? (:error out)))
+ (t/is (uuid? (get-in out [:result :id])))
+ (t/is (= (:user data) (get-in out [:result :user-id])))
+ (t/is (= (:name data) (get-in out [:result :name])))
+ (t/is (= (:data data) (get-in out [:result :data])))
+ (t/is (= (:metadata data) (get-in out [:result :metadata])))
+ (t/is (= 0 (get-in out [:result :version])))))
+
+(t/deftest mutation-update-project-page-data
+ (let [user @(th/create-user db/pool 1)
+ proj @(th/create-project db/pool (:id user) 1)
+ file @(th/create-project-file db/pool (:id user) (:id proj) 1)
+ page @(th/create-project-page db/pool (:id user) (:id file) 1)
+ data {::sm/type :update-project-page-data
+ :id (:id page)
+ :data {:shapes [1 2 3]}
+ :file-id (:id file)
+ :user (:id user)}
+ out (th/try-on! (sm/handle data))]
+
+ ;; (th/print-result! out)
+ ;; TODO: check history creation
+ ;; TODO: check correct page data update operation
+
+ (t/is (nil? (:error out)))
+ (t/is (= (:id data) (get-in out [:result :id])))
+ (t/is (= 1 (get-in out [:result :version])))))
+
+(t/deftest mutation-delete-project-page
+ (let [user @(th/create-user db/pool 1)
+ proj @(th/create-project db/pool (:id user) 1)
+ file @(th/create-project-file db/pool (:id user) (:id proj) 1)
+ page @(th/create-project-page db/pool (:id user) (:id file) 1)
+ data {::sm/type :delete-project-page
+ :id (:id page)
+ :user (:id user)}
+ out (th/try-on! (sm/handle data))]
+ ;; (th/print-result! out)
+ (t/is (nil? (:error out)))
+ (t/is (nil? (:result out)))))
diff --git a/backend/test/uxbox/tests/test_services_projects.clj b/backend/test/uxbox/tests/test_services_projects.clj
index eb5d4cc619..9d354fa768 100644
--- a/backend/test/uxbox/tests/test_services_projects.clj
+++ b/backend/test/uxbox/tests/test_services_projects.clj
@@ -11,17 +11,13 @@
(t/use-fixtures :once th/state-init)
(t/use-fixtures :each th/database-reset)
-;; TODO: migrate from try-on to try-on!
-
-(t/deftest query-project-list
+(t/deftest query-projects
(let [user @(th/create-user db/pool 1)
proj @(th/create-project db/pool (:id user) 1)
data {::sq/type :projects
:user (:id user)}
out (th/try-on! (sq/handle data))]
-
;; (th/print-result! out)
-
(t/is (nil? (:error out)))
(t/is (= 1 (count (:result out))))
(t/is (= (:id proj) (get-in out [:result 0 :id])))
@@ -32,11 +28,10 @@
data {::sm/type :create-project
:user (:id user)
:name "test project"}
- [err rsp] (th/try-on (sm/handle data))]
- ;; (prn "RESPONSE:" err rsp)
- (t/is (nil? err))
- (t/is (= (:user data) (:user-id rsp)))
- (t/is (= (:name data) (:name rsp)))))
+ out (th/try-on! (sm/handle data))]
+ ;; (th/print-result! out)
+ (t/is (nil? (:error out)))
+ (t/is (= (:name data) (get-in out [:result :name])))))
(t/deftest mutation-update-project
(let [user @(th/create-user db/pool 1)
@@ -45,12 +40,12 @@
:id (:id proj)
:name "test project mod"
:user (:id user)}
- [err rsp] (th/try-on (sm/handle data))]
- ;; (prn "RESPONSE:" err rsp)
- (t/is (nil? err))
- (t/is (= (:id data) (:id rsp)))
- (t/is (= (:user data) (:user-id rsp)))
- (t/is (= (:name data) (:name rsp)))))
+ out (th/try-on! (sm/handle data))]
+ ;; (th/print-result! out)
+ (t/is (nil? (:error out)))
+ (t/is (= (:id data) (get-in out [:result :id])))
+ (t/is (= (:user data) (get-in out [:result :user-id])))
+ (t/is (= (:name data) (get-in out [:result :name])))))
(t/deftest mutation-delete-project
(let [user @(th/create-user db/pool 1)
@@ -58,12 +53,13 @@
data {::sm/type :delete-project
:id (:id proj)
:user (:id user)}
- [err rsp] (th/try-on (sm/handle data))]
- ;; (prn "RESPONSE:" err rsp)
- (t/is (nil? err))
- (t/is (nil? rsp))
+ out (th/try-on! (sm/handle data))]
+ ;; (th/print-result! out)
+ (t/is (nil? (:error out)))
+ (t/is (nil? (:result out)))
- (let [sql "SELECT * FROM projects
- WHERE user_id=$1 AND deleted_at is null"
+ (let [sql "select * from projects where user_id=$1 and deleted_at is null"
res @(db/query db/pool [sql (:id user)])]
(t/is (empty? res)))))
+
+;; TODO: add permisions related tests
diff --git a/backend/test/uxbox/tests/test_services_user_storage.clj b/backend/test/uxbox/tests/test_services_user_attrs.clj
similarity index 62%
rename from backend/test/uxbox/tests/test_services_user_storage.clj
rename to backend/test/uxbox/tests/test_services_user_attrs.clj
index 8585f5e967..db9826d9e9 100644
--- a/backend/test/uxbox/tests/test_services_user_storage.clj
+++ b/backend/test/uxbox/tests/test_services_user_attrs.clj
@@ -1,4 +1,4 @@
-(ns uxbox.tests.test-services-user-storage
+(ns uxbox.tests.test-services-user-attrs
(:require
[clojure.spec.alpha :as s]
[clojure.test :as t]
@@ -12,16 +12,22 @@
(t/use-fixtures :once th/state-init)
(t/use-fixtures :each th/database-reset)
-(t/deftest test-user-storage
+(t/deftest test-user-attrs
(let [{:keys [id] :as user} @(th/create-user db/pool 1)]
- (let [out (th/try-on! (sq/handle {::sq/type :user-storage-entry
+ (let [out (th/try-on! (sq/handle {::sq/type :user-attr
:key "foobar"
:user id}))]
(t/is (nil? (:result out)))
- (t/is (map? (:error out)))
- (t/is (= :not-found (get-in out [:error :type]))))
- (let [out (th/try-on! (sm/handle {::sm/type :upsert-user-storage-entry
+ (let [error (:error out)]
+ (t/is (th/ex-info? error))
+ (t/is (th/ex-of-type? error :service-error)))
+
+ (let [error (ex-cause (:error out))]
+ (t/is (th/ex-info? error))
+ (t/is (th/ex-of-type? error :not-found))))
+
+ (let [out (th/try-on! (sm/handle {::sm/type :upsert-user-attr
:user id
:key "foobar"
:val {:some #{:value}}}))]
@@ -29,7 +35,7 @@
(t/is (nil? (:error out)))
(t/is (nil? (:result out))))
- (let [out (th/try-on! (sq/handle {::sq/type :user-storage-entry
+ (let [out (th/try-on! (sq/handle {::sq/type :user-attr
:key "foobar"
:user id}))]
;; (th/print-result! out)
@@ -37,18 +43,23 @@
(t/is (= {:some #{:value}} (get-in out [:result :val])))
(t/is (= "foobar" (get-in out [:result :key]))))
- (let [out (th/try-on! (sm/handle {::sm/type :delete-user-storage-entry
+ (let [out (th/try-on! (sm/handle {::sm/type :delete-user-attr
:user id
:key "foobar"}))]
;; (th/print-result! out)
(t/is (nil? (:error out)))
(t/is (nil? (:result out))))
- (let [out (th/try-on! (sq/handle {::sq/type :user-storage-entry
+ (let [out (th/try-on! (sq/handle {::sq/type :user-attr
:key "foobar"
:user id}))]
;; (th/print-result! out)
(t/is (nil? (:result out)))
- (t/is (map? (:error out)))
- (t/is (= :not-found (get-in out [:error :type]))))))
+ (let [error (:error out)]
+ (t/is (th/ex-info? error))
+ (t/is (th/ex-of-type? error :service-error)))
+
+ (let [error (ex-cause (:error out))]
+ (t/is (th/ex-info? error))
+ (t/is (th/ex-of-type? error :not-found))))))
diff --git a/backend/test/uxbox/tests/test_users.clj b/backend/test/uxbox/tests/test_services_users.clj
similarity index 65%
rename from backend/test/uxbox/tests/test_users.clj
rename to backend/test/uxbox/tests/test_services_users.clj
index f761b32ed7..ab64fb38d3 100644
--- a/backend/test/uxbox/tests/test_users.clj
+++ b/backend/test/uxbox/tests/test_services_users.clj
@@ -1,11 +1,10 @@
-(ns uxbox.tests.test-users
+(ns uxbox.tests.test-services-users
(:require
[clojure.test :as t]
[clojure.java.io :as io]
[promesa.core :as p]
[cuerdas.core :as str]
[datoteka.core :as fs]
- [vertx.core :as vc]
[uxbox.db :as db]
[uxbox.services.mutations :as sm]
[uxbox.services.queries :as sq]
@@ -16,47 +15,48 @@
(t/deftest test-query-profile
(let [user @(th/create-user db/pool 1)
- event {::sq/type :profile
- :user (:id user)}
- [err rsp] (th/try-on (sq/handle event))]
- ;; (println "RESPONSE:" resp)))
- (t/is (nil? err))
- (t/is (= (:fullname rsp) "User 1"))
- (t/is (= (:username rsp) "user1"))
- (t/is (= (:metadata rsp) {}))
- (t/is (= (:email rsp) "user1.test@uxbox.io"))
- (t/is (not (contains? rsp :password)))))
+ data {::sq/type :profile
+ :user (:id user)}
+
+ out (th/try-on! (sq/handle data))]
+ ;; (th/print-result! out)
+ (t/is (nil? (:error out)))
+ (t/is (= "User 1" (get-in out [:result :fullname])))
+ (t/is (= "user1" (get-in out [:result :username])))
+ (t/is (= "user1.test@uxbox.io" (get-in out [:result :email])))
+ (t/is (not (contains? (:result out) :password)))))
(t/deftest test-mutation-update-profile
- (let [user @(th/create-user db/pool 1)
- event (assoc user
- ::sm/type :update-profile
- :fullname "Full Name"
- :username "user222"
- :metadata {:foo "bar"}
- :email "user222@uxbox.io")
- [err data] (th/try-on (sm/handle event))]
- ;; (println "RESPONSE:" err data)
- (t/is (nil? err))
- (t/is (= (:fullname data) "Full Name"))
- (t/is (= (:username data) "user222"))
- (t/is (= (:metadata data) {:foo "bar"}))
- (t/is (= (:email data) "user222@uxbox.io"))
- (t/is (not (contains? data :password)))))
+ (let [user @(th/create-user db/pool 1)
+ data (assoc user
+ ::sm/type :update-profile
+ :fullname "Full Name"
+ :username "user222"
+ :metadata {:foo "bar"}
+ :email "user222@uxbox.io")
+ out (th/try-on! (sm/handle data))]
+ ;; (th/print-result! out)
+ (t/is (nil? (:error out)))
+ (t/is (= (:fullname data) (get-in out [:result :fullname])))
+ (t/is (= (:username data) (get-in out [:result :username])))
+ (t/is (= (:email data) (get-in out [:result :email])))
+ (t/is (= (:metadata data) (get-in out [:result :metadata])))
+ (t/is (not (contains? (:result out) :password)))))
(t/deftest test-mutation-update-profile-photo
(let [user @(th/create-user db/pool 1)
- event {::sm/type :update-profile-photo
- :user (:id user)
- :file {:name "sample.jpg"
- :path (fs/path "test/uxbox/tests/_files/sample.jpg")
- :size 123123
- :mtype "image/jpeg"}}
- [err rsp] (th/try-on (sm/handle event))]
- ;; (prn "RESPONSE:" [err rsp])
- (t/is (nil? err))
- (t/is (= (:id user) (:id rsp)))
- (t/is (str/starts-with? (:photo rsp) "http"))))
+ data {::sm/type :update-profile-photo
+ :user (:id user)
+ :file {:name "sample.jpg"
+ :path (fs/path "test/uxbox/tests/_files/sample.jpg")
+ :size 123123
+ :mtype "image/jpeg"}}
+
+ out (th/try-on! (sm/handle data))]
+ ;; (th/print-result! out)
+ (t/is (nil? (:error out)))
+ (t/is (= (:id user) (get-in out [:result :id])))
+ (t/is (str/starts-with? (get-in out [:result :photo]) "http"))))
;; (t/deftest test-mutation-register-profile
;; (let[data {:fullname "Full Name"
diff --git a/backend/test/uxbox/tests/test_util_svg.clj b/backend/test/uxbox/tests/test_util_svg.clj
index a58c2c6f6e..75270bb6bc 100644
--- a/backend/test/uxbox/tests/test_util_svg.clj
+++ b/backend/test/uxbox/tests/test_util_svg.clj
@@ -34,19 +34,20 @@
(t/deftest parse-invalid-svg-1
(let [image (io/resource "uxbox/tests/_files/sample.jpg")
- result (th/try! (svg/parse image))]
- (t/is (map? (:error result)))
- (t/is (= (get-in result [:error :code])
- ::svg/invalid-input))))
+ out (th/try! (svg/parse image))]
+
+ (let [error (:error out)]
+ (t/is (th/ex-info? error))
+ (t/is (th/ex-of-code? error ::svg/invalid-input)))))
(t/deftest parse-invalid-svg-2
- (let [result (th/try! (svg/parse-string ""))]
- (t/is (map? (:error result)))
- (t/is (= (get-in result [:error :code])
- ::svg/invalid-input))))
+ (let [out (th/try! (svg/parse-string ""))]
+ (let [error (:error out)]
+ (t/is (th/ex-info? error))
+ (t/is (th/ex-of-code? error ::svg/invalid-input)))))
(t/deftest parse-invalid-svg-3
- (let [result (th/try! (svg/parse-string ""))]
- (t/is (map? (:error result)))
- (t/is (= (get-in result [:error :code])
- ::svg/invalid-result))))
+ (let [out (th/try! (svg/parse-string ""))]
+ (let [error (:error out)]
+ (t/is (th/ex-info? error))
+ (t/is (th/ex-of-code? error ::svg/invalid-result)))))