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