mirror of
https://github.com/penpot/penpot.git
synced 2025-05-16 20:56:11 +02:00
♻️ Refactor services (for add the project-file concept.
And fix many tests.
This commit is contained in:
parent
af62d949d8
commit
183f0a5400
40 changed files with 1279 additions and 1006 deletions
|
@ -10,10 +10,11 @@ CREATE TABLE users (
|
||||||
email text NOT NULL,
|
email text NOT NULL,
|
||||||
photo text NOT NULL,
|
photo text NOT NULL,
|
||||||
password 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,
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||||
|
@ -25,7 +26,7 @@ CREATE TABLE IF NOT EXISTS user_storage (
|
||||||
PRIMARY KEY (key, user_id)
|
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,
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
token text NOT NULL,
|
token text NOT NULL,
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ CREATE TABLE user_tokens (
|
||||||
PRIMARY KEY (token, user_id)
|
PRIMARY KEY (token, user_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE sessions (
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
|
||||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
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
|
CREATE UNIQUE INDEX users__username__idx
|
||||||
ON users USING btree (username)
|
ON users (username)
|
||||||
WHERE deleted_at is null;
|
WHERE deleted_at is null;
|
||||||
|
|
||||||
CREATE UNIQUE INDEX users_email_idx
|
CREATE UNIQUE INDEX users__email__idx
|
||||||
ON users USING btree (email)
|
ON users (email)
|
||||||
WHERE deleted_at is null;
|
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();
|
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();
|
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||||
|
|
||||||
|
|
|
@ -8,43 +8,150 @@ CREATE TABLE IF NOT EXISTS projects (
|
||||||
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
modified_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||||
deleted_at timestamptz DEFAULT NULL,
|
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,
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
||||||
modified_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)
|
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
|
-- Indexes
|
||||||
|
|
||||||
CREATE INDEX projects_user_idx ON projects(user_id);
|
CREATE INDEX projects__user_id__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 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
|
-- Triggers
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION handle_project_insert()
|
CREATE OR REPLACE FUNCTION handle_project_insert()
|
||||||
RETURNS TRIGGER AS $$
|
RETURNS TRIGGER AS $$
|
||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO projects_users (user_id, project_id, role)
|
INSERT INTO project_users (user_id, project_id, can_edit)
|
||||||
VALUES (NEW.user_id, NEW.id, 'owner');
|
VALUES (NEW.user_id, NEW.id, true);
|
||||||
|
|
||||||
RETURN NEW;
|
RETURN NEW;
|
||||||
END;
|
END;
|
||||||
$$ LANGUAGE plpgsql;
|
$$ 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
|
CREATE TRIGGER projects_on_insert_tgr
|
||||||
AFTER INSERT ON projects
|
AFTER INSERT ON projects
|
||||||
FOR EACH ROW EXECUTE PROCEDURE handle_project_insert();
|
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
|
BEFORE UPDATE ON projects
|
||||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
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();
|
||||||
|
|
|
@ -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();
|
|
|
@ -1,6 +1,6 @@
|
||||||
-- Tables
|
-- Tables
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS images_collections (
|
CREATE TABLE IF NOT EXISTS image_collections (
|
||||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
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,
|
width int NOT NULL,
|
||||||
height int NOT NULL,
|
height int NOT NULL,
|
||||||
mimetype text NOT NULL,
|
mimetype text NOT NULL,
|
||||||
collection_id uuid REFERENCES images_collections(id)
|
collection_id uuid REFERENCES image_collections(id)
|
||||||
ON DELETE SET NULL
|
ON DELETE SET NULL
|
||||||
DEFAULT NULL,
|
DEFAULT NULL,
|
||||||
name text NOT NULL,
|
name text NOT NULL,
|
||||||
|
@ -31,20 +31,17 @@ CREATE TABLE IF NOT EXISTS images (
|
||||||
|
|
||||||
-- Indexes
|
-- Indexes
|
||||||
|
|
||||||
CREATE INDEX images_collections_user_idx
|
CREATE INDEX image_collections__user_id__idx ON image_collections (user_id);
|
||||||
ON images_collections (user_id);
|
CREATE INDEX images__collection_id__idx ON images (collection_id);
|
||||||
|
CREATE INDEX images__user_id__idx ON images (user_id);
|
||||||
CREATE INDEX images_collection_idx
|
|
||||||
ON images (collection_id);
|
|
||||||
|
|
||||||
CREATE INDEX images_user_idx
|
|
||||||
ON images (user_id);
|
|
||||||
|
|
||||||
-- Triggers
|
-- 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();
|
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||||
|
|
||||||
CREATE TRIGGER images_modified_at_tgr BEFORE UPDATE ON images
|
CREATE TRIGGER images__modified_at__tgr
|
||||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
BEFORE UPDATE ON images
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
-- Tables
|
-- Tables
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS icons_collections (
|
CREATE TABLE IF NOT EXISTS icon_collections (
|
||||||
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
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,
|
content text NOT NULL,
|
||||||
metadata bytea NOT NULL,
|
metadata bytea NOT NULL,
|
||||||
|
|
||||||
collection_id uuid REFERENCES icons_collections(id)
|
collection_id uuid REFERENCES icon_collections(id)
|
||||||
ON DELETE SET NULL
|
ON DELETE SET NULL
|
||||||
DEFAULT NULL
|
DEFAULT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Indexes
|
-- Indexes
|
||||||
|
|
||||||
CREATE INDEX icon_colections_user_idx
|
CREATE INDEX icon_colections__user_id__idx ON icon_collections (user_id);
|
||||||
ON icons_collections (user_id);
|
CREATE INDEX icons__user_id__idx ON icons(user_id);
|
||||||
|
CREATE INDEX icons__collection_id__idx ON icons(collection_id);
|
||||||
CREATE INDEX icons_user_idx
|
|
||||||
ON icons (user_id);
|
|
||||||
|
|
||||||
CREATE INDEX icons_collection_idx
|
|
||||||
ON icons (collection_id);
|
|
||||||
|
|
||||||
-- Triggers
|
-- Triggers
|
||||||
|
|
||||||
CREATE TRIGGER icons_collections_modified_at_tgr BEFORE UPDATE ON icons_collections
|
CREATE TRIGGER icon_collections__modified_at__tgr
|
||||||
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
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();
|
|
||||||
|
|
||||||
|
CREATE TRIGGER icons__modified_at__tgr
|
||||||
|
BEFORE UPDATE ON icons
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
|
|
@ -50,7 +50,7 @@
|
||||||
:email-reply-to (lookup-env env :uxbox-email-reply-to "no-reply@uxbox.io")
|
: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")
|
: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-port (lookup-env env :uxbox-smtp-port 25)
|
||||||
:smtp-user (lookup-env env :uxbox-smtp-user nil)
|
:smtp-user (lookup-env env :uxbox-smtp-user nil)
|
||||||
:smtp-password (lookup-env env :uxbox-smtp-password nil)
|
:smtp-password (lookup-env env :uxbox-smtp-password nil)
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
[uxbox.config :as cfg]
|
[uxbox.config :as cfg]
|
||||||
[uxbox.core :refer [system]]
|
[uxbox.core :refer [system]]
|
||||||
[uxbox.util.data :as data]
|
[uxbox.util.data :as data]
|
||||||
|
[uxbox.util.exceptions :as ex]
|
||||||
[uxbox.util.pgsql :as pg]
|
[uxbox.util.pgsql :as pg]
|
||||||
[vertx.core :as vx])
|
[vertx.core :as vx])
|
||||||
(:import io.vertx.core.buffer.Buffer))
|
(:import io.vertx.core.buffer.Buffer))
|
||||||
|
@ -33,17 +34,22 @@
|
||||||
:start (create-pool cfg/config system))
|
:start (create-pool cfg/config system))
|
||||||
|
|
||||||
(defmacro with-atomic
|
(defmacro with-atomic
|
||||||
[& args]
|
[bindings & args]
|
||||||
`(pg/with-atomic ~@args))
|
`(pg/with-atomic ~bindings (p/do! ~@args)))
|
||||||
|
|
||||||
(def row-xfm
|
(def row-xfm
|
||||||
(comp (map pg/row->map)
|
(comp (map pg/row->map)
|
||||||
(map data/normalize-attrs)))
|
(map data/normalize-attrs)))
|
||||||
|
|
||||||
(defmacro query
|
(defmacro query
|
||||||
[& args]
|
[conn sql]
|
||||||
`(pg/query ~@args {:xfm row-xfm}))
|
`(-> (pg/query ~conn ~sql {:xfm row-xfm})
|
||||||
|
(p/catch' (fn [err#]
|
||||||
|
(ex/raise :type :database-error
|
||||||
|
:cause err#)))))
|
||||||
(defmacro query-one
|
(defmacro query-one
|
||||||
[& args]
|
[conn sql]
|
||||||
`(pg/query-one ~@args {:xfm row-xfm}))
|
`(-> (pg/query-one ~conn ~sql {:xfm row-xfm})
|
||||||
|
(p/catch' (fn [err#]
|
||||||
|
(ex/raise :type :database-error
|
||||||
|
:cause err#)))))
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
(ns uxbox.fixtures
|
(ns uxbox.fixtures
|
||||||
"A initial fixtures."
|
"A initial fixtures."
|
||||||
(:require
|
(:require
|
||||||
|
[clojure.tools.logging :as log]
|
||||||
[buddy.hashers :as hashers]
|
[buddy.hashers :as hashers]
|
||||||
[mount.core :as mount]
|
[mount.core :as mount]
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
|
@ -20,26 +21,27 @@
|
||||||
|
|
||||||
(defn- mk-uuid
|
(defn- mk-uuid
|
||||||
[prefix & args]
|
[prefix & args]
|
||||||
(uuid/namespaced uuid/oid (apply str prefix args)))
|
(uuid/namespaced uuid/oid (apply str prefix (interpose "-" args))))
|
||||||
|
|
||||||
;; --- Users creation
|
;; --- Users creation
|
||||||
|
|
||||||
(def create-user-sql
|
(def create-user-sql
|
||||||
"insert into users (id, fullname, username, email, password, metadata, photo)
|
"insert into users (id, fullname, username, email, password, photo)
|
||||||
values ($1, $2, $3, $4, $5, $6, $7)
|
values ($1, $2, $3, $4, $5, $6)
|
||||||
returning *;")
|
returning *;")
|
||||||
|
|
||||||
|
(def password (hashers/encrypt "123123"))
|
||||||
|
|
||||||
(defn create-user
|
(defn create-user
|
||||||
[conn i]
|
[conn user-index]
|
||||||
(println "create user" i)
|
(log/info "create user" user-index)
|
||||||
(db/query-one conn [create-user-sql
|
(let [sql create-user-sql
|
||||||
(mk-uuid "user" i)
|
id (mk-uuid "user" user-index)
|
||||||
(str "User " i)
|
fullname (str "User " user-index)
|
||||||
(str "user" i)
|
username (str "user" user-index)
|
||||||
(str "user" i ".test@uxbox.io")
|
email (str "user" user-index ".test@uxbox.io")
|
||||||
(hashers/encrypt "123123")
|
photo ""]
|
||||||
(blob/encode {})
|
(db/query-one conn [sql id fullname username email password photo])))
|
||||||
""]))
|
|
||||||
|
|
||||||
;; --- Projects creation
|
;; --- Projects creation
|
||||||
|
|
||||||
|
@ -49,29 +51,46 @@
|
||||||
returning *;")
|
returning *;")
|
||||||
|
|
||||||
(defn create-project
|
(defn create-project
|
||||||
[conn [pjid uid]]
|
[conn [project-index user-index]]
|
||||||
(println "create project" pjid "(for user=" uid ")")
|
(log/info "create project" user-index project-index)
|
||||||
(db/query-one conn [create-project-sql
|
(let [sql create-project-sql
|
||||||
(mk-uuid "project" pjid uid)
|
id (mk-uuid "project" project-index user-index)
|
||||||
(mk-uuid "user" uid)
|
user-id (mk-uuid "user" user-index)
|
||||||
(str "sample project " pjid)]))
|
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
|
(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)
|
version, ordering, data, metadata)
|
||||||
values ($1, $2, $3, $4, $5, $6, $7, $8)
|
values ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
returning id;")
|
returning id;")
|
||||||
|
|
||||||
(def create-page-history-sql
|
(def create-page-history-sql
|
||||||
"insert into pages_history (page_id, user_id, version, data, metadata)
|
"insert into project_page_history (page_id, user_id, version, data)
|
||||||
values ($1, $2, $3, $4, $5)
|
values ($1, $2, $3, $4)
|
||||||
returning id;")
|
returning id;")
|
||||||
|
|
||||||
(defn create-page
|
(defn create-page
|
||||||
[conn [pjid paid uid]]
|
[conn [page-index file-index project-index user-index]]
|
||||||
(println "create page" paid "(for project=" pjid ", user=" uid ")")
|
(log/info "create page" user-index project-index file-index page-index)
|
||||||
(let [canvas {:id (mk-uuid "canvas" 1)
|
(let [canvas {:id (mk-uuid "canvas" 1)
|
||||||
:name "Canvas-1"
|
:name "Canvas-1"
|
||||||
:type :canvas
|
:type :canvas
|
||||||
|
@ -82,29 +101,61 @@
|
||||||
data {:shapes []
|
data {:shapes []
|
||||||
:canvas [(:id canvas)]
|
:canvas [(:id canvas)]
|
||||||
:shapes-by-id {(:id canvas) 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)
|
data (blob/encode data)
|
||||||
mdata (blob/encode {})]
|
mdata (blob/encode {})]
|
||||||
(p/do!
|
(p/do!
|
||||||
(db/query-one conn [create-page-sql
|
(db/query-one conn [sql1 id user-id file-id name version ordering data mdata])
|
||||||
(mk-uuid "page" pjid paid uid)
|
#_(db/query-one conn [sql2 id user-id version data]))))
|
||||||
(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]))))
|
|
||||||
|
|
||||||
|
(def preset-small
|
||||||
|
{:users 50
|
||||||
|
:projects 5
|
||||||
|
:files 5
|
||||||
|
:pages 3})
|
||||||
|
|
||||||
(def num-users 5)
|
(def preset-medium
|
||||||
(def num-projects 5)
|
{:users 500
|
||||||
(def num-pages 5)
|
: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
|
(defn -main
|
||||||
[& args]
|
[& args]
|
||||||
|
@ -115,18 +166,12 @@
|
||||||
#'uxbox.db/pool
|
#'uxbox.db/pool
|
||||||
#'uxbox.migrations/migrations})
|
#'uxbox.migrations/migrations})
|
||||||
(mount/start))
|
(mount/start))
|
||||||
@(db/with-atomic [conn db/pool]
|
(let [preset (case (first args)
|
||||||
(p/do!
|
(nil "small") preset-small
|
||||||
(p/run! #(create-user conn %) (range num-users))
|
"medium" preset-medium
|
||||||
(p/run! #(create-project conn %)
|
"big" preset-big
|
||||||
(for [uid (range num-users)
|
preset-small)]
|
||||||
pjid (range num-projects)]
|
(log/info "Using preset:" (pr-str preset))
|
||||||
[pjid uid]))
|
(deref (run preset)))
|
||||||
(p/run! #(create-page conn %)
|
|
||||||
(for [pjid(range num-projects)
|
|
||||||
paid (range num-pages)
|
|
||||||
uid (range num-users)]
|
|
||||||
[pjid paid uid]))
|
|
||||||
(p/promise 1)))
|
|
||||||
(finally
|
(finally
|
||||||
(mount/stop))))
|
(mount/stop))))
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"A errors handling for the http server."
|
"A errors handling for the http server."
|
||||||
(:require
|
(:require
|
||||||
[clojure.tools.logging :as log]
|
[clojure.tools.logging :as log]
|
||||||
|
[cuerdas.core :as str]
|
||||||
[io.aviso.exception :as e]))
|
[io.aviso.exception :as e]))
|
||||||
|
|
||||||
(defmulti handle-exception
|
(defmulti handle-exception
|
||||||
|
@ -16,9 +17,18 @@
|
||||||
|
|
||||||
(defmethod handle-exception :validation
|
(defmethod handle-exception :validation
|
||||||
[err req]
|
[err req]
|
||||||
(let [response (ex-data err)]
|
(let [header (get-in req [:headers "accept"])
|
||||||
{:status 400
|
response (ex-data err)]
|
||||||
:body response}))
|
(cond
|
||||||
|
(and (str/starts-with? header "text/html")
|
||||||
|
(= :spec-validation (:code response)))
|
||||||
|
{:status 400
|
||||||
|
:headers {"content-type" "text/html"}
|
||||||
|
:body (str "<pre style='font-size:16px'>" (:explain response) "</pre>\n")}
|
||||||
|
|
||||||
|
:else
|
||||||
|
{:status 400
|
||||||
|
:body response})))
|
||||||
|
|
||||||
(defmethod handle-exception :not-found
|
(defmethod handle-exception :not-found
|
||||||
[err req]
|
[err req]
|
||||||
|
@ -26,6 +36,10 @@
|
||||||
{:status 404
|
{:status 404
|
||||||
:body response}))
|
:body response}))
|
||||||
|
|
||||||
|
(defmethod handle-exception :service-error
|
||||||
|
[err req]
|
||||||
|
(handle-exception (.getCause err) req))
|
||||||
|
|
||||||
(defmethod handle-exception :parse
|
(defmethod handle-exception :parse
|
||||||
[err req]
|
[err req]
|
||||||
{:status 400
|
{:status 400
|
||||||
|
|
|
@ -26,18 +26,15 @@
|
||||||
{:desc "Initial projects tables"
|
{:desc "Initial projects tables"
|
||||||
:name "0003-projects"
|
:name "0003-projects"
|
||||||
:fn (mg/resource "migrations/0003.projects.sql")}
|
: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"
|
{:desc "Initial emails related tables"
|
||||||
:name "0005-emails"
|
:name "0004-emails"
|
||||||
:fn (mg/resource "migrations/0005.emails.sql")}
|
:fn (mg/resource "migrations/0004.emails.sql")}
|
||||||
{:desc "Initial images tables"
|
{:desc "Initial images tables"
|
||||||
:name "0006-images"
|
:name "0005-images"
|
||||||
:fn (mg/resource "migrations/0006.images.sql")}
|
:fn (mg/resource "migrations/0005.images.sql")}
|
||||||
{:desc "Initial icons tables"
|
{:desc "Initial icons tables"
|
||||||
:name "0007-icons"
|
:name "0006-icons"
|
||||||
:fn (mg/resource "migrations/0007.icons.sql")}
|
:fn (mg/resource "migrations/0006.icons.sql")}
|
||||||
]})
|
]})
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
|
@ -13,20 +13,22 @@
|
||||||
[]
|
[]
|
||||||
(require 'uxbox.services.queries.icons)
|
(require 'uxbox.services.queries.icons)
|
||||||
(require 'uxbox.services.queries.images)
|
(require 'uxbox.services.queries.images)
|
||||||
(require 'uxbox.services.queries.pages)
|
|
||||||
(require 'uxbox.services.queries.profiles)
|
|
||||||
(require 'uxbox.services.queries.projects)
|
(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
|
(defn- load-mutation-services
|
||||||
[]
|
[]
|
||||||
(require 'uxbox.services.mutations.auth)
|
|
||||||
(require 'uxbox.services.mutations.icons)
|
(require 'uxbox.services.mutations.icons)
|
||||||
(require 'uxbox.services.mutations.images)
|
(require 'uxbox.services.mutations.images)
|
||||||
(require 'uxbox.services.mutations.projects)
|
(require 'uxbox.services.mutations.projects)
|
||||||
(require 'uxbox.services.mutations.pages)
|
(require 'uxbox.services.mutations.project-files)
|
||||||
(require 'uxbox.services.mutations.profiles)
|
(require 'uxbox.services.mutations.project-pages)
|
||||||
(require 'uxbox.services.mutations.user-storage))
|
(require 'uxbox.services.mutations.auth)
|
||||||
|
(require 'uxbox.services.mutations.users)
|
||||||
|
(require 'uxbox.services.mutations.user-attrs))
|
||||||
|
|
||||||
(defstate query-services
|
(defstate query-services
|
||||||
:start (load-query-services))
|
:start (load-query-services))
|
||||||
|
|
|
@ -6,11 +6,13 @@
|
||||||
|
|
||||||
(ns uxbox.services.mutations
|
(ns uxbox.services.mutations
|
||||||
(:require
|
(:require
|
||||||
[uxbox.util.dispatcher :as uds]))
|
[uxbox.util.dispatcher :as uds]
|
||||||
|
[uxbox.util.exceptions :as ex]))
|
||||||
|
|
||||||
(uds/defservice handle
|
(uds/defservice handle
|
||||||
{:dispatch-by ::type
|
{:dispatch-by ::type
|
||||||
:interceptors [uds/spec-interceptor
|
:interceptors [uds/spec-interceptor
|
||||||
|
uds/wrap-errors
|
||||||
#_logging-interceptor
|
#_logging-interceptor
|
||||||
#_context-interceptor]})
|
#_context-interceptor]})
|
||||||
|
|
||||||
|
|
|
@ -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 <niwi@niwi.nz>
|
|
||||||
|
|
||||||
(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)))
|
|
142
backend/src/uxbox/services/mutations/project_files.clj
Normal file
142
backend/src/uxbox/services/mutations/project_files.clj
Normal file
|
@ -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 <niwi@niwi.nz>
|
||||||
|
|
||||||
|
(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))))
|
190
backend/src/uxbox/services/mutations/project_pages.clj
Normal file
190
backend/src/uxbox/services/mutations/project_pages.clj
Normal file
|
@ -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 <niwi@niwi.nz>
|
||||||
|
|
||||||
|
(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)))
|
|
@ -13,6 +13,7 @@
|
||||||
[uxbox.services.mutations :as sm]
|
[uxbox.services.mutations :as sm]
|
||||||
[uxbox.services.util :as su]
|
[uxbox.services.util :as su]
|
||||||
[uxbox.util.blob :as blob]
|
[uxbox.util.blob :as blob]
|
||||||
|
[uxbox.util.exceptions :as ex]
|
||||||
[uxbox.util.uuid :as uuid]))
|
[uxbox.util.uuid :as uuid]))
|
||||||
|
|
||||||
;; --- Helpers & Specs
|
;; --- Helpers & Specs
|
||||||
|
@ -22,6 +23,27 @@
|
||||||
(s/def ::token ::us/string)
|
(s/def ::token ::us/string)
|
||||||
(s/def ::user ::us/uuid)
|
(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
|
;; --- Mutation: Create Project
|
||||||
|
|
||||||
(declare create-project)
|
(declare create-project)
|
||||||
|
@ -31,11 +53,9 @@
|
||||||
:opt-un [::id]))
|
:opt-un [::id]))
|
||||||
|
|
||||||
(sm/defmutation ::create-project
|
(sm/defmutation ::create-project
|
||||||
[{:keys [id user name] :as params}]
|
[params]
|
||||||
(let [id (or id (uuid/next))
|
(db/with-atomic [conn db/pool]
|
||||||
sql "insert into projects (id, user_id, name)
|
(create-project conn params)))
|
||||||
values ($1, $2, $3) returning *"]
|
|
||||||
(db/query-one db/pool [sql id user name])))
|
|
||||||
|
|
||||||
(defn create-project
|
(defn create-project
|
||||||
[conn {:keys [id user name] :as params}]
|
[conn {:keys [id user name] :as params}]
|
||||||
|
@ -46,32 +66,49 @@
|
||||||
|
|
||||||
;; --- Mutation: Update Project
|
;; --- Mutation: Update Project
|
||||||
|
|
||||||
|
(declare update-project)
|
||||||
|
|
||||||
(s/def ::update-project
|
(s/def ::update-project
|
||||||
(s/keys :req-un [::user ::name ::id]))
|
(s/keys :req-un [::user ::name ::id]))
|
||||||
|
|
||||||
(sm/defmutation ::update-project
|
(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
|
(let [sql "update projects
|
||||||
set name = $3
|
set name = $3
|
||||||
where id = $1
|
where id = $1
|
||||||
and user_id = $2
|
and user_id = $2
|
||||||
and deleted_at is null
|
and deleted_at is null
|
||||||
returning *"]
|
returning *"]
|
||||||
(db/query-one db/pool [sql id user name])))
|
(db/query-one conn [sql id user name])))
|
||||||
|
|
||||||
;; --- Mutation: Delete Project
|
;; --- Mutation: Delete Project
|
||||||
|
|
||||||
|
(declare delete-project)
|
||||||
|
|
||||||
(s/def ::delete-project
|
(s/def ::delete-project
|
||||||
(s/keys :req-un [::id ::user]))
|
(s/keys :req-un [::id ::user]))
|
||||||
|
|
||||||
(sm/defmutation ::delete-project
|
(sm/defmutation ::delete-project
|
||||||
[{:keys [id user] :as params}]
|
[{:keys [id user] :as params}]
|
||||||
(let [sql "update projects
|
(db/with-atomic [conn db/pool]
|
||||||
set deleted_at = clock_timestamp()
|
(check-edition-permissions! conn user id)
|
||||||
where id = $1
|
(delete-project conn params)))
|
||||||
and user_id = $2
|
|
||||||
and deleted_at is null
|
(def ^:private sql:delete-project
|
||||||
returning id"]
|
"update projects
|
||||||
(-> (db/query-one db/pool [sql id user])
|
set deleted_at = clock_timestamp()
|
||||||
(p/then' su/raise-not-found-if-nil)
|
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))))
|
(p/then' su/constantly-nil))))
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.services.mutations.user-storage
|
(ns uxbox.services.mutations.user-attrs
|
||||||
(:require
|
(:require
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[uxbox.db :as db]
|
[uxbox.db :as db]
|
||||||
[uxbox.services.mutations :as sm]
|
[uxbox.services.mutations :as sm]
|
||||||
[uxbox.services.util :as su]
|
[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.blob :as blob]
|
||||||
[uxbox.util.spec :as us]))
|
[uxbox.util.spec :as us]))
|
||||||
|
|
||||||
|
@ -21,12 +21,12 @@
|
||||||
(s/def ::key ::us/string)
|
(s/def ::key ::us/string)
|
||||||
(s/def ::val any?)
|
(s/def ::val any?)
|
||||||
|
|
||||||
(s/def ::upsert-user-storage-entry
|
(s/def ::upsert-user-attr
|
||||||
(s/keys :req-un [::key ::val ::user]))
|
(s/keys :req-un [::key ::val ::user]))
|
||||||
|
|
||||||
(sm/defmutation ::upsert-user-storage-entry
|
(sm/defmutation ::upsert-user-attr
|
||||||
[{:keys [key val user] :as params}]
|
[{: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)
|
values ($1, $2, $3)
|
||||||
on conflict (user_id, key)
|
on conflict (user_id, key)
|
||||||
do update set val = $2"
|
do update set val = $2"
|
||||||
|
@ -36,12 +36,12 @@
|
||||||
|
|
||||||
;; --- Delete KVStore
|
;; --- Delete KVStore
|
||||||
|
|
||||||
(s/def ::delete-user-storage-entry
|
(s/def ::delete-user-attr
|
||||||
(s/keys :req-un [::key ::user]))
|
(s/keys :req-un [::key ::user]))
|
||||||
|
|
||||||
(sm/defmutation ::delete-user-storage-entry
|
(sm/defmutation ::delete-user-attr
|
||||||
[{:keys [user key] :as params}]
|
[{:keys [user key] :as params}]
|
||||||
(let [sql "delete from user_storage
|
(let [sql "delete from user_attrs
|
||||||
where user_id = $2
|
where user_id = $2
|
||||||
and key = $1"]
|
and key = $1"]
|
||||||
(-> (db/query-one db/pool [sql key user])
|
(-> (db/query-one db/pool [sql key user])
|
|
@ -4,7 +4,7 @@
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.services.mutations.profiles
|
(ns uxbox.services.mutations.users
|
||||||
(:require
|
(:require
|
||||||
[buddy.hashers :as hashers]
|
[buddy.hashers :as hashers]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
|
@ -19,10 +19,10 @@
|
||||||
[uxbox.media :as media]
|
[uxbox.media :as media]
|
||||||
[uxbox.services.mutations :as sm]
|
[uxbox.services.mutations :as sm]
|
||||||
[uxbox.services.util :as su]
|
[uxbox.services.util :as su]
|
||||||
[uxbox.services.queries.profiles :refer [get-profile
|
[uxbox.services.queries.users :refer [get-profile
|
||||||
decode-profile-row
|
decode-profile-row
|
||||||
strip-private-attrs
|
strip-private-attrs
|
||||||
resolve-thumbnail]]
|
resolve-thumbnail]]
|
||||||
[uxbox.util.blob :as blob]
|
[uxbox.util.blob :as blob]
|
||||||
[uxbox.util.exceptions :as ex]
|
[uxbox.util.exceptions :as ex]
|
||||||
[uxbox.util.spec :as us]
|
[uxbox.util.spec :as us]
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
and id != $1
|
and id != $1
|
||||||
) as val"]
|
) as val"]
|
||||||
(p/let [res1 (db/query-one conn [sql1 id username])
|
(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)
|
(when (:val res1)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code ::username-already-exists))
|
:code ::username-already-exists))
|
||||||
|
@ -83,9 +83,7 @@
|
||||||
(s/def ::update-profile
|
(s/def ::update-profile
|
||||||
(s/keys :req-un [::id ::username ::email ::fullname ::metadata]))
|
(s/keys :req-un [::id ::username ::email ::fullname ::metadata]))
|
||||||
|
|
||||||
(sm/defmutation :update-profile
|
(sm/defmutation ::update-profile
|
||||||
{:doc "Update self profile."
|
|
||||||
:spec ::update-profile}
|
|
||||||
[params]
|
[params]
|
||||||
(db/with-atomic [conn db/pool]
|
(db/with-atomic [conn db/pool]
|
||||||
(-> (p/resolved params)
|
(-> (p/resolved params)
|
||||||
|
@ -134,9 +132,7 @@
|
||||||
(def valid-image-types?
|
(def valid-image-types?
|
||||||
#{"image/jpeg", "image/png", "image/webp"})
|
#{"image/jpeg", "image/png", "image/webp"})
|
||||||
|
|
||||||
(sm/defmutation :update-profile-photo
|
(sm/defmutation ::update-profile-photo
|
||||||
{:doc "Update profile photo."
|
|
||||||
:spec ::update-profile-photo}
|
|
||||||
[{:keys [user file] :as params}]
|
[{:keys [user file] :as params}]
|
||||||
(letfn [(store-photo [{:keys [name path] :as upload}]
|
(letfn [(store-photo [{:keys [name path] :as upload}]
|
||||||
(let [filename (fs/name name)
|
(let [filename (fs/name name)
|
||||||
|
@ -149,16 +145,17 @@
|
||||||
set photo = $1
|
set photo = $1
|
||||||
where id = $2
|
where id = $2
|
||||||
and deleted_at is null
|
and deleted_at is null
|
||||||
returning *"]
|
returning id, photo"]
|
||||||
(-> (db/query-one db/pool [sql (str path) user])
|
(-> (db/query-one db/pool [sql (str path) user])
|
||||||
(p/then' su/raise-not-found-if-nil)
|
(p/then' su/raise-not-found-if-nil)
|
||||||
(p/then' strip-private-attrs)
|
;; (p/then' strip-private-attrs)
|
||||||
(p/then resolve-thumbnail))))]
|
(p/then resolve-thumbnail))))]
|
||||||
|
|
||||||
(when-not (valid-image-types? (:mtype file))
|
(when-not (valid-image-types? (:mtype file))
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :image-type-not-allowed
|
:code :image-type-not-allowed
|
||||||
:hint "Seems like you are uploading an invalid image."))
|
:hint "Seems like you are uploading an invalid image."))
|
||||||
|
|
||||||
(-> (store-photo file)
|
(-> (store-photo file)
|
||||||
(p/then update-user-photo))))
|
(p/then update-user-photo))))
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
(uds/defservice handle
|
(uds/defservice handle
|
||||||
{:dispatch-by ::type
|
{:dispatch-by ::type
|
||||||
:interceptors [uds/spec-interceptor
|
:interceptors [uds/spec-interceptor
|
||||||
|
uds/wrap-errors
|
||||||
#_logging-interceptor
|
#_logging-interceptor
|
||||||
#_context-interceptor]})
|
#_context-interceptor]})
|
||||||
|
|
||||||
|
|
|
@ -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 <niwi@niwi.nz>
|
|
||||||
|
|
||||||
(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)))))
|
|
55
backend/src/uxbox/services/queries/project_files.clj
Normal file
55
backend/src/uxbox/services/queries/project_files.clj
Normal file
|
@ -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 <niwi@niwi.nz>
|
||||||
|
|
||||||
|
(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)))))
|
145
backend/src/uxbox/services/queries/project_pages.clj
Normal file
145
backend/src/uxbox/services/queries/project_pages.clj
Normal file
|
@ -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 <niwi@niwi.nz>
|
||||||
|
|
||||||
|
(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)))))
|
|
@ -13,6 +13,8 @@
|
||||||
[uxbox.util.blob :as blob]
|
[uxbox.util.blob :as blob]
|
||||||
[uxbox.util.spec :as us]))
|
[uxbox.util.spec :as us]))
|
||||||
|
|
||||||
|
(declare decode-row)
|
||||||
|
|
||||||
;; --- Helpers & Specs
|
;; --- Helpers & Specs
|
||||||
|
|
||||||
(s/def ::id ::us/uuid)
|
(s/def ::id ::us/uuid)
|
||||||
|
@ -22,19 +24,26 @@
|
||||||
|
|
||||||
;; --- Query: Projects
|
;; --- 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
|
(def ^:private projects-sql
|
||||||
"select distinct on (p.id, p.created_at)
|
"select p.*
|
||||||
p.*,
|
from project_users as pu
|
||||||
array_agg(pg.id) over (
|
inner join projects as p on (p.id = pu.project_id)
|
||||||
partition by p.id
|
where pu.can_edit = true
|
||||||
order by pg.created_at
|
and pu.user_id = $1;")
|
||||||
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")
|
|
||||||
|
|
||||||
(s/def ::projects
|
(s/def ::projects
|
||||||
(s/keys :req-un [::user]))
|
(s/keys :req-un [::user]))
|
||||||
|
@ -42,5 +51,12 @@
|
||||||
(sq/defquery ::projects
|
(sq/defquery ::projects
|
||||||
[{:keys [user] :as params}]
|
[{:keys [user] :as params}]
|
||||||
(-> (db/query db/pool [projects-sql user])
|
(-> (db/query db/pool [projects-sql user])
|
||||||
(p/then (fn [rows]
|
(p/then' (partial mapv decode-row))))
|
||||||
(mapv #(update % :pages vec) rows)))))
|
|
||||||
|
;; --- Helpers
|
||||||
|
|
||||||
|
(defn decode-row
|
||||||
|
[{:keys [metadata] :as row}]
|
||||||
|
(when row
|
||||||
|
(cond-> row
|
||||||
|
metadata (assoc :metadata (blob/decode metadata)))))
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.services.queries.user-storage
|
(ns uxbox.services.queries.user-attrs
|
||||||
(:require
|
(:require
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
|
@ -20,13 +20,13 @@
|
||||||
(cond-> row
|
(cond-> row
|
||||||
val (assoc :val (blob/decode val)))))
|
val (assoc :val (blob/decode val)))))
|
||||||
|
|
||||||
(s/def ::user-storage-entry
|
(s/def ::user-attr
|
||||||
(s/keys :req-un [::key ::user]))
|
(s/keys :req-un [::key ::user]))
|
||||||
|
|
||||||
(sq/defquery ::user-storage-entry
|
(sq/defquery ::user-attr
|
||||||
[{:keys [key user]}]
|
[{:keys [key user]}]
|
||||||
(let [sql "select kv.*
|
(let [sql "select kv.*
|
||||||
from user_storage as kv
|
from user_attrs as kv
|
||||||
where kv.user_id = $2
|
where kv.user_id = $2
|
||||||
and kv.key = $1"]
|
and kv.key = $1"]
|
||||||
(-> (db/query-one db/pool [sql key user])
|
(-> (db/query-one db/pool [sql key user])
|
|
@ -4,7 +4,7 @@
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.services.queries.profiles
|
(ns uxbox.services.queries.users
|
||||||
(:require
|
(:require
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
|
@ -51,9 +51,7 @@
|
||||||
(s/def ::profile
|
(s/def ::profile
|
||||||
(s/keys :req-un [::user]))
|
(s/keys :req-un [::user]))
|
||||||
|
|
||||||
(sq/defquery :profile
|
(sq/defquery ::profile
|
||||||
{:doc "Retrieve the user profile."
|
|
||||||
:spec ::profile}
|
|
||||||
[{:keys [user] :as params}]
|
[{:keys [user] :as params}]
|
||||||
(-> (get-profile db/pool user)
|
(-> (get-profile db/pool user)
|
||||||
(p/then' strip-private-attrs)))
|
(p/then' strip-private-attrs)))
|
|
@ -28,7 +28,7 @@
|
||||||
IDispatcher
|
IDispatcher
|
||||||
(add [this key f metadata]
|
(add [this key f metadata]
|
||||||
(.put ^Map reg key (MapEntry/create f metadata))
|
(.put ^Map reg key (MapEntry/create f metadata))
|
||||||
nil)
|
this)
|
||||||
|
|
||||||
clojure.lang.IDeref
|
clojure.lang.IDeref
|
||||||
(deref [_]
|
(deref [_]
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
|
|
||||||
(defn dispatcher?
|
(defn dispatcher?
|
||||||
[v]
|
[v]
|
||||||
(instance? Dispatcher v))
|
(instance? IDispatcher v))
|
||||||
|
|
||||||
(defmacro defservice
|
(defmacro defservice
|
||||||
[sname {:keys [dispatch-by interceptors]}]
|
[sname {:keys [dispatch-by interceptors]}]
|
||||||
|
@ -118,5 +118,16 @@
|
||||||
:code :spec-validation
|
:code :spec-validation
|
||||||
:explain (with-out-str
|
:explain (with-out-str
|
||||||
(expound/printer data))
|
(expound/printer data))
|
||||||
:data data))))
|
:data (::s/problems data)))))
|
||||||
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))))})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
(s/def ::code keyword?)
|
(s/def ::code keyword?)
|
||||||
(s/def ::mesage string?)
|
(s/def ::mesage string?)
|
||||||
(s/def ::hint string?)
|
(s/def ::hint string?)
|
||||||
(s/def ::cause #(instance? Exception %))
|
(s/def ::cause #(instance? Throwable %))
|
||||||
(s/def ::error-params
|
(s/def ::error-params
|
||||||
(s/keys :req-un [::type]
|
(s/keys :req-un [::type]
|
||||||
:opt-un [::code
|
:opt-un [::code
|
||||||
|
|
|
@ -4,213 +4,5 @@
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.util.http)
|
(ns uxbox.util.http
|
||||||
|
"Http related helpers.")
|
||||||
(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)))
|
|
||||||
|
|
|
@ -157,7 +157,9 @@
|
||||||
(into rp p)
|
(into rp p)
|
||||||
(first n)
|
(first n)
|
||||||
(rest 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
|
(defn- process-param-tokens
|
||||||
[sql]
|
[sql]
|
||||||
|
@ -168,7 +170,7 @@
|
||||||
(def ^:private select-formatters
|
(def ^:private select-formatters
|
||||||
[#(format-exprs (::select %) {:prefix "SELECT "})
|
[#(format-exprs (::select %) {:prefix "SELECT "})
|
||||||
#(format-exprs (::from %) {:prefix "FROM "})
|
#(format-exprs (::from %) {:prefix "FROM "})
|
||||||
#(format-exprs (::join %))
|
#(format-exprs (::join %) {:join-with " "})
|
||||||
#(format-exprs (::where %) {:prefix "WHERE ("
|
#(format-exprs (::where %) {:prefix "WHERE ("
|
||||||
:join-with ") AND ("
|
:join-with ") AND ("
|
||||||
:suffix ")"})
|
:suffix ")"})
|
||||||
|
|
|
@ -6,9 +6,10 @@
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[mount.core :as mount]
|
[mount.core :as mount]
|
||||||
[datoteka.storages :as st]
|
[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.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.fixtures :as fixtures]
|
||||||
[uxbox.migrations]
|
[uxbox.migrations]
|
||||||
[uxbox.media]
|
[uxbox.media]
|
||||||
|
@ -24,6 +25,8 @@
|
||||||
#'uxbox.config/secret
|
#'uxbox.config/secret
|
||||||
#'uxbox.core/system
|
#'uxbox.core/system
|
||||||
#'uxbox.db/pool
|
#'uxbox.db/pool
|
||||||
|
#'uxbox.services.init/query-services
|
||||||
|
#'uxbox.services.init/mutation-services
|
||||||
#'uxbox.migrations/migrations
|
#'uxbox.migrations/migrations
|
||||||
#'uxbox.media/assets-storage
|
#'uxbox.media/assets-storage
|
||||||
#'uxbox.media/media-storage
|
#'uxbox.media/media-storage
|
||||||
|
@ -64,39 +67,44 @@
|
||||||
|
|
||||||
(defn create-user
|
(defn create-user
|
||||||
[conn i]
|
[conn i]
|
||||||
(profiles/create-profile conn {:id (mk-uuid "user" i)
|
(users/create-profile conn {:id (mk-uuid "user" i)
|
||||||
:fullname (str "User " i)
|
:fullname (str "User " i)
|
||||||
:username (str "user" i)
|
:username (str "user" i)
|
||||||
:email (str "user" i ".test@uxbox.io")
|
:email (str "user" i ".test@uxbox.io")
|
||||||
:password "123123"
|
:password "123123"
|
||||||
:metadata {}}))
|
:metadata {}}))
|
||||||
|
|
||||||
(defn create-project
|
(defn create-project
|
||||||
[conn user-id i]
|
[conn user-id i]
|
||||||
(projects/create-project conn {:id (mk-uuid "project" i)
|
(projects/create-project conn {:id (mk-uuid "project" i)
|
||||||
:user user-id
|
:user user-id
|
||||||
|
:version 1
|
||||||
:name (str "sample project " i)}))
|
: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)
|
(pages/create-page conn {:id (mk-uuid "page" i)
|
||||||
:user uid
|
:user user-id
|
||||||
:project-id pid
|
:file-id file-id
|
||||||
:name (str "page" i)
|
:name (str "page" i)
|
||||||
:data {:shapes []}
|
:ordering i
|
||||||
|
:data {}
|
||||||
:metadata {}}))
|
:metadata {}}))
|
||||||
|
|
||||||
(defn handle-error
|
(defn handle-error
|
||||||
[err]
|
[err]
|
||||||
(cond
|
(if (instance? java.util.concurrent.ExecutionException err)
|
||||||
(instance? clojure.lang.ExceptionInfo err)
|
|
||||||
(ex-data err)
|
|
||||||
|
|
||||||
(instance? java.util.concurrent.ExecutionException err)
|
|
||||||
(handle-error (.getCause err))
|
(handle-error (.getCause err))
|
||||||
|
err))
|
||||||
:else
|
|
||||||
[err nil]))
|
|
||||||
|
|
||||||
(defmacro try-on
|
(defmacro try-on
|
||||||
[expr]
|
[expr]
|
||||||
|
@ -126,21 +134,28 @@
|
||||||
{:error (handle-error e#)
|
{:error (handle-error e#)
|
||||||
:result nil})))
|
: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!
|
(defn print-result!
|
||||||
[{:keys [error result]}]
|
[{:keys [error result]}]
|
||||||
(if error
|
(if error
|
||||||
(do
|
(do
|
||||||
(println "====> START ERROR")
|
(println "====> START ERROR")
|
||||||
(if (= :spec-validation (:code error))
|
(print-error! error)
|
||||||
(s/explain-out (:data error))
|
|
||||||
(prn error))
|
|
||||||
(println "====> END ERROR"))
|
(println "====> END ERROR"))
|
||||||
(do
|
(do
|
||||||
(println "====> START RESPONSE")
|
(println "====> START RESPONSE")
|
||||||
(prn result)
|
(prn result)
|
||||||
(println "====> END RESPONSE"))))
|
(println "====> END RESPONSE"))))
|
||||||
|
|
||||||
|
|
||||||
(defn exception?
|
(defn exception?
|
||||||
[v]
|
[v]
|
||||||
(instance? Throwable v))
|
(instance? Throwable v))
|
||||||
|
@ -154,6 +169,11 @@
|
||||||
(let [data (ex-data e)]
|
(let [data (ex-data e)]
|
||||||
(= type (:type data))))
|
(= type (:type data))))
|
||||||
|
|
||||||
|
(defn ex-of-code?
|
||||||
|
[e code]
|
||||||
|
(let [data (ex-data e)]
|
||||||
|
(= code (:code data))))
|
||||||
|
|
||||||
(defn ex-with-code?
|
(defn ex-with-code?
|
||||||
[e code]
|
[e code]
|
||||||
(let [data (ex-data e)]
|
(let [data (ex-data e)]
|
||||||
|
|
|
@ -25,20 +25,20 @@
|
||||||
(t/is (contains? result :reply-to))
|
(t/is (contains? result :reply-to))
|
||||||
(t/is (vector? (:body result)))))
|
(t/is (vector? (:body result)))))
|
||||||
|
|
||||||
(t/deftest email-sending-and-sendmail-job
|
;; (t/deftest email-sending-and-sendmail-job
|
||||||
(let [res @(emails/send! emails/register {:to "example@uxbox.io" :name "foo"})]
|
;; (let [res @(emails/send! emails/register {:to "example@uxbox.io" :name "foo"})]
|
||||||
(t/is (nil? res)))
|
;; (t/is (nil? res)))
|
||||||
(with-mock mock
|
;; (with-mock mock
|
||||||
{:target 'uxbox.jobs.sendmail/impl-sendmail
|
;; {:target 'uxbox.jobs.sendmail/impl-sendmail
|
||||||
:return (p/resolved nil)}
|
;; :return (p/resolved nil)}
|
||||||
|
|
||||||
(let [res @(uxbox.jobs.sendmail/send-emails {})]
|
;; (let [res @(uxbox.jobs.sendmail/send-emails {})]
|
||||||
(t/is (= 1 res))
|
;; (t/is (= 1 res))
|
||||||
(t/is (:called? @mock))
|
;; (t/is (:called? @mock))
|
||||||
(t/is (= 1 (:call-count @mock))))
|
;; (t/is (= 1 (:call-count @mock))))
|
||||||
|
|
||||||
(let [res @(uxbox.jobs.sendmail/send-emails {})]
|
;; (let [res @(uxbox.jobs.sendmail/send-emails {})]
|
||||||
(t/is (= 0 res))
|
;; (t/is (= 0 res))
|
||||||
(t/is (:called? @mock))
|
;; (t/is (:called? @mock))
|
||||||
(t/is (= 1 (:call-count @mock))))))
|
;; (t/is (= 1 (:call-count @mock))))))
|
||||||
|
|
||||||
|
|
|
@ -24,9 +24,14 @@
|
||||||
:scope "foobar"}
|
:scope "foobar"}
|
||||||
out (th/try-on! (sm/handle event))]
|
out (th/try-on! (sm/handle event))]
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (map? (:error out)))
|
(let [error (:error out)]
|
||||||
(t/is (= (get-in out [:error :type]) :validation))
|
(t/is (th/ex-info? error))
|
||||||
(t/is (= (get-in out [:error :code]) :uxbox.services.mutations.auth/wrong-credentials))))
|
(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
|
(t/deftest success-auth
|
||||||
(let [user @(th/create-user db/pool 1)
|
(let [user @(th/create-user db/pool 1)
|
||||||
|
|
|
@ -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)))))))
|
|
76
backend/test/uxbox/tests/test_services_project_files.clj
Normal file
76
backend/test/uxbox/tests/test_services_project_files.clj
Normal file
|
@ -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
|
84
backend/test/uxbox/tests/test_services_project_pages.clj
Normal file
84
backend/test/uxbox/tests/test_services_project_pages.clj
Normal file
|
@ -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)))))
|
|
@ -11,17 +11,13 @@
|
||||||
(t/use-fixtures :once th/state-init)
|
(t/use-fixtures :once th/state-init)
|
||||||
(t/use-fixtures :each th/database-reset)
|
(t/use-fixtures :each th/database-reset)
|
||||||
|
|
||||||
;; TODO: migrate from try-on to try-on!
|
(t/deftest query-projects
|
||||||
|
|
||||||
(t/deftest query-project-list
|
|
||||||
(let [user @(th/create-user db/pool 1)
|
(let [user @(th/create-user db/pool 1)
|
||||||
proj @(th/create-project db/pool (:id user) 1)
|
proj @(th/create-project db/pool (:id user) 1)
|
||||||
data {::sq/type :projects
|
data {::sq/type :projects
|
||||||
:user (:id user)}
|
:user (:id user)}
|
||||||
out (th/try-on! (sq/handle data))]
|
out (th/try-on! (sq/handle data))]
|
||||||
|
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
|
|
||||||
(t/is (nil? (:error out)))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (= 1 (count (:result out))))
|
(t/is (= 1 (count (:result out))))
|
||||||
(t/is (= (:id proj) (get-in out [:result 0 :id])))
|
(t/is (= (:id proj) (get-in out [:result 0 :id])))
|
||||||
|
@ -32,11 +28,10 @@
|
||||||
data {::sm/type :create-project
|
data {::sm/type :create-project
|
||||||
:user (:id user)
|
:user (:id user)
|
||||||
:name "test project"}
|
:name "test project"}
|
||||||
[err rsp] (th/try-on (sm/handle data))]
|
out (th/try-on! (sm/handle data))]
|
||||||
;; (prn "RESPONSE:" err rsp)
|
;; (th/print-result! out)
|
||||||
(t/is (nil? err))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (= (:user data) (:user-id rsp)))
|
(t/is (= (:name data) (get-in out [:result :name])))))
|
||||||
(t/is (= (:name data) (:name rsp)))))
|
|
||||||
|
|
||||||
(t/deftest mutation-update-project
|
(t/deftest mutation-update-project
|
||||||
(let [user @(th/create-user db/pool 1)
|
(let [user @(th/create-user db/pool 1)
|
||||||
|
@ -45,12 +40,12 @@
|
||||||
:id (:id proj)
|
:id (:id proj)
|
||||||
:name "test project mod"
|
:name "test project mod"
|
||||||
:user (:id user)}
|
:user (:id user)}
|
||||||
[err rsp] (th/try-on (sm/handle data))]
|
out (th/try-on! (sm/handle data))]
|
||||||
;; (prn "RESPONSE:" err rsp)
|
;; (th/print-result! out)
|
||||||
(t/is (nil? err))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (= (:id data) (:id rsp)))
|
(t/is (= (:id data) (get-in out [:result :id])))
|
||||||
(t/is (= (:user data) (:user-id rsp)))
|
(t/is (= (:user data) (get-in out [:result :user-id])))
|
||||||
(t/is (= (:name data) (:name rsp)))))
|
(t/is (= (:name data) (get-in out [:result :name])))))
|
||||||
|
|
||||||
(t/deftest mutation-delete-project
|
(t/deftest mutation-delete-project
|
||||||
(let [user @(th/create-user db/pool 1)
|
(let [user @(th/create-user db/pool 1)
|
||||||
|
@ -58,12 +53,13 @@
|
||||||
data {::sm/type :delete-project
|
data {::sm/type :delete-project
|
||||||
:id (:id proj)
|
:id (:id proj)
|
||||||
:user (:id user)}
|
:user (:id user)}
|
||||||
[err rsp] (th/try-on (sm/handle data))]
|
out (th/try-on! (sm/handle data))]
|
||||||
;; (prn "RESPONSE:" err rsp)
|
;; (th/print-result! out)
|
||||||
(t/is (nil? err))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (nil? rsp))
|
(t/is (nil? (:result out)))
|
||||||
|
|
||||||
(let [sql "SELECT * FROM projects
|
(let [sql "select * from projects where user_id=$1 and deleted_at is null"
|
||||||
WHERE user_id=$1 AND deleted_at is null"
|
|
||||||
res @(db/query db/pool [sql (:id user)])]
|
res @(db/query db/pool [sql (:id user)])]
|
||||||
(t/is (empty? res)))))
|
(t/is (empty? res)))))
|
||||||
|
|
||||||
|
;; TODO: add permisions related tests
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
(ns uxbox.tests.test-services-user-storage
|
(ns uxbox.tests.test-services-user-attrs
|
||||||
(:require
|
(:require
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[clojure.test :as t]
|
[clojure.test :as t]
|
||||||
|
@ -12,16 +12,22 @@
|
||||||
(t/use-fixtures :once th/state-init)
|
(t/use-fixtures :once th/state-init)
|
||||||
(t/use-fixtures :each th/database-reset)
|
(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 [{: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"
|
:key "foobar"
|
||||||
:user id}))]
|
:user id}))]
|
||||||
(t/is (nil? (:result out)))
|
(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
|
:user id
|
||||||
:key "foobar"
|
:key "foobar"
|
||||||
:val {:some #{:value}}}))]
|
:val {:some #{:value}}}))]
|
||||||
|
@ -29,7 +35,7 @@
|
||||||
(t/is (nil? (:error out)))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (nil? (:result 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"
|
:key "foobar"
|
||||||
:user id}))]
|
:user id}))]
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
|
@ -37,18 +43,23 @@
|
||||||
(t/is (= {:some #{:value}} (get-in out [:result :val])))
|
(t/is (= {:some #{:value}} (get-in out [:result :val])))
|
||||||
(t/is (= "foobar" (get-in out [:result :key]))))
|
(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
|
:user id
|
||||||
:key "foobar"}))]
|
:key "foobar"}))]
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (nil? (:error out)))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (nil? (:result 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"
|
:key "foobar"
|
||||||
:user id}))]
|
:user id}))]
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (nil? (:result out)))
|
(t/is (nil? (:result out)))
|
||||||
(t/is (map? (:error out)))
|
(let [error (:error out)]
|
||||||
(t/is (= :not-found (get-in out [:error :type]))))))
|
(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))))))
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
(ns uxbox.tests.test-users
|
(ns uxbox.tests.test-services-users
|
||||||
(:require
|
(:require
|
||||||
[clojure.test :as t]
|
[clojure.test :as t]
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[datoteka.core :as fs]
|
[datoteka.core :as fs]
|
||||||
[vertx.core :as vc]
|
|
||||||
[uxbox.db :as db]
|
[uxbox.db :as db]
|
||||||
[uxbox.services.mutations :as sm]
|
[uxbox.services.mutations :as sm]
|
||||||
[uxbox.services.queries :as sq]
|
[uxbox.services.queries :as sq]
|
||||||
|
@ -16,47 +15,48 @@
|
||||||
|
|
||||||
(t/deftest test-query-profile
|
(t/deftest test-query-profile
|
||||||
(let [user @(th/create-user db/pool 1)
|
(let [user @(th/create-user db/pool 1)
|
||||||
event {::sq/type :profile
|
data {::sq/type :profile
|
||||||
:user (:id user)}
|
:user (:id user)}
|
||||||
[err rsp] (th/try-on (sq/handle event))]
|
|
||||||
;; (println "RESPONSE:" resp)))
|
out (th/try-on! (sq/handle data))]
|
||||||
(t/is (nil? err))
|
;; (th/print-result! out)
|
||||||
(t/is (= (:fullname rsp) "User 1"))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (= (:username rsp) "user1"))
|
(t/is (= "User 1" (get-in out [:result :fullname])))
|
||||||
(t/is (= (:metadata rsp) {}))
|
(t/is (= "user1" (get-in out [:result :username])))
|
||||||
(t/is (= (:email rsp) "user1.test@uxbox.io"))
|
(t/is (= "user1.test@uxbox.io" (get-in out [:result :email])))
|
||||||
(t/is (not (contains? rsp :password)))))
|
(t/is (not (contains? (:result out) :password)))))
|
||||||
|
|
||||||
(t/deftest test-mutation-update-profile
|
(t/deftest test-mutation-update-profile
|
||||||
(let [user @(th/create-user db/pool 1)
|
(let [user @(th/create-user db/pool 1)
|
||||||
event (assoc user
|
data (assoc user
|
||||||
::sm/type :update-profile
|
::sm/type :update-profile
|
||||||
:fullname "Full Name"
|
:fullname "Full Name"
|
||||||
:username "user222"
|
:username "user222"
|
||||||
:metadata {:foo "bar"}
|
:metadata {:foo "bar"}
|
||||||
:email "user222@uxbox.io")
|
:email "user222@uxbox.io")
|
||||||
[err data] (th/try-on (sm/handle event))]
|
out (th/try-on! (sm/handle data))]
|
||||||
;; (println "RESPONSE:" err data)
|
;; (th/print-result! out)
|
||||||
(t/is (nil? err))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (= (:fullname data) "Full Name"))
|
(t/is (= (:fullname data) (get-in out [:result :fullname])))
|
||||||
(t/is (= (:username data) "user222"))
|
(t/is (= (:username data) (get-in out [:result :username])))
|
||||||
(t/is (= (:metadata data) {:foo "bar"}))
|
(t/is (= (:email data) (get-in out [:result :email])))
|
||||||
(t/is (= (:email data) "user222@uxbox.io"))
|
(t/is (= (:metadata data) (get-in out [:result :metadata])))
|
||||||
(t/is (not (contains? data :password)))))
|
(t/is (not (contains? (:result out) :password)))))
|
||||||
|
|
||||||
(t/deftest test-mutation-update-profile-photo
|
(t/deftest test-mutation-update-profile-photo
|
||||||
(let [user @(th/create-user db/pool 1)
|
(let [user @(th/create-user db/pool 1)
|
||||||
event {::sm/type :update-profile-photo
|
data {::sm/type :update-profile-photo
|
||||||
:user (:id user)
|
:user (:id user)
|
||||||
:file {:name "sample.jpg"
|
:file {:name "sample.jpg"
|
||||||
:path (fs/path "test/uxbox/tests/_files/sample.jpg")
|
:path (fs/path "test/uxbox/tests/_files/sample.jpg")
|
||||||
:size 123123
|
:size 123123
|
||||||
:mtype "image/jpeg"}}
|
:mtype "image/jpeg"}}
|
||||||
[err rsp] (th/try-on (sm/handle event))]
|
|
||||||
;; (prn "RESPONSE:" [err rsp])
|
out (th/try-on! (sm/handle data))]
|
||||||
(t/is (nil? err))
|
;; (th/print-result! out)
|
||||||
(t/is (= (:id user) (:id rsp)))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (str/starts-with? (:photo rsp) "http"))))
|
(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
|
;; (t/deftest test-mutation-register-profile
|
||||||
;; (let[data {:fullname "Full Name"
|
;; (let[data {:fullname "Full Name"
|
|
@ -34,19 +34,20 @@
|
||||||
|
|
||||||
(t/deftest parse-invalid-svg-1
|
(t/deftest parse-invalid-svg-1
|
||||||
(let [image (io/resource "uxbox/tests/_files/sample.jpg")
|
(let [image (io/resource "uxbox/tests/_files/sample.jpg")
|
||||||
result (th/try! (svg/parse image))]
|
out (th/try! (svg/parse image))]
|
||||||
(t/is (map? (:error result)))
|
|
||||||
(t/is (= (get-in result [:error :code])
|
(let [error (:error out)]
|
||||||
::svg/invalid-input))))
|
(t/is (th/ex-info? error))
|
||||||
|
(t/is (th/ex-of-code? error ::svg/invalid-input)))))
|
||||||
|
|
||||||
(t/deftest parse-invalid-svg-2
|
(t/deftest parse-invalid-svg-2
|
||||||
(let [result (th/try! (svg/parse-string ""))]
|
(let [out (th/try! (svg/parse-string ""))]
|
||||||
(t/is (map? (:error result)))
|
(let [error (:error out)]
|
||||||
(t/is (= (get-in result [:error :code])
|
(t/is (th/ex-info? error))
|
||||||
::svg/invalid-input))))
|
(t/is (th/ex-of-code? error ::svg/invalid-input)))))
|
||||||
|
|
||||||
(t/deftest parse-invalid-svg-3
|
(t/deftest parse-invalid-svg-3
|
||||||
(let [result (th/try! (svg/parse-string "<svg></svg>"))]
|
(let [out (th/try! (svg/parse-string "<svg></svg>"))]
|
||||||
(t/is (map? (:error result)))
|
(let [error (:error out)]
|
||||||
(t/is (= (get-in result [:error :code])
|
(t/is (th/ex-info? error))
|
||||||
::svg/invalid-result))))
|
(t/is (th/ex-of-code? error ::svg/invalid-result)))))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue