From de57630c14e838c712d11966b13eb3f4e3a3938a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sun, 20 Nov 2016 20:04:52 +0100 Subject: [PATCH] Import backend code inside the repository. --- backend/profiles.clj | 8 + backend/project.clj | 48 +++ backend/resources/.catacumba.basedir | 0 backend/resources/builtin.edn | 11 + backend/resources/config/default.edn | 37 ++ backend/resources/config/test.edn | 18 + backend/resources/emails/en/register.mustache | 17 + backend/resources/migrations/0000.main.up.sql | 28 ++ .../resources/migrations/0001.txlog.up.sql | 17 + backend/resources/migrations/0002.auth.up.sql | 49 +++ .../resources/migrations/0003.projects.up.sql | 62 ++++ .../resources/migrations/0004.pages.up.sql | 74 ++++ .../resources/migrations/0005.kvstore.up.sql | 19 ++ .../resources/migrations/0006.emails.up.sql | 27 ++ .../resources/migrations/0007.images.up.sql | 59 ++++ .../resources/migrations/0008.icons.up.sql | 56 ++++ .../resources/migrations/XXXX.workers.up.sql | 11 + .../public/static/images/email/facebook.png | Bin 0 -> 2356 bytes .../public/static/images/email/img-header.jpg | Bin 0 -> 39021 bytes .../public/static/images/email/linkedin.png | Bin 0 -> 750 bytes .../public/static/images/email/logo.png | Bin 0 -> 6696 bytes .../public/static/images/email/twitter.png | Bin 0 -> 1192 bytes backend/resources/sql/cli.sql | 28 ++ backend/resources/sql/emails.sql | 45 +++ backend/resources/sql/icons.sql | 67 ++++ backend/resources/sql/images.sql | 66 ++++ backend/resources/sql/kvstore.sql | 17 + backend/resources/sql/pages.sql | 89 +++++ backend/resources/sql/projects.sql | 64 ++++ backend/resources/sql/users.sql | 69 ++++ backend/resources/sql/workers.sql | 53 +++ backend/scripts/fixtures.sh | 2 + backend/scripts/run.sh | 3 + backend/scripts/smtpd.sh | 2 + backend/src/data_readers.clj | 1 + backend/src/uxbox/cli/collimp.clj | 172 ++++++++++ backend/src/uxbox/cli/sql.clj | 4 + backend/src/uxbox/config.clj | 52 +++ backend/src/uxbox/db.clj | 89 +++++ backend/src/uxbox/emails.clj | 14 + backend/src/uxbox/emails/core.clj | 89 +++++ backend/src/uxbox/emails/layouts.clj | 228 +++++++++++++ backend/src/uxbox/emails/users.clj | 77 +++++ backend/src/uxbox/fixtures.clj | 86 +++++ backend/src/uxbox/frontend.clj | 155 +++++++++ backend/src/uxbox/frontend/auth.clj | 33 ++ backend/src/uxbox/frontend/debug_emails.clj | 68 ++++ backend/src/uxbox/frontend/errors.clj | 74 ++++ backend/src/uxbox/frontend/icons.clj | 156 +++++++++ backend/src/uxbox/frontend/images.clj | 196 +++++++++++ backend/src/uxbox/frontend/kvstore.clj | 60 ++++ backend/src/uxbox/frontend/pages.clj | 119 +++++++ backend/src/uxbox/frontend/projects.clj | 73 ++++ backend/src/uxbox/frontend/svgparse.clj | 22 ++ backend/src/uxbox/frontend/users.clj | 149 ++++++++ backend/src/uxbox/images.clj | 66 ++++ backend/src/uxbox/locks.clj | 21 ++ backend/src/uxbox/main.clj | 92 +++++ backend/src/uxbox/media.clj | 39 +++ backend/src/uxbox/migrations.clj | 78 +++++ backend/src/uxbox/portation.clj | 114 +++++++ backend/src/uxbox/scheduled_jobs.clj | 23 ++ backend/src/uxbox/scheduled_jobs/emails.clj | 123 +++++++ backend/src/uxbox/scheduled_jobs/garbage.clj | 28 ++ backend/src/uxbox/services.clj | 61 ++++ backend/src/uxbox/services/auth.clj | 47 +++ backend/src/uxbox/services/core.clj | 27 ++ backend/src/uxbox/services/icons.clj | 228 +++++++++++++ backend/src/uxbox/services/images.clj | 236 +++++++++++++ backend/src/uxbox/services/kvstore.clj | 87 +++++ backend/src/uxbox/services/pages.clj | 226 +++++++++++++ backend/src/uxbox/services/projects.clj | 143 ++++++++ backend/src/uxbox/services/svgparse.clj | 95 ++++++ backend/src/uxbox/services/users.clj | 317 ++++++++++++++++++ backend/src/uxbox/sql.clj | 18 + backend/src/uxbox/util/blob.clj | 21 ++ backend/src/uxbox/util/cli.clj | 12 + backend/src/uxbox/util/closeable.clj | 31 ++ backend/src/uxbox/util/data.clj | 52 +++ backend/src/uxbox/util/exceptions.clj | 21 ++ backend/src/uxbox/util/images.clj | 44 +++ backend/src/uxbox/util/quartz.clj | 139 ++++++++ backend/src/uxbox/util/response.clj | 66 ++++ backend/src/uxbox/util/snappy.clj | 40 +++ backend/src/uxbox/util/spec.clj | 113 +++++++ backend/src/uxbox/util/tempfile.clj | 17 + backend/src/uxbox/util/template.clj | 56 ++++ backend/src/uxbox/util/time.clj | 76 +++++ backend/src/uxbox/util/token.clj | 24 ++ backend/src/uxbox/util/transit.clj | 71 ++++ backend/src/uxbox/util/uuid.clj | 25 ++ backend/src/uxbox/util/workers.clj | 74 ++++ backend/test/storages/tests.clj | 106 ++++++ backend/test/uxbox/tests/_files/sample.jpg | Bin 0 -> 312043 bytes backend/test/uxbox/tests/_files/sample1.svg | 20 ++ backend/test/uxbox/tests/_files/sample2.svg | 54 +++ backend/test/uxbox/tests/helpers.clj | 209 ++++++++++++ backend/test/uxbox/tests/test_auth.clj | 53 +++ backend/test/uxbox/tests/test_icons.clj | 163 +++++++++ backend/test/uxbox/tests/test_images.clj | 169 ++++++++++ backend/test/uxbox/tests/test_kvstore.clj | 65 ++++ backend/test/uxbox/tests/test_pages.clj | 224 +++++++++++++ backend/test/uxbox/tests/test_projects.clj | 92 +++++ backend/test/uxbox/tests/test_svgparse.clj | 89 +++++ backend/test/uxbox/tests/test_txlog.clj | 19 ++ backend/test/uxbox/tests/test_users.clj | 120 +++++++ backend/vendor/executors/core.clj | 176 ++++++++++ backend/vendor/storages/core.clj | 59 ++++ backend/vendor/storages/fs/local.clj | 104 ++++++ backend/vendor/storages/fs/misc.clj | 111 ++++++ backend/vendor/storages/impl.clj | 115 +++++++ backend/vendor/storages/proto.clj | 38 +++ backend/vendor/storages/util.clj | 140 ++++++++ 113 files changed, 8040 insertions(+) create mode 100644 backend/profiles.clj create mode 100644 backend/project.clj create mode 100644 backend/resources/.catacumba.basedir create mode 100644 backend/resources/builtin.edn create mode 100644 backend/resources/config/default.edn create mode 100644 backend/resources/config/test.edn create mode 100644 backend/resources/emails/en/register.mustache create mode 100644 backend/resources/migrations/0000.main.up.sql create mode 100644 backend/resources/migrations/0001.txlog.up.sql create mode 100644 backend/resources/migrations/0002.auth.up.sql create mode 100644 backend/resources/migrations/0003.projects.up.sql create mode 100644 backend/resources/migrations/0004.pages.up.sql create mode 100644 backend/resources/migrations/0005.kvstore.up.sql create mode 100644 backend/resources/migrations/0006.emails.up.sql create mode 100644 backend/resources/migrations/0007.images.up.sql create mode 100644 backend/resources/migrations/0008.icons.up.sql create mode 100644 backend/resources/migrations/XXXX.workers.up.sql create mode 100644 backend/resources/public/static/images/email/facebook.png create mode 100644 backend/resources/public/static/images/email/img-header.jpg create mode 100644 backend/resources/public/static/images/email/linkedin.png create mode 100644 backend/resources/public/static/images/email/logo.png create mode 100644 backend/resources/public/static/images/email/twitter.png create mode 100644 backend/resources/sql/cli.sql create mode 100644 backend/resources/sql/emails.sql create mode 100644 backend/resources/sql/icons.sql create mode 100644 backend/resources/sql/images.sql create mode 100644 backend/resources/sql/kvstore.sql create mode 100644 backend/resources/sql/pages.sql create mode 100644 backend/resources/sql/projects.sql create mode 100644 backend/resources/sql/users.sql create mode 100644 backend/resources/sql/workers.sql create mode 100755 backend/scripts/fixtures.sh create mode 100755 backend/scripts/run.sh create mode 100644 backend/scripts/smtpd.sh create mode 100644 backend/src/data_readers.clj create mode 100644 backend/src/uxbox/cli/collimp.clj create mode 100644 backend/src/uxbox/cli/sql.clj create mode 100644 backend/src/uxbox/config.clj create mode 100644 backend/src/uxbox/db.clj create mode 100644 backend/src/uxbox/emails.clj create mode 100644 backend/src/uxbox/emails/core.clj create mode 100644 backend/src/uxbox/emails/layouts.clj create mode 100644 backend/src/uxbox/emails/users.clj create mode 100644 backend/src/uxbox/fixtures.clj create mode 100644 backend/src/uxbox/frontend.clj create mode 100644 backend/src/uxbox/frontend/auth.clj create mode 100644 backend/src/uxbox/frontend/debug_emails.clj create mode 100644 backend/src/uxbox/frontend/errors.clj create mode 100644 backend/src/uxbox/frontend/icons.clj create mode 100644 backend/src/uxbox/frontend/images.clj create mode 100644 backend/src/uxbox/frontend/kvstore.clj create mode 100644 backend/src/uxbox/frontend/pages.clj create mode 100644 backend/src/uxbox/frontend/projects.clj create mode 100644 backend/src/uxbox/frontend/svgparse.clj create mode 100644 backend/src/uxbox/frontend/users.clj create mode 100644 backend/src/uxbox/images.clj create mode 100644 backend/src/uxbox/locks.clj create mode 100644 backend/src/uxbox/main.clj create mode 100644 backend/src/uxbox/media.clj create mode 100644 backend/src/uxbox/migrations.clj create mode 100644 backend/src/uxbox/portation.clj create mode 100644 backend/src/uxbox/scheduled_jobs.clj create mode 100644 backend/src/uxbox/scheduled_jobs/emails.clj create mode 100644 backend/src/uxbox/scheduled_jobs/garbage.clj create mode 100644 backend/src/uxbox/services.clj create mode 100644 backend/src/uxbox/services/auth.clj create mode 100644 backend/src/uxbox/services/core.clj create mode 100644 backend/src/uxbox/services/icons.clj create mode 100644 backend/src/uxbox/services/images.clj create mode 100644 backend/src/uxbox/services/kvstore.clj create mode 100644 backend/src/uxbox/services/pages.clj create mode 100644 backend/src/uxbox/services/projects.clj create mode 100644 backend/src/uxbox/services/svgparse.clj create mode 100644 backend/src/uxbox/services/users.clj create mode 100644 backend/src/uxbox/sql.clj create mode 100644 backend/src/uxbox/util/blob.clj create mode 100644 backend/src/uxbox/util/cli.clj create mode 100644 backend/src/uxbox/util/closeable.clj create mode 100644 backend/src/uxbox/util/data.clj create mode 100644 backend/src/uxbox/util/exceptions.clj create mode 100644 backend/src/uxbox/util/images.clj create mode 100644 backend/src/uxbox/util/quartz.clj create mode 100644 backend/src/uxbox/util/response.clj create mode 100644 backend/src/uxbox/util/snappy.clj create mode 100644 backend/src/uxbox/util/spec.clj create mode 100644 backend/src/uxbox/util/tempfile.clj create mode 100644 backend/src/uxbox/util/template.clj create mode 100644 backend/src/uxbox/util/time.clj create mode 100644 backend/src/uxbox/util/token.clj create mode 100644 backend/src/uxbox/util/transit.clj create mode 100644 backend/src/uxbox/util/uuid.clj create mode 100644 backend/src/uxbox/util/workers.clj create mode 100644 backend/test/storages/tests.clj create mode 100644 backend/test/uxbox/tests/_files/sample.jpg create mode 100644 backend/test/uxbox/tests/_files/sample1.svg create mode 100644 backend/test/uxbox/tests/_files/sample2.svg create mode 100644 backend/test/uxbox/tests/helpers.clj create mode 100644 backend/test/uxbox/tests/test_auth.clj create mode 100644 backend/test/uxbox/tests/test_icons.clj create mode 100644 backend/test/uxbox/tests/test_images.clj create mode 100644 backend/test/uxbox/tests/test_kvstore.clj create mode 100644 backend/test/uxbox/tests/test_pages.clj create mode 100644 backend/test/uxbox/tests/test_projects.clj create mode 100644 backend/test/uxbox/tests/test_svgparse.clj create mode 100644 backend/test/uxbox/tests/test_txlog.clj create mode 100644 backend/test/uxbox/tests/test_users.clj create mode 100644 backend/vendor/executors/core.clj create mode 100644 backend/vendor/storages/core.clj create mode 100644 backend/vendor/storages/fs/local.clj create mode 100644 backend/vendor/storages/fs/misc.clj create mode 100644 backend/vendor/storages/impl.clj create mode 100644 backend/vendor/storages/proto.clj create mode 100644 backend/vendor/storages/util.clj diff --git a/backend/profiles.clj b/backend/profiles.clj new file mode 100644 index 000000000..2662e7bde --- /dev/null +++ b/backend/profiles.clj @@ -0,0 +1,8 @@ +{:dev + {:plugins [[lein-ancient "0.6.10"]] + :dependencies [[clj-http "2.1.0"]] + :main ^:skip-aot uxbox.main} + + :prod + {:jvm-opts ^:replace ["-Xms4g" "-Xmx4g" "-XX:+UseG1GC" + "-XX:+AggressiveOpts" "-server"]}} diff --git a/backend/project.clj b/backend/project.clj new file mode 100644 index 000000000..e4deec835 --- /dev/null +++ b/backend/project.clj @@ -0,0 +1,48 @@ +(defproject uxbox-backend "0.1.0-SNAPSHOT" + :description "UXBox backend." + :url "http://uxbox.github.io" + :license {:name "MPL 2.0" :url "https://www.mozilla.org/en-US/MPL/2.0/"} + :source-paths ["src" "vendor"] + :javac-options ["-target" "1.8" "-source" "1.8" "-Xlint:-options"] + :jvm-opts ["-Dclojure.compiler.direct-linking=true" + ;; "-Dcom.sun.management.jmxremote.port=9090" + ;; "-Dcom.sun.management.jmxremote.authenticate=false" + ;; "-Dcom.sun.management.jmxremote.ssl=false" + ;; "-Dcom.sun.management.jmxremote.rmi.port=9090" + ;; "-Djava.rmi.server.hostname=0.0.0.0" + "-Dclojure.spec.check-asserts=true" + "-Dclojure.spec.compile-asserts=true" + "-XX:+UseG1GC" "-Xms1g" "-Xmx1g"] + :global-vars {*assert* true} + :dependencies [[org.clojure/clojure "1.9.0-alpha14"] + [org.clojure/tools.logging "0.3.1"] + [funcool/struct "1.0.0"] + [funcool/suricatta "1.2.0"] + [funcool/promesa "1.6.0"] + [funcool/catacumba "2.0.0-SNAPSHOT"] + + [org.clojure/data.xml "0.1.0-beta2"] + [org.jsoup/jsoup "1.10.1"] + + [hiccup "1.0.5"] + [org.im4java/im4java "1.4.0"] + + [org.slf4j/slf4j-simple "1.7.21"] + [com.layerware/hugsql-core "0.4.7" + :exclusions [org.clojure/tools.reader]] + [niwinz/migrante "0.1.0"] + + [buddy/buddy-sign "1.3.0" :exclusions [org.clojure/tools.reader]] + [buddy/buddy-hashers "1.1.0"] + + [org.xerial.snappy/snappy-java "1.1.2.6"] + [com.github.spullara.mustache.java/compiler "0.9.4"] + [org.postgresql/postgresql "9.4.1212"] + [org.quartz-scheduler/quartz "2.2.3"] + [org.quartz-scheduler/quartz-jobs "2.2.3"] + [commons-io/commons-io "2.5"] + [com.draines/postal "2.0.2"] + + [hikari-cp "1.7.5"] + [mount "0.1.10"] + [environ "1.1.0"]]) diff --git a/backend/resources/.catacumba.basedir b/backend/resources/.catacumba.basedir new file mode 100644 index 000000000..e69de29bb diff --git a/backend/resources/builtin.edn b/backend/resources/builtin.edn new file mode 100644 index 000000000..7e3216732 --- /dev/null +++ b/backend/resources/builtin.edn @@ -0,0 +1,11 @@ +{:icons + [{:name "Material Design (Action)" + :path "./material/action/svg/production" + :regex #"^.*_48px\.svg$"}] + + :images + [{:name "Generic Collection 1" + :path "./my-images/collection1/" + :regex #"^.*\.(png|jpg|webp)$"}]} + + diff --git a/backend/resources/config/default.edn b/backend/resources/config/default.edn new file mode 100644 index 000000000..d7cf66269 --- /dev/null +++ b/backend/resources/config/default.edn @@ -0,0 +1,37 @@ +{;; A secret key used for create tokens + ;; WARNING: this is a default secret key and + ;; it should be overwritten in production env. + :secret "5qjiAn-QUpawUNqGP10UZKklSqbLKcdGY3sJpq0UUACpVXGg2HOFJCBejDWVHskhRyp7iHb4rjOLXX2ZjF-5cw" + :smtp + {:host "localhost" ;; Hostname of the desired SMTP server. + :port 25 ;; Port of SMTP server. + :user nil ;; Username to authenticate with (if authenticating). + :pass nil ;; Password to authenticate with (if authenticating). + :ssl false ;; Enables SSL encryption if value is truthy. + :tls false ;; Enables TLS encryption if value is truthy. + :noop true} + + :auth-options {:alg :a256kw :enc :a128cbc-hs256} + + :email {:reply-to "no-reply@uxbox.io" + :from "no-reply@uxbox.io"} + + :http {:port 6060 + :max-body-size 52428800 + :debug true} + + :media + {:basedir "resources/public/media" + :baseuri "http://localhost:6060/media/"} + + :static + {:basedir "resources/public/static" + :baseuri "http://localhost:6060/static/"} + + :database + {:adapter "postgresql" + :username nil + :password nil + :database-name "uxbox" + :server-name "localhost" + :port-number 5432}} diff --git a/backend/resources/config/test.edn b/backend/resources/config/test.edn new file mode 100644 index 000000000..2ded91c1b --- /dev/null +++ b/backend/resources/config/test.edn @@ -0,0 +1,18 @@ +{:migrations + {:verbose false} + + :media + {:basedir "/tmp/uxbox/media" + :baseuri "http://localhost:6060/media/"} + + :static + {:basedir "/tmp/uxbox/static" + :baseuri "http://localhost:6060/static/"} + + :database + {:adapter "postgresql" + :username nil + :password nil + :database-name "test" + :server-name "localhost" + :port-number 5432}} diff --git a/backend/resources/emails/en/register.mustache b/backend/resources/emails/en/register.mustache new file mode 100644 index 000000000..c23b78dbe --- /dev/null +++ b/backend/resources/emails/en/register.mustache @@ -0,0 +1,17 @@ +-- begin :subject +Welcome to UXBOX. +-- end + +-- begin :body-text +Hello {{user}}! + +Welcome to UXBOX. + +UXBOX team. +-- end + +-- begin :body-html +

Hello {{user}}!

+

Welcome to UXBOX.

+

UXBOX team.

+-- end \ No newline at end of file diff --git a/backend/resources/migrations/0000.main.up.sql b/backend/resources/migrations/0000.main.up.sql new file mode 100644 index 000000000..d4f5e4c0f --- /dev/null +++ b/backend/resources/migrations/0000.main.up.sql @@ -0,0 +1,28 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +-- OCC + +CREATE OR REPLACE FUNCTION handle_occ() + RETURNS TRIGGER AS $occ$ + BEGIN + IF (NEW.version != OLD.version) THEN + RAISE EXCEPTION 'Version missmatch: expected % given %', + OLD.version, NEW.version + USING ERRCODE='P0002'; + ELSE + NEW.version := NEW.version + 1; + END IF; + RETURN NEW; + END; +$occ$ LANGUAGE plpgsql; + +-- Modified At + +CREATE OR REPLACE FUNCTION update_modified_at() + RETURNS TRIGGER AS $updt$ + BEGIN + NEW.modified_at := clock_timestamp(); + RETURN NEW; + END; +$updt$ LANGUAGE plpgsql; diff --git a/backend/resources/migrations/0001.txlog.up.sql b/backend/resources/migrations/0001.txlog.up.sql new file mode 100644 index 000000000..a2777ccda --- /dev/null +++ b/backend/resources/migrations/0001.txlog.up.sql @@ -0,0 +1,17 @@ +-- A table that will store the whole transaction log of the database. +CREATE TABLE IF NOT EXISTS txlog ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + payload bytea NOT NULL +); + +CREATE OR REPLACE FUNCTION handle_txlog_notify() + RETURNS TRIGGER AS $notify$ + BEGIN + PERFORM pg_notify('uxbox.transaction', (NEW.id)::text); + RETURN NEW; + END; +$notify$ LANGUAGE plpgsql; + +CREATE TRIGGER txlog_notify_tgr AFTER INSERT ON txlog + FOR EACH ROW EXECUTE PROCEDURE handle_txlog_notify(); diff --git a/backend/resources/migrations/0002.auth.up.sql b/backend/resources/migrations/0002.auth.up.sql new file mode 100644 index 000000000..eb722f845 --- /dev/null +++ b/backend/resources/migrations/0002.auth.up.sql @@ -0,0 +1,49 @@ +CREATE TABLE users ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + modified_at timestamptz NOT NULL DEFAULT clock_timestamp(), + deleted_at timestamptz DEFAULT NULL, + + fullname text NOT NULL DEFAULT '', + username text NOT NULL, + email text NOT NULL, + photo text NOT NULL, + password text NOT NULL, + metadata bytea NOT NULL +); + +-- Insert a placeholder system user. +INSERT INTO users (id, fullname, username, email, photo, password, metadata) +VALUES ('00000000-0000-0000-0000-000000000000'::uuid, + 'System User', + '00000000-0000-0000-0000-000000000000', + 'system@uxbox.io', + '', + '!', + ''::bytea); + +CREATE UNIQUE INDEX users_username_idx + ON users USING btree (username); + +CREATE UNIQUE INDEX users_email_idx + ON users USING btree (email); + +CREATE TRIGGER users_modified_at_tgr BEFORE UPDATE ON users + FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); + +CREATE TABLE user_pswd_recovery ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "user" uuid REFERENCES users(id) ON DELETE CASCADE, + token text NOT NULL, + + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + used_at timestamptz DEFAULT NULL +); + +CREATE INDEX user_pswd_recovery_user_idx + ON user_pswd_recovery USING btree ("user"); + +CREATE UNIQUE INDEX user_pswd_recovery_token_idx + ON user_pswd_recovery USING btree (token); + diff --git a/backend/resources/migrations/0003.projects.up.sql b/backend/resources/migrations/0003.projects.up.sql new file mode 100644 index 000000000..1b3233599 --- /dev/null +++ b/backend/resources/migrations/0003.projects.up.sql @@ -0,0 +1,62 @@ +-- Table + +CREATE TABLE IF NOT EXISTS projects ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "user" 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(), + deleted_at timestamptz DEFAULT NULL, + + version bigint NOT NULL DEFAULT 0, + name text NOT NULL +); + +CREATE TABLE IF NOT EXISTS project_shares ( + project uuid PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE, + + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + modified_at timestamptz NOT NULL DEFAULT clock_timestamp(), + token text +); + +-- Indexes + +CREATE INDEX projects_user_idx + ON projects("user"); + +CREATE UNIQUE INDEX projects_shares_token_idx + ON project_shares(token); + +-- Triggers + +CREATE OR REPLACE FUNCTION handle_project_create() + RETURNS TRIGGER AS $$ + DECLARE + token text; + BEGIN + SELECT encode(digest(gen_random_bytes(128), 'sha256'), 'hex') + INTO token; + + INSERT INTO project_shares (project, token) + VALUES (NEW.id, token); + + RETURN NEW; + END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER project_on_create_tgr + AFTER INSERT ON projects + FOR EACH ROW EXECUTE PROCEDURE handle_project_create(); + +CREATE TRIGGER project_occ_tgr +BEFORE UPDATE ON projects + FOR EACH ROW EXECUTE PROCEDURE handle_occ(); + +CREATE TRIGGER projects_modified_at_tgr +BEFORE UPDATE ON projects + FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); + +CREATE TRIGGER project_shares_modified_at_tgr +BEFORE UPDATE ON project_shares + FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); diff --git a/backend/resources/migrations/0004.pages.up.sql b/backend/resources/migrations/0004.pages.up.sql new file mode 100644 index 000000000..3a2ca69c1 --- /dev/null +++ b/backend/resources/migrations/0004.pages.up.sql @@ -0,0 +1,74 @@ +-- Tables + +CREATE TABLE IF NOT EXISTS pages ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + + "user" uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, + project 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 DEFAULT 0, + + 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" uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, + page uuid NOT NULL REFERENCES pages(id) ON DELETE CASCADE, + + created_at timestamptz NOT NULL, + modified_at timestamptz NOT NULL, + version bigint NOT NULL DEFAULT 0, + + pinned bool NOT NULL DEFAULT false, + label text NOT NULL DEFAULT '', + data bytea NOT NULL +); + +-- Indexes + +CREATE INDEX pages_project_idx ON pages(project); +CREATE INDEX pages_user_idx ON pages("user"); +CREATE INDEX pages_history_page_idx ON pages_history(page); +CREATE INDEX pages_history_user_idx ON pages_history("user"); + +-- 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; + + --- Register a new history entry if the data + --- property is changed. + IF (OLD.data != NEW.data) THEN + INSERT INTO pages_history (page, "user", created_at, + modified_at, data, version) + VALUES (OLD.id, OLD."user", OLD.modified_at, + OLD.modified_at, OLD.data, OLD.version); + END IF; + + 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 page_occ_tgr BEFORE UPDATE ON pages + FOR EACH ROW EXECUTE PROCEDURE handle_occ(); + +CREATE TRIGGER pages_modified_at_tgr BEFORE UPDATE ON pages + FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); + +CREATE TRIGGER pages_history_modified_at_tgr BEFORE UPDATE ON pages + FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); diff --git a/backend/resources/migrations/0005.kvstore.up.sql b/backend/resources/migrations/0005.kvstore.up.sql new file mode 100644 index 000000000..0b540c1d0 --- /dev/null +++ b/backend/resources/migrations/0005.kvstore.up.sql @@ -0,0 +1,19 @@ +CREATE TABLE IF NOT EXISTS kvstore ( + "user" 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(), + + version bigint NOT NULL DEFAULT 0, + + key text NOT NULL, + value bytea NOT NULL, + + PRIMARY KEY (key, "user") +); + +CREATE TRIGGER kvstore_occ_tgr BEFORE UPDATE ON kvstore + FOR EACH ROW EXECUTE PROCEDURE handle_occ(); + +CREATE TRIGGER kvstore_modified_at_tgr BEFORE UPDATE ON kvstore + FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); diff --git a/backend/resources/migrations/0006.emails.up.sql b/backend/resources/migrations/0006.emails.up.sql new file mode 100644 index 000000000..b621501b7 --- /dev/null +++ b/backend/resources/migrations/0006.emails.up.sql @@ -0,0 +1,27 @@ +CREATE TYPE email_status AS ENUM ('pending', 'ok', 'failed'); + +CREATE TABLE IF NOT EXISTS email_queue ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + modified_at timestamptz NOT NULL DEFAULT clock_timestamp(), + deleted_at timestamptz DEFAULT NULL, + + data bytea NOT NULL, + + priority smallint NOT NULL DEFAULT 10 + CHECK (priority BETWEEN 0 and 10), + + status email_status NOT NULL DEFAULT 'pending', + retries integer NOT NULL DEFAULT -1 +); + +-- Triggers + +CREATE TRIGGER email_queue_modified_at_tgr BEFORE UPDATE ON email_queue + FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); + +-- Indexes + +CREATE INDEX email_status_idx + ON email_queue (status); diff --git a/backend/resources/migrations/0007.images.up.sql b/backend/resources/migrations/0007.images.up.sql new file mode 100644 index 000000000..4abb6a86b --- /dev/null +++ b/backend/resources/migrations/0007.images.up.sql @@ -0,0 +1,59 @@ +-- Tables + +CREATE TABLE IF NOT EXISTS images_collections ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "user" 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(), + deleted_at timestamptz DEFAULT NULL, + version bigint NOT NULL DEFAULT 0, + + name text NOT NULL +); + +CREATE TABLE IF NOT EXISTS images ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "user" 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(), + deleted_at timestamptz DEFAULT NULL, + + version bigint NOT NULL DEFAULT 0, + + width int NOT NULL, + height int NOT NULL, + mimetype text NOT NULL, + collection uuid REFERENCES images_collections(id) + ON DELETE SET NULL + DEFAULT NULL, + name text NOT NULL, + path text NOT NULL +); + +-- Indexes + +CREATE INDEX images_collections_user_idx + ON images_collections ("user"); + +CREATE INDEX images_collection_idx + ON images (collection); + +CREATE INDEX images_user_idx + ON images ("user"); + +-- Triggers + +CREATE TRIGGER images_collections_occ_tgr BEFORE UPDATE ON images_collections + FOR EACH ROW EXECUTE PROCEDURE handle_occ(); + +CREATE TRIGGER images_collections_modified_at_tgr BEFORE UPDATE ON images_collections + FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); + +CREATE TRIGGER images_occ_tgr BEFORE UPDATE ON images + FOR EACH ROW EXECUTE PROCEDURE handle_occ(); + +CREATE TRIGGER images_modified_at_tgr BEFORE UPDATE ON images + FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); + diff --git a/backend/resources/migrations/0008.icons.up.sql b/backend/resources/migrations/0008.icons.up.sql new file mode 100644 index 000000000..aee7ad025 --- /dev/null +++ b/backend/resources/migrations/0008.icons.up.sql @@ -0,0 +1,56 @@ +-- Tables + +CREATE TABLE IF NOT EXISTS icons_collections ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "user" 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(), + deleted_at timestamptz DEFAULT NULL, + version bigint NOT NULL DEFAULT 0, + + name text NOT NULL +); + +CREATE TABLE IF NOT EXISTS icons ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + "user" 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(), + deleted_at timestamptz DEFAULT NULL, + version bigint NOT NULL DEFAULT 0, + + name text NOT NULL, + content text NOT NULL, + metadata bytea NOT NULL, + collection uuid REFERENCES icons_collections(id) + ON DELETE SET NULL + DEFAULT NULL +); + +-- Indexes + +CREATE INDEX icon_colections_user_idx + ON icons_collections ("user"); + +CREATE INDEX icons_user_idx + ON icons ("user"); + +CREATE INDEX icons_collection_idx + ON icons (collection); + +-- Triggers + +CREATE TRIGGER icons_collections_occ_tgr BEFORE UPDATE ON icons_collections + FOR EACH ROW EXECUTE PROCEDURE handle_occ(); + +CREATE TRIGGER icons_collections_modified_at_tgr BEFORE UPDATE ON icons_collections + FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); + +CREATE TRIGGER icons_occ_tgr BEFORE UPDATE ON icons + FOR EACH ROW EXECUTE PROCEDURE handle_occ(); + +CREATE TRIGGER icons_modified_at_tgr BEFORE UPDATE ON icons + FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); + diff --git a/backend/resources/migrations/XXXX.workers.up.sql b/backend/resources/migrations/XXXX.workers.up.sql new file mode 100644 index 000000000..31237318c --- /dev/null +++ b/backend/resources/migrations/XXXX.workers.up.sql @@ -0,0 +1,11 @@ +CREATE TYPE task_status + AS ENUM ('pending', 'canceled', 'completed', 'failed'); + +CREATE TABLE task ( + id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + completed_at timestamptz DEFAULT NULL, + queue text NOT NULL DEFAULT '', + status task_status NOT NULL DEFAULT 'pending', + error text NOT NULL DEFAULT '' +) WITH (OIDS=FALSE); diff --git a/backend/resources/public/static/images/email/facebook.png b/backend/resources/public/static/images/email/facebook.png new file mode 100644 index 0000000000000000000000000000000000000000..bb0fe05297bf3377518c83db00aebbdc721bcdc7 GIT binary patch literal 2356 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 PFSJ|YIAR>iJJ0{wH*B?S literal 0 HcmV?d00001 diff --git a/backend/resources/public/static/images/email/linkedin.png b/backend/resources/public/static/images/email/linkedin.png new file mode 100644 index 0000000000000000000000000000000000000000..79aa66bc74a3d6d144181b808b23790aea13179d GIT binary patch literal 750 zcmVlCA|o@BpfU zRk{0v_1ecm7={n!y*~zgVem88NF`0v6VA*oF?@0W1?SwHt0u&-GVS=6t;5Rr2-!-> zTS*_y>?AOmlTrXIUA zrE9@7O_yrdW+q^%O?c0c_uewZzmxRA%uWGk@{+12Yk{6Hvx!_xT9744213^}7=~eE zl2BV7W_GJwE-%;j#c{kW=_c^g%&r!T#n(xaB$7^>*=68dPX+&z&qlIl*x zHq30Jp>GP9YIvmExIKb@fXo%XPwY5gd(akb*m9EI4BDaLuq)|G5Co5)?vx*(dR{#bOatTeFS~buEi+p; gvy~`{HftS!0Z{$Sn>qve{{R3007*qoM6N<$f``~uKmY&$ literal 0 HcmV?d00001 diff --git a/backend/resources/public/static/images/email/logo.png b/backend/resources/public/static/images/email/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b4917b065d597ea5aa43b491fa98a92969664265 GIT binary patch literal 6696 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 (locked_tasks.created_at, locked_tasks.id) + order by created_at, id + limit 1 + ) as j + from locked_tasks + where locked_tasks.id is not null + limit 1 + ) as t1 + ) +) +select id, status, error, created_at + from locked_tasks + where locked + limit 1; + +-- :name create-task :? :1 +insert into tasks (queue) +values (:queue) +returning *; + +-- :name mark-task-done +update tasks + set status = 'completed', + completed_at = clock_timestamp() + where id = :id; + +-- :name mark-task-failed +update tasks + set status = 'failed', + error = :error, + completed_at = clock_timestamp() + where id = :id; diff --git a/backend/scripts/fixtures.sh b/backend/scripts/fixtures.sh new file mode 100755 index 000000000..f06e934ca --- /dev/null +++ b/backend/scripts/fixtures.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +lein run -m uxbox.fixtures/init diff --git a/backend/scripts/run.sh b/backend/scripts/run.sh new file mode 100755 index 000000000..f3401bf97 --- /dev/null +++ b/backend/scripts/run.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +export PATH=/opt/img/bin:$PATH +lein trampoline run -m uxbox.main diff --git a/backend/scripts/smtpd.sh b/backend/scripts/smtpd.sh new file mode 100644 index 000000000..a4aa39cc6 --- /dev/null +++ b/backend/scripts/smtpd.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +python -m smtpd -n -c DebuggingServer localhost:25 diff --git a/backend/src/data_readers.clj b/backend/src/data_readers.clj new file mode 100644 index 000000000..bd70c26c8 --- /dev/null +++ b/backend/src/data_readers.clj @@ -0,0 +1 @@ +{instant uxbox.util.time/from-string} diff --git a/backend/src/uxbox/cli/collimp.clj b/backend/src/uxbox/cli/collimp.clj new file mode 100644 index 000000000..e19d02705 --- /dev/null +++ b/backend/src/uxbox/cli/collimp.clj @@ -0,0 +1,172 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.cli.collimp + "Collection importer command line helper." + (:require [clojure.spec :as s] + [clojure.pprint :refer [pprint]] + [clojure.java.io :as io] + [mount.core :as mount] + [cuerdas.core :as str] + [suricatta.core :as sc] + [storages.core :as st] + [storages.util :as fs] + [uxbox.config] + [uxbox.db :as db] + [uxbox.migrations] + [uxbox.media :as media] + [uxbox.cli.sql :as sql] + [uxbox.util.spec :as us] + [uxbox.util.cli :as cli] + [uxbox.util.uuid :as uuid] + [uxbox.util.data :as data]) + (:import [java.io Reader PushbackReader] + [javax.imageio ImageIO])) + +;; --- Constants & Specs + +(def ^:const +imates-uuid-ns+ #uuid "3642a582-565f-4070-beba-af797ab27a6e") + +(s/def ::name string?) +(s/def ::type keyword?) +(s/def ::path string?) +(s/def ::regex us/regex?) + +(s/def ::import-entry + (s/keys :req-un [::name ::type ::path ::regex])) + +;; --- CLI Helpers + + +(defn printerr + [& args] + (binding [*out* *err*] + (apply println args))) + +(defn pushback-reader + [reader] + (PushbackReader. ^Reader reader)) + +;; --- Colors Collections Importer + +(def storage media/images-storage) + +(defn- create-image-collection + "Create or replace image collection by its name." + [conn {:keys [name] :as entry}] + (let [id (uuid/namespaced +imates-uuid-ns+ name) + sqlv (sql/create-image-collection {:id id :name name})] + (sc/execute conn sqlv) + id)) + +(defn- retrieve-image-size + [path] + (let [path (fs/path path) + file (.toFile path) + buff (ImageIO/read file)] + [(.getWidth buff) + (.getHeight buff)])) + +(defn- retrieve-image + [conn id] + {:pre [(uuid? id)]} + (let [sqlv (sql/get-image {:id id})] + (some->> (sc/fetch-one conn sqlv) + (data/normalize-attrs)))) + +(defn- delete-image + [conn {:keys [id path] :as image}] + {:pre [(uuid? id) + (fs/path? path)]} + (let [sqlv (sql/delete-image {:id id})] + @(st/delete storage path) + (sc/execute conn sqlv))) + +(defn- create-image + [conn collid imageid localpath] + {:pre [(fs/path? localpath) + (uuid? collid) + (uuid? imageid)]} + (let [filename (fs/base-name localpath) + [width height] (retrieve-image-size localpath) + extension (second (fs/split-ext filename)) + path @(st/save storage filename localpath) + params {:name filename + :path (str path) + :mimetype (case extension + ".jpg" "image/jpeg" + ".png" "image/png") + :width width + :height height + :collection collid + :id imageid} + sqlv (sql/create-image params)] + (sc/execute conn sqlv))) + +(defn- import-image + [conn id fpath] + {:pre [(uuid? id) (fs/path? fpath)]} + #_(let [imageid (uuid/namespaced +imates-uuid-ns+ (str id fpath))] + (if-let [image (retrieve-image conn imageid)] + (do + (delete-image conn image) + (create-image conn id imageid fpath)) + (create-image conn id imageid fpath)))) + +(defn- process-images-entry + [conn {:keys [path regex] :as entry}] + {:pre [(s/valid? ::import-entry entry)]} + (let [id (uuid/random) #_(create-image-collection conn entry)] + (doseq [fpath (fs/list-files path)] + (when (re-matches regex (str fpath)) + (import-image conn id fpath))))) + +;; --- Entry Point + +(defn- check-path! + [path] + (when-not path + (cli/print-err! "No path is provided.") + (cli/exit! -1)) + (when-not (fs/exists? path) + (cli/print-err! "Path does not exists.") + (cli/exit! -1)) + (when (fs/directory? path) + (cli/print-err! "The provided path is a directory.") + (cli/exit! -1)) + (fs/path path)) + +(defn- read-import-file + [path] + (let [path (check-path! path) + parent (fs/parent path) + reader (pushback-reader (io/reader path))] + [parent (read reader)])) + +(defn- start-system + [] + (-> (mount/only #{#'uxbox.config/config + #'uxbox.db/datasource + #'uxbox.migrations/migrations}) + (mount/start))) + +(defn- stop-system + [] + (mount/stop)) + +(defn- run-importer + [directory data] + (println "Running importer on:") + (pprint data)) + +(defn -main + [& [path]] + (let [[directory data] (read-import-file path)] + (start-system) + (try + (run-importer directory data) + (finally + (stop-system))))) diff --git a/backend/src/uxbox/cli/sql.clj b/backend/src/uxbox/cli/sql.clj new file mode 100644 index 000000000..77890198f --- /dev/null +++ b/backend/src/uxbox/cli/sql.clj @@ -0,0 +1,4 @@ +(ns uxbox.cli.sql + (:require [hugsql.core :as hugsql])) + +(hugsql/def-sqlvec-fns "sql/cli.sql" {:quoting :ansi :fn-suffix ""}) diff --git a/backend/src/uxbox/config.clj b/backend/src/uxbox/config.clj new file mode 100644 index 000000000..8b6ce12bc --- /dev/null +++ b/backend/src/uxbox/config.clj @@ -0,0 +1,52 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.config + "A configuration management." + (:require [mount.core :refer [defstate]] + [environ.core :refer (env)] + [buddy.core.hash :as hash] + [clojure.java.io :as io] + [clojure.edn :as edn] + [uxbox.util.exceptions :as ex] + [uxbox.util.data :refer [deep-merge]])) + +;; --- Configuration Loading & Parsing + +(def ^:dynamic *default-config-path* "config/default.edn") +(def ^:dynamic *local-config-path* "config/local.edn") + +(defn read-config + [] + (let [builtin (io/resource *default-config-path*) + local (io/resource *local-config-path*) + external (io/file (:uxbox-config env))] + (deep-merge (edn/read-string (slurp builtin)) + (when local (edn/read-string (slurp local))) + (when (and external (.exists external)) + (edn/read-string (slurp external)))))) + +(defn read-test-config + [] + (binding [*local-config-path* "config/test.edn"] + (read-config))) + +(defstate config + :start (read-config)) + +;; --- Secret Loading & Parsing + +(defn- initialize-secret + [config] + (let [secret (:secret config)] + (when-not secret + (ex/raise :code ::missing-secret-key + :message "Missing `:secret` key in config.")) + (hash/blake2b-256 secret))) + +(defstate secret + :start (initialize-secret config)) + diff --git a/backend/src/uxbox/db.clj b/backend/src/uxbox/db.clj new file mode 100644 index 000000000..9d60a3889 --- /dev/null +++ b/backend/src/uxbox/db.clj @@ -0,0 +1,89 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.db + "Database access layer for UXBOX." + (:require [mount.core :as mount :refer (defstate)] + [promesa.core :as p] + [hikari-cp.core :as hikari] + [executors.core :as exec] + [suricatta.core :as sc] + [suricatta.proto :as scp] + [suricatta.types :as sct] + [suricatta.transaction :as sctx] + [uxbox.config :as cfg]) + (:import org.jooq.TransactionContext + org.jooq.TransactionProvider + org.jooq.Configuration)) + +;; --- State + +(def ^:const +defaults+ + {:connection-timeout 30000 + :idle-timeout 600000 + :max-lifetime 1800000 + :minimum-idle 10 + :maximum-pool-size 10 + :adapter "postgresql" + :username "" + :password "" + :database-name "" + :server-name "localhost" + :port-number 5432}) + +(defn create-datasource + [config] + (let [dbconf (merge +defaults+ config)] + (hikari/make-datasource dbconf))) + +(defstate datasource + :start (create-datasource (:database cfg/config)) + :stop (hikari/close-datasource datasource)) + +;; --- Suricatta Async Adapter + +(defn transaction + "Asynchronous transaction handling." + {:internal true} + [ctx func] + (let [^Configuration conf (.derive (scp/-config ctx)) + ^TransactionContext txctx (sctx/transaction-context conf) + ^TransactionProvider provider (.transactionProvider conf)] + (doto conf + (.data "suricatta.rollback" false) + (.data "suricatta.transaction" true)) + (try + (.begin provider txctx) + (->> (func (sct/context conf)) + (p/map (fn [result] + (if (.data conf "suricatta.rollback") + (.rollback provider txctx) + (.commit provider txctx)) + result)) + (p/error (fn [error] + (.rollback provider (.cause txctx error)) + (p/rejected error)))) + (catch Exception cause + (.rollback provider (.cause txctx cause)) + (p/rejected cause))))) + +;; --- Public Api + +(defmacro atomic + [ctx & body] + `(transaction ~ctx (fn [~ctx] ~@body))) + +(defn connection + [] + (sc/context datasource)) + +(defn fetch + [& args] + (exec/submit #(apply sc/fetch args))) + +(defn execute + [& args] + (exec/submit #(apply sc/execute args))) diff --git a/backend/src/uxbox/emails.clj b/backend/src/uxbox/emails.clj new file mode 100644 index 000000000..d4d89f7bd --- /dev/null +++ b/backend/src/uxbox/emails.clj @@ -0,0 +1,14 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.emails + "Main api for send emails." + (:require [uxbox.emails.core :as core])) + +(def send! core/send!) +(def render core/render) + +(load "emails/users") diff --git a/backend/src/uxbox/emails/core.clj b/backend/src/uxbox/emails/core.clj new file mode 100644 index 000000000..454bae340 --- /dev/null +++ b/backend/src/uxbox/emails/core.clj @@ -0,0 +1,89 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.emails.core + (:require [hiccup.core :refer (html)] + [hiccup.page :refer (html4)] + [suricatta.core :as sc] + [uxbox.db :as db] + [uxbox.config :as cfg] + [uxbox.sql :as sql] + [uxbox.emails.layouts :as layouts] + [uxbox.util.blob :as blob] + [uxbox.util.transit :as t])) + +(def emails + "A global state for registring emails." + (atom {})) + +(defmacro defemail + [type & args] + (let [email (apply hash-map args)] + `(do + (swap! emails assoc ~type ~email) + nil))) + +(defn- render-subject + [{:keys [subject]} context] + (cond + (delay? subject) (deref subject) + (ifn? subject) (subject context) + (string? subject) subject + :else (throw (ex-info "Invalid subject." {})))) + +(defn- render-body + [[type bodyfn] layout context] + (let [layoutfn (get layout type)] + {:content (cond-> (bodyfn context) + layoutfn (layoutfn context) + (= type :text/html) (html4)) + ::type type + :type (subs (str type) 1)})) + +(defn- render-body-alternatives + [{:keys [layout body] :as email} context] + (reduce #(conj %1 (render-body %2 layout context)) [:alternatives] body)) + +(defn render-email + [email context] + (let [config (:email cfg/config) + from (or (:email/from context) + (:from config)) + reply-to (or (:email/reply-to context) + (:reply-to config) + from)] + {:subject (render-subject email context) + :body (render-body-alternatives email context) + :to (:email/to context) + :from from + :reply-to reply-to})) + +(def valid-priority? #{:high :low}) +(def valid-email-identifier? #(contains? @emails %)) + +(defn render + "Render a email as data structure." + [{name :email/name :as context}] + {:pre [(valid-email-identifier? name)]} + (let [email (get @emails name)] + (render-email email context))) + +(defn send! + "Schedule the email for sending." + [{name :email/name + priority :email/priority + :or {priority :high} + :as context}] + {:pre [(valid-priority? priority) + (valid-email-identifier? name)]} + (let [email (get @emails name) + email (render-email email context) + data (-> email t/encode blob/encode) + priority (case priority :low 1 :high 10) + sqlv (sql/insert-email {:data data :priority priority})] + (with-open [conn (db/connection)] + (sc/atomic conn + (sc/execute conn sqlv))))) diff --git a/backend/src/uxbox/emails/layouts.clj b/backend/src/uxbox/emails/layouts.clj new file mode 100644 index 000000000..9f26be117 --- /dev/null +++ b/backend/src/uxbox/emails/layouts.clj @@ -0,0 +1,228 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.emails.layouts + (:require [uxbox.media :as md])) + +(def default-embedded-styles + "/* GLOBAL */ + * { + margin:0; + padding:0; + font-family: Arial, sans-serif; + font-size: 100%; + line-height: 1.6; + } + + img { + max-width: 100%; + width: 100%; + } + + .img-header { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + } + + body { + -webkit-font-smoothing:antialiased; + -webkit-text-size-adjust:none; + width: 100%!important; + height: 100%; + } + + /* ELEMENTS */ + a { + color: #78dbbe; + text-decoration:none; + font-weight: bold; + } + + .btn-primary { + text-decoration:none; + color: #fff; + background-color: #78dbbe; + padding: 10px 30px; + font-weight: bold; + margin: 20px 0; + text-align: center; + cursor: pointer; + display: inline-block; + border-radius: 4px; + } + + .btn-primary:hover { + color: #FFF; + background-color: #8eefcf; + } + + .last { + margin-bottom: 0; + } + + .first{ + margin-top: 0; + } + + .logo { + background-color: #f6f6f6; + padding: 10px; + text-align: center; + padding-bottom: 25px; + } + .logo h2 { + color: #777; + font-size: 20px; + font-weight: bold; + margin-top: 15px; + } + .logo img { + max-width: 150px; + } + + /* BODY */ + table.body-wrap { + width: 100%; + padding: 20px; + } + + table.body-wrap .container{ + border-radius: 5px; + color: #ababab; + } + + + /* FOOTER */ + table.footer-wrap { + width: 100%; + clear:both!important; + } + + .footer-wrap .container p { + font-size: 12px; + color:#666; + + } + + table.footer-wrap a{ + color: #999; + } + + + /* TYPOGRAPHY */ + h1,h2,h3{ + font-family: Arial, sans-serif; + line-height: 1.1; + margin-bottom:15px; + color:#000; + margin: 40px 0 10px; + line-height: 1.2; + font-weight:200; + } + + h1 { + color: #777; + font-size: 28px; + font-weight: bold; + } + h2 { + font-size: 24px; + } + h3 { + font-size: 18px; + } + + p, ul { + margin-bottom: 10px; + font-weight: normal; + } + + ul li { + margin-left:5px; + list-style-position: inside; + } + + /* RESPONSIVE */ + + /* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink down on a phone or something */ + .container { + display: block !important; + max-width: 620px !important; + margin: 0 auto !important; /* makes it centered */ + clear: both !important; + } + + /* This should also be a block element, so that it will fill 100% of the .container */ + .content { + padding: 20px; + max-width: 620px; + margin: 0 auto; + display: block; + } + + /* Let's make sure tables in the content area are 100% wide */ + .content table { + width: 100%; + }") + +(defn- default-html + [body context] + [:html + [:head + [:meta {:http-equiv "Content-Type" + :content "text/html; charset=UTF-8"}] + [:meta {:name "viewport" + :content "width=device-width"}] + [:title "title"] + [:style default-embedded-styles]] + [:body {:bgcolor "#f6f6f6" + :cz-shortcut-listen "true"} + [:table.body-wrap + [:tbody + [:tr + [:td] + [:td.container {:bgcolor "#FFFFFF"} + [:div.logo + [:img {:src (md/resolve-asset "images/email/logo.png") + :alt "UXBOX"}]] + body] + [:td]]]] + [:table.footer-wrap + [:tbody + [:tr + [:td] + [:td.container + [:div.content + [:table + [:tbody + [:tr + [:td + [:div {:style "text-align: center;"} + [:a {:href "#" :target "_blank"} + [:img {:style "display: inline-block; width: 25px; margin-right: 5px;" + :src (md/resolve-asset "images/email/twitter.png")}]] + [:a {:href "#" :target "_blank"} + [:img {:style "display: inline-block; width: 25px; margin-right: 5px;" + :src (md/resolve-asset "images/email/facebook.png")}]] + [:a {:href "#" :target "_blank"} + [:img {:style "display: inline-block; width: 25px; margin-right: 5px;" + :src (md/resolve-asset "images/email/linkedin.png")}]]]]] + [:tr + [:td {:align "center"} + [:p + [:span "Sent from UXBOX | "] + [:a {:href "#" :target "_blank"} + [:unsubscribe "Email preferences"]]]]]]]]] + [:td]]]]]]) + +(defn default-text + [body context] + body) + +(def default + "Default layout instance." + {:text/html default-html + :text/plain default-text}) diff --git a/backend/src/uxbox/emails/users.clj b/backend/src/uxbox/emails/users.clj new file mode 100644 index 000000000..d6f1f0cf7 --- /dev/null +++ b/backend/src/uxbox/emails/users.clj @@ -0,0 +1,77 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.emails.users + (:require [uxbox.media :as md] + [uxbox.emails.core :refer (defemail)] + [uxbox.emails.layouts :as layouts])) + +;; --- User Register + +(defn- register-body-html + [{:keys [name] :as ctx}] + [:div + [:img.img-header {:src (md/resolve-asset "images/email/img-header.jpg") + :alt "UXBOX"}] + [:div.content + [:table + [:tbody + [:tr + [:td + [:h1 "Hi " name] + [:p "Welcome to uxbox."] + [:p + [:a.btn-primary {:href "#"} "Sign in"]] + [:p "Sincerely," [:br] [:strong "The UXBOX Team"]] + #_[:p "P.S. Having trouble signing up? please contact " + [:a {:href "#"} "Support email"]]]]]]]]) + +(defn- register-body-text + [{:keys [name] :as ctx}] + (str "Hi " name "\n\n" + "Welcome to uxbox!\n\n" + "Sincerely, the UXBOX team.\n")) + +(defemail :users/register + :layout layouts/default + :subject "UXBOX: Welcome!" + :body {:text/html register-body-html + :text/plain register-body-text}) + +;; --- Password Recovery + +(defn- password-recovery-body-html + [{:keys [name token] :as ctx}] + [:div + [:img.img-header {:src (md/resolve-asset "images/img-header.jpg") + :alt "UXBOX"}] + [:div.content + [:table + [:tbody + [:tr + [:td + [:h1 "Hi " name] + [:p "A password recovery is requested."] + [:p + "Please, follow the following url in order to" + "change your password." + [:a {:href "#"} "http://uxbox.io/..."]] + [:p "Sincerely," [:br] [:strong "The UXBOX Team"]]]]]]]]) + +(defn- password-recovery-body-text + [{:keys [name token] :as ctx}] + (str "Hi " name "\n\n" + "A password recovery is requested.\n\n" + "Please follow the following url in order to change the password:\n\n" + " http://uxbox.io/recovery/" token "\n\n\n" + "Sincerely, the UXBOX team.\n")) + +(defemail :users/password-recovery + :layout layouts/default + :subject "Password recovery requested." + :body {:text/html password-recovery-body-html + :text/plain password-recovery-body-text}) + diff --git a/backend/src/uxbox/fixtures.clj b/backend/src/uxbox/fixtures.clj new file mode 100644 index 000000000..d75f33d52 --- /dev/null +++ b/backend/src/uxbox/fixtures.clj @@ -0,0 +1,86 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.fixtures + "A initial fixtures." + (:require [buddy.hashers :as hashers] + [buddy.core.codecs :as codecs] + [catacumba.serializers :as sz] + [mount.core :as mount] + [clj-uuid :as uuid] + [suricatta.core :as sc] + [uxbox.config :as cfg] + [uxbox.db :as db] + [uxbox.migrations] + [uxbox.util.transit :as t] + [uxbox.services.users :as susers] + [uxbox.services.projects :as sproj] + [uxbox.services.pages :as spag])) + +(defn- mk-uuid + [prefix i] + (uuid/v5 uuid/+namespace-oid+ (str prefix i))) + +(defn- data-encode + [data] + (-> (t/encode data) + (codecs/bytes->str))) + +(defn- create-user + [conn i] + (println "create user" i) + (susers/create-user conn + {:username (str "user" i) + :id (mk-uuid "user" i) + :fullname (str "User " i) + :metadata (data-encode {}) + :password "123123" + :email (str "user" i ".test@uxbox.io")})) + +(defn- create-project + [conn i ui] + ;; (Thread/sleep 20) + (println "create project" i "for user" ui) + (sproj/create-project conn + {:id (mk-uuid "project" i) + :user (mk-uuid "user" ui) + :name (str "project " i)})) + +(defn- create-page + [conn i pi ui] + ;; (Thread/sleep 1) + (println "create page" i "for user" ui "for project" pi) + (spag/create-page conn + {:id (mk-uuid "page" i) + :user (mk-uuid "user" ui) + :project (mk-uuid "project" pi) + :data nil + :metadata {:width 1024 + :height 768 + :layout "tablet"} + :name (str "page " i)})) + +(def num-users 50) +(def num-projects 5) +(def num-pages 5) + +(defn init + [] + (mount/start) + (with-open [conn (db/connection)] + (sc/atomic conn + (doseq [i (range num-users)] + (create-user conn i)) + + (doseq [ui (range num-users)] + (doseq [i (range num-projects)] + (create-project conn (str ui i) ui))) + + (doseq [pi (range num-projects)] + (doseq [ui (range num-users)] + (doseq [i (range num-pages)] + (create-page conn (str pi ui i) (str ui pi) ui)))))) + (mount/stop)) diff --git a/backend/src/uxbox/frontend.clj b/backend/src/uxbox/frontend.clj new file mode 100644 index 000000000..385ace05d --- /dev/null +++ b/backend/src/uxbox/frontend.clj @@ -0,0 +1,155 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.frontend + (:require [mount.core :refer [defstate]] + [catacumba.core :as ct] + [catacumba.http :as http] + [catacumba.serializers :as sz] + [catacumba.handlers.auth :as cauth] + [catacumba.handlers.parse :as cparse] + [catacumba.handlers.misc :as cmisc] + [uxbox.config :as cfg] + [uxbox.frontend.auth :as auth] + [uxbox.frontend.users :as users] + [uxbox.frontend.errors :as errors] + [uxbox.frontend.projects :as projects] + [uxbox.frontend.pages :as pages] + [uxbox.frontend.images :as images] + [uxbox.frontend.icons :as icons] + [uxbox.frontend.kvstore :as kvstore] + [uxbox.frontend.svgparse :as svgparse] + [uxbox.frontend.debug-emails :as dbgemails] + [uxbox.util.response :refer [rsp]] + [uxbox.util.uuid :as uuid])) + +;; --- Top Level Handlers + +(defn- welcome-api + "A GET entry point for the api that shows + a welcome message." + [context] + (let [body {:message "Welcome to UXBox api."}] + (-> (sz/encode body :json) + (http/ok {:content-type "application/json"})))) + +(defn- debug-only + [context] + (if (-> cfg/config :server :debug) + (ct/delegate) + (http/not-found ""))) + +;; --- Config + +(def cors-conf + {:origin "*" + :max-age 3600 + :allow-methods #{:post :put :get :delete :trace} + :allow-headers #{:x-requested-with :content-type :authorization}}) + +;; --- Routes + +(defn routes + ([] (routes cfg/config)) + ([config] + (let [auth-opts {:secret cfg/secret + :options (:auth-options cfg/config)}] + (ct/routes + [[:any (cauth/auth (cauth/jwe-backend auth-opts))] + [:any (cmisc/autoreloader)] + + [:get "api" #'welcome-api] + [:assets "media" {:dir "public/media"}] + [:assets "static" {:dir "public/static"}] + + [:prefix "debug" + [:any debug-only] + [:get "emails" #'dbgemails/list-emails] + [:get "emails/email" #'dbgemails/show-email]] + + [:prefix "api" + [:any (cmisc/cors cors-conf)] + [:any (cparse/body-params)] + [:error #'errors/handler] + + [:post "auth/token" #'auth/login] + [:post "auth/register" #'users/register-user] + [:get "auth/recovery/:token" #'users/validate-recovery-token] + [:post "auth/recovery" #'users/request-recovery] + [:put "auth/recovery" #'users/recover-password] + + [:get "projects-by-token/:token" #'projects/retrieve-project-by-share-token] + + ;; SVG Parse + [:post "svg/parse" #'svgparse/parse] + + [:any #'auth/authorization] + + ;; KVStore + [:put "kvstore" #'kvstore/update] + [:get "kvstore/:key" #'kvstore/retrieve] + [:delete "kvstore/:key" #'kvstore/delete] + + ;; Projects + [:get "projects/:id/pages" #'pages/list-pages-by-project] + [:put "projects/:id" #'projects/update-project] + [:delete "projects/:id" #'projects/delete-project] + [:post "projects" #'projects/create-project] + [:get "projects" #'projects/list-projects] + + ;; Image Collections + [:put "library/image-collections/:id" #'images/update-collection] + [:delete "library/image-collections/:id" #'images/delete-collection] + [:get "library/image-collections" #'images/list-collections] + [:post "library/image-collections" #'images/create-collection] + [:get "library/image-collections/:id/images" #'images/list-images] + [:get "library/image-collections/images" #'images/list-images] + + ;; Images + [:put "library/images/copy" #'images/copy-image] + [:delete "library/images/:id" #'images/delete-image] + [:get "library/images/:id" #'images/retrieve-image] + [:put "library/images/:id" #'images/update-image] + [:post "library/images" #'images/create-image] + + ;; Icon Collections + [:put "library/icon-collections/:id" #'icons/update-collection] + [:delete "library/icon-collections/:id" #'icons/delete-collection] + [:get "library/icon-collections" #'icons/list-collections] + [:post "library/icon-collections" #'icons/create-collection] + [:get "library/icon-collections/:id/icons" #'icons/list-icons] + [:get "library/icon-collections/icons" #'icons/list-icons] + + ;; Icons + [:put "library/icons/copy" #'icons/copy-icon] + [:delete "library/icons/:id" #'icons/delete-icon] + [:put "library/icons/:id" #'icons/update-icon] + [:post "library/icons" #'icons/create-icon] + + ;; Pages + [:put "pages/:id/metadata" #'pages/update-page-metadata] + [:get "pages/:id/history" #'pages/retrieve-page-history] + [:put "pages/:id/history/:hid" #'pages/update-page-history] + [:put "pages/:id" #'pages/update-page] + [:delete "pages/:id" #'pages/delete-page] + [:post "pages" #'pages/create-page] + + ;; Profile + [:get "profile/me" #'users/retrieve-profile] + [:put "profile/me" #'users/update-profile] + [:put "profile/me/password" #'users/update-password] + [:post "profile/me/photo" #'users/update-photo]]])))) + +;; --- State Initialization + +(defn- start-server + [config] + (let [config (:http config)] + (ct/run-server (routes config) config))) + +(defstate server + :start (start-server cfg/config) + :stop (.stop server)) diff --git a/backend/src/uxbox/frontend/auth.clj b/backend/src/uxbox/frontend/auth.clj new file mode 100644 index 000000000..724f6d994 --- /dev/null +++ b/backend/src/uxbox/frontend/auth.clj @@ -0,0 +1,33 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.frontend.auth + (:require [clojure.spec :as s] + [catacumba.core :as ct] + [catacumba.http :as http] + [promesa.core :as p] + [uxbox.util.spec :as us] + [uxbox.services :as sv] + [uxbox.util.uuid :as uuid] + [uxbox.util.response :refer (rsp)])) + +(s/def ::scope string?) +(s/def ::login (s/keys :req-un [::us/username ::us/password ::scope])) + +(defn login + [{data :data}] + (let [data (us/conform ::login data) + message (assoc data :type :login)] + (->> (sv/novelty message) + (p/map #(http/ok (rsp %)))))) + +;; TODO: improve authorization + +(defn authorization + [{:keys [identity] :as context}] + (if identity + (ct/delegate {:identity (uuid/from-string (:id identity))}) + (http/forbidden (rsp nil)))) diff --git a/backend/src/uxbox/frontend/debug_emails.clj b/backend/src/uxbox/frontend/debug_emails.clj new file mode 100644 index 000000000..606177889 --- /dev/null +++ b/backend/src/uxbox/frontend/debug_emails.clj @@ -0,0 +1,68 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.frontend.debug-emails + "A helper namespace for just render emails." + (:require [clojure.edn :as edn] + [catacumba.http :as http] + [hiccup.page :refer (html5)] + [uxbox.emails :as emails] + [uxbox.emails.core :as emails-core])) + +(def +available-emails+ + {:users/register + {:name "Cirilla"} + :users/password-recovery + {:name "Cirilla" + :token "agNFhA6SolcFb4Us2NOTNWh0cfFDquVLAav400xQPjw"}}) + +(defn- render-emails-list + [] + (html5 + [:section {:style "font-family: Monoid, monospace; font-size: 14px;"} + [:h1 "Available emails"] + [:table {:style "width: 500px;"} + [:tbody + [:tr + (for [[type email] @emails-core/emails] + [:tr + [:td (pr-str type)] + [:td + [:a {:href (str "/debug/emails/email?id=" + (pr-str type) + "&type=:text/html")} + "(html)"]] + [:td + [:a {:href (str "/debug/emails/email?id=" + (pr-str type) + "&type=:text/plain")} + "(text)"]]])]]]])) + +(defn list-emails + [context] + (http/ok (render-emails-list) + {:content-type "text/html; charset=utf-8"})) + +(defn- render-email + [type content] + (if (= type :text/html) + content + (html5 + [:pre content]))) + +(defn show-email + [{params :query-params}] + (let [id (edn/read-string (:id params)) + type (or (edn/read-string (:type params)) :text/html) + params (-> (get +available-emails+ id) + (assoc :email/name id)) + email (emails/render params) + content (->> (:body email) + (filter #(= (:uxbox.emails.core/type %) type)) + (first) + (:content))] + (-> (render-email type content) + (http/ok {:content-type "text/html; charset=utf-8"})))) diff --git a/backend/src/uxbox/frontend/errors.clj b/backend/src/uxbox/frontend/errors.clj new file mode 100644 index 000000000..b7ffe1143 --- /dev/null +++ b/backend/src/uxbox/frontend/errors.clj @@ -0,0 +1,74 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.frontend.errors + "A errors handling for frontend api." + (:require [catacumba.core :as ct] + [catacumba.http :as http] + [uxbox.util.response :refer (rsp)])) + +(defmulti handle-exception #(:type (ex-data %))) + +(defmethod handle-exception :validation + [err] + (println "\n*********** stack trace ***********") + (.printStackTrace err) + (println "\n********* end stack trace *********") + (let [response (ex-data err)] + (http/bad-request (rsp response)))) + +(defmethod handle-exception :default + [err] + (println "\n*********** stack trace ***********") + (.printStackTrace err) + (println "\n********* end stack trace *********") + (let [response (ex-data err)] + (http/internal-server-error (rsp response)))) + +;; --- Entry Point + +(defn- handle-data-access-exception + [err] + (let [err (.getCause err) + state (.getSQLState err) + message (.getMessage err)] + (case state + "P0002" + (-> (rsp {:message message + :payload nil + :type :occ}) + (http/precondition-failed)) + + (do + (.printStackTrace err) + (-> (rsp {:message message + :type :unexpected + :payload nil}) + (http/internal-server-error)))))) + +(defn- handle-unexpected-exception + [err] + (.printStackTrace err) + (let [message (.getMessage err)] + (-> (rsp {:message message + :type :unexpected + :payload nil}) + (http/internal-server-error)))) + +(defn handler + [context err] + (cond + (instance? clojure.lang.ExceptionInfo err) + (handle-exception err) + + (instance? java.util.concurrent.CompletionException err) + (handler context (.getCause err)) + + (instance? org.jooq.exception.DataAccessException err) + (handle-data-access-exception err) + + :else + (handle-unexpected-exception err))) diff --git a/backend/src/uxbox/frontend/icons.clj b/backend/src/uxbox/frontend/icons.clj new file mode 100644 index 000000000..c69c41ef4 --- /dev/null +++ b/backend/src/uxbox/frontend/icons.clj @@ -0,0 +1,156 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.frontend.icons + (:require [clojure.spec :as s] + [promesa.core :as p] + [catacumba.http :as http] + [storages.core :as st] + [uxbox.util.spec :as us] + [uxbox.services :as sv] + [uxbox.util.response :refer (rsp)] + [uxbox.util.uuid :as uuid])) + +;; --- Constants & Config + +(s/def ::collection (s/nilable ::us/uuid-string)) + +(s/def ::width (s/and number? pos?)) +(s/def ::height (s/and number? pos?)) +(s/def ::view-box (s/and (s/coll-of number?) + #(= 4 (count %)) + vector?)) + +(s/def ::mimetype string?) +(s/def ::metadata + (s/keys :opt-un [::width ::height ::view-box ::mimetype])) + +(s/def ::content string?) + +;; --- Create Collection + +(s/def ::create-collection + (s/keys :req-un [::us/name] :opt-un [::us/id])) + +(defn create-collection + [{user :identity data :data}] + (let [data (us/conform ::create-collection data) + message (assoc data + :type :create-icon-collection + :user user)] + (->> (sv/novelty message) + (p/map (fn [result] + (let [loc (str "/api/library/icons/" (:id result))] + (http/created loc (rsp result)))))))) + +;; --- Update Collection + +(s/def ::update-collection + (s/merge ::create-collection (s/keys :req-un [::us/version]))) + +(defn update-collection + [{user :identity params :route-params data :data}] + (let [data (us/conform ::update-collection data) + message (assoc data + :id (uuid/from-string (:id params)) + :type :update-icon-collection + :user user)] + (-> (sv/novelty message) + (p/then #(http/ok (rsp %)))))) + +;; --- Delete Collection + +(defn delete-collection + [{user :identity params :route-params}] + (let [message {:id (uuid/from-string (:id params)) + :type :delete-icon-collection + :user user}] + (-> (sv/novelty message) + (p/then (fn [v] (http/no-content)))))) + +;; --- List collections + +(defn list-collections + [{user :identity}] + (let [params {:user user + :type :list-icon-collections}] + (-> (sv/query params) + (p/then #(http/ok (rsp %)))))) + +;; --- Create Icon + +(s/def ::create-icon + (s/keys :req-un [::metadata ::us/name ::metadata ::content] + :opt-un [::us/id ::collection])) + +(defn create-icon + [{user :identity data :data :as request}] + (let [{:keys [id name content metadata collection]} (us/conform ::create-icon data) + id (or id (uuid/random))] + (->> (sv/novelty {:id id + :type :create-icon + :user user + :name name + :collection collection + :metadata metadata + :content content}) + (p/map (fn [entry] + (let [loc (str "/api/library/icons/" (:id entry))] + (http/created loc (rsp entry)))))))) + +;; --- Update Icon + +(s/def ::update-icon + (s/keys :req-un [::us/name ::us/version ::collection] :opt-un [::us/id])) + +(defn update-icon + [{user :identity params :route-params data :data}] + (let [data (us/conform ::update-icon data) + message (assoc data + :id (uuid/from-string (:id params)) + :type :update-icon + :user user)] + (->> (sv/novelty message) + (p/map #(http/ok (rsp %)))))) + +;; --- Copy Icon + +(s/def ::copy-icon + (s/keys :req-un [:us/id ::collection])) + +(defn copy-icon + [{user :identity data :data}] + (let [data (us/conform ::copy-icon data) + message (assoc data + :user user + :type :copy-icon)] + (->> (sv/novelty message) + (p/map #(http/ok (rsp %)))))) + +;; --- Delete Icon + +(defn delete-icon + [{user :identity params :route-params}] + (let [message {:id (uuid/from-string (:id params)) + :type :delete-icon + :user user}] + (->> (sv/novelty message) + (p/map (fn [v] (http/no-content)))))) + +;; --- List collections + +(s/def ::list-icons + (s/keys :opt-un [::us/id])) + +(defn list-icons + [{user :identity route-params :route-params}] + (let [{:keys [id]} (us/conform ::list-icons route-params) + params {:collection id + :type :list-icons + :user user}] + (->> (sv/query params) + (p/map rsp) + (p/map http/ok)))) diff --git a/backend/src/uxbox/frontend/images.clj b/backend/src/uxbox/frontend/images.clj new file mode 100644 index 000000000..95708d7e9 --- /dev/null +++ b/backend/src/uxbox/frontend/images.clj @@ -0,0 +1,196 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.frontend.images + (:require [clojure.spec :as s] + [promesa.core :as p] + [catacumba.http :as http] + [storages.core :as st] + [storages.util :as path] + [uxbox.media :as media] + [uxbox.images :as images] + [uxbox.util.spec :as us] + [uxbox.services :as sv] + [uxbox.util.response :refer (rsp)] + [uxbox.util.uuid :as uuid])) + +;; --- Constants & Config + +(s/def ::file ::us/uploaded-file) +(s/def ::width ::us/integer-string) +(s/def ::height ::us/integer-string) +(s/def ::collection (s/nilable ::us/uuid-string)) +(s/def ::mimetype string?) + +(def +thumbnail-options+ {:src :path + :dst :thumbnail + :size [300 110] + :quality 92 + :format "webp"}) + +(def populate-thumbnails + #(images/populate-thumbnails % +thumbnail-options+)) + +(def populate-urls + #(images/populate-urls % media/images-storage :path :url)) + +;; --- Create Collection + +(s/def ::create-collection + (s/keys :req-un [::us/name] :opt-un [::us/id])) + +(defn create-collection + [{user :identity data :data}] + (let [data (us/conform ::create-collection data) + message (assoc data + :type :create-image-collection + :user user)] + (->> (sv/novelty message) + (p/map (fn [result] + (let [loc (str "/api/library/images/" (:id result))] + (http/created loc (rsp result)))))))) + +;; --- Update Collection + +(s/def ::update-collection + (s/merge ::create-collection (s/keys :req-un [::us/version]))) + +(defn update-collection + [{user :identity params :route-params data :data}] + (let [data (us/conform ::update-collection data) + message (assoc data + :id (uuid/from-string (:id params)) + :type :update-image-collection + :user user)] + (-> (sv/novelty message) + (p/then #(http/ok (rsp %)))))) + +;; --- Delete Collection + +(defn delete-collection + [{user :identity params :route-params}] + (let [message {:id (uuid/from-string (:id params)) + :type :delete-image-collection + :user user}] + (-> (sv/novelty message) + (p/then (fn [v] (http/no-content)))))) + +;; --- List collections + +(defn list-collections + [{user :identity}] + (let [params {:user user + :type :list-image-collections}] + (-> (sv/query params) + (p/then #(http/ok (rsp %)))))) + +;; --- Retrieve Image + +(s/def ::retrieve-image + (s/keys :req-un [::us/id])) + +(defn retrieve-image + [{user :identity params :route-params}] + (let [params (us/conform ::retrieve-image params) + params (assoc params :user user :type :retrieve-image)] + (->> (sv/query params) + (p/map (fn [result] + (if result + (-> (populate-thumbnails result) + (populate-urls) + (rsp) + (http/ok)) + (http/not-found ""))))))) + +;; --- Create Image + +(s/def ::create-image + (s/keys :req-un [::file ::width ::height ::mimetype] + :opt-un [::us/id ::collection])) + +(defn create-image + [{user :identity data :data}] + (let [{:keys [file id width height + mimetype collection]} (us/conform ::create-image data) + id (or id (uuid/random)) + filename (path/base-name file) + storage media/images-storage] + (letfn [(persist-image-entry [path] + (sv/novelty {:id id + :type :create-image + :user user + :width width + :height height + :mimetype mimetype + :collection collection + :name filename + :path (str path)})) + (create-response [entry] + (let [loc (str "/api/library/images/" (:id entry))] + (http/created loc (rsp entry))))] + (->> (st/save storage filename file) + (p/mapcat persist-image-entry) + (p/map populate-thumbnails) + (p/map populate-urls) + (p/map create-response))))) + +;; --- Update Image + +(s/def ::update-image + (s/keys :req-un [::us/name ::us/version ::collection] :opt-un [::us/id])) + +(defn update-image + [{user :identity params :route-params data :data}] + (let [data (us/conform ::update-image data) + message (assoc data + :id (uuid/from-string (:id params)) + :type :update-image + :user user)] + (->> (sv/novelty message) + (p/map populate-thumbnails) + (p/map populate-urls) + (p/map #(http/ok (rsp %)))))) + +;; --- Copy Image + +(s/def ::copy-image + (s/keys :req-un [::us/id ::collection])) + +(defn copy-image + [{user :identity data :data}] + (let [data (us/conform ::copy-image data) + params (assoc data :user user :type :copy-image)] + (->> (sv/novelty params) + (p/map populate-thumbnails) + (p/map populate-urls) + (p/map #(http/ok (rsp %)))))) + +;; --- Delete Image + +(defn delete-image + [{user :identity params :route-params}] + (let [message {:id (uuid/from-string (:id params)) + :type :delete-image + :user user}] + (->> (sv/novelty message) + (p/map (fn [v] (http/no-content)))))) + +;; --- List collections + +(s/def ::list-images + (s/keys :opt-un [::us/id])) + +(defn list-images + [{user :identity route-params :route-params}] + (let [{:keys [id]} (us/conform ::list-images route-params) + params {:collection id + :type :list-images + :user user}] + (->> (sv/query params) + (p/map (partial map populate-thumbnails)) + (p/map (partial map populate-urls)) + (p/map rsp) + (p/map http/ok)))) diff --git a/backend/src/uxbox/frontend/kvstore.clj b/backend/src/uxbox/frontend/kvstore.clj new file mode 100644 index 000000000..968453f4f --- /dev/null +++ b/backend/src/uxbox/frontend/kvstore.clj @@ -0,0 +1,60 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.frontend.kvstore + (:refer-clojure :exclude [update]) + (:require [clojure.spec :as s] + [promesa.core :as p] + [catacumba.http :as http] + [uxbox.media :as media] + [uxbox.util.spec :as us] + [uxbox.services :as sv] + [uxbox.util.response :refer (rsp)] + [uxbox.util.uuid :as uuid])) + +(s/def ::version integer?) +(s/def ::key string?) +(s/def ::value any?) + +;; --- Retrieve + +(s/def ::retrieve (s/keys :req-un [::key])) + +(defn retrieve + [{user :identity params :route-params}] + (let [data (us/conform ::retrieve params) + params (assoc data + :type :retrieve-kvstore + :user user)] + (->> (sv/query params) + (p/map #(http/ok (rsp %)))))) + +;; --- Update (or Create) + +(s/def ::update (s/keys :req-un [::key ::value] + :opt-un [::version])) + +(defn update + [{user :identity data :data}] + (let [data (us/conform ::update data) + params (assoc data + :type :update-kvstore + :user user)] + (->> (sv/novelty params) + (p/map #(http/ok (rsp %)))))) + +;; --- Delete + +(s/def ::delete (s/keys :req-un [::key])) + +(defn delete + [{user :identity params :route-params}] + (let [data (us/conform ::delete params) + params (assoc data + :type :delete-kvstore + :user user)] + (->> (sv/novelty params) + (p/map (fn [_] (http/no-content)))))) diff --git a/backend/src/uxbox/frontend/pages.clj b/backend/src/uxbox/frontend/pages.clj new file mode 100644 index 000000000..d6cfb39e9 --- /dev/null +++ b/backend/src/uxbox/frontend/pages.clj @@ -0,0 +1,119 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.frontend.pages + (:require [clojure.spec :as s] + [promesa.core :as p] + [catacumba.http :as http] + [uxbox.util.spec :as us] + [uxbox.services :as sv] + [uxbox.util.response :refer (rsp)] + [uxbox.util.uuid :as uuid])) + +;; --- List Pages + +(defn list-pages-by-project + [{user :identity params :route-params}] + (let [params {:user user + :project (uuid/from-string (:id params)) + :type :list-pages-by-project}] + (-> (sv/query params) + (p/then #(http/ok (rsp %)))))) + +;; --- Create Page + +(s/def ::data any?) +(s/def ::metadata any?) +(s/def ::project ::us/id) +(s/def ::create-page + (s/keys :req-un [::data ::metadata ::project ::us/name] + :opt-un [::us/id])) + +(defn create-page + [{user :identity data :data}] + (let [data (us/conform ::create-page data) + message (assoc data + :type :create-page + :user user)] + (->> (sv/novelty message) + (p/map (fn [result] + (let [loc (str "/api/pages/" (:id result))] + (http/created loc (rsp result)))))))) + +;; --- Update Page + +(s/def ::update-page + (s/merge ::create-page (s/keys :req-un [::us/version]))) + +(defn update-page + [{user :identity params :route-params data :data}] + (let [data (us/conform ::update-page data) + message (assoc data + :id (uuid/from-string (:id params)) + :type :update-page + :user user)] + (->> (sv/novelty message) + (p/map #(http/ok (rsp %)))))) + +;; --- Update Page Metadata + +(s/def ::update-page-metadata + (s/keys :req-un [::us/id ::metadata ::project ::us/name])) + +(defn update-page-metadata + [{user :identity params :route-params data :data}] + (let [data (us/conform ::update-page-metadata data) + message (assoc data + :id (uuid/from-string (:id params)) + :type :update-page-metadata + :user user)] + (->> (sv/novelty message) + (p/map #(http/ok (rsp %)))))) + +;; --- Delete Page + +(defn delete-page + [{user :identity params :route-params}] + (let [message {:id (uuid/from-string (:id params)) + :type :delete-page + :user user}] + (-> (sv/novelty message) + (p/then (fn [v] (http/no-content)))))) + +;; --- Retrieve Page History + +(s/def ::max (s/and ::us/integer-string ::us/positive-integer)) +(s/def ::since ::us/integer-string) +(s/def ::pinned ::us/boolean-string) + +(s/def ::retrieve-page-history + (s/keys :opt-un [::max ::since ::pinned])) + +(defn retrieve-page-history + [{user :identity params :route-params query :query-params}] + (let [query (us/conform ::retrieve-page-history query) + message (assoc query + :id (uuid/from-string (:id params)) + :type :list-page-history + :user user)] + (->> (sv/query message) + (p/map #(http/ok (rsp %)))))) + +;; --- Update Page History + +(s/def ::label string?) +(s/def ::update-page-history + (s/keys :req-un [::label ::pinned])) + +(defn update-page-history + [{user :identity params :route-params data :data}] + (let [data (us/conform ::update-page-history data) + message (assoc data + :type :update-page-history + :id (uuid/from-string (:hid params)) + :user user)] + (->> (sv/novelty message) + (p/map #(http/ok (rsp %)))))) diff --git a/backend/src/uxbox/frontend/projects.clj b/backend/src/uxbox/frontend/projects.clj new file mode 100644 index 000000000..645ca8c4d --- /dev/null +++ b/backend/src/uxbox/frontend/projects.clj @@ -0,0 +1,73 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.frontend.projects + (:require [clojure.spec :as s] + [promesa.core :as p] + [catacumba.http :as http] + [uxbox.util.spec :as us] + [uxbox.services :as sv] + [uxbox.util.response :refer (rsp)] + [uxbox.util.uuid :as uuid])) + +;; --- List Projects + +(defn list-projects + [{user :identity}] + (let [message {:user user :type :list-projects}] + (->> (sv/query message) + (p/map #(http/ok (rsp %)))))) + +;; --- Create Projects + +(s/def ::create-project + (s/keys :req-un [::us/name] :opt-un [::us/id])) + +(defn create-project + [{user :identity data :data}] + (let [data (us/conform ::create-project data) + message (assoc data + :type :create-project + :user user)] + (->> (sv/novelty message) + (p/map (fn [result] + (let [loc (str "/api/projects/" (:id result))] + (http/created loc (rsp result)))))))) + +;; --- Update Project + +(s/def ::update-project + (s/keys :req-un [::us/name ::us/version])) + +(defn update-project + [{user :identity params :route-params data :data}] + (let [data (us/conform ::update-project data) + message (assoc data + :id (uuid/from-string (:id params)) + :type :update-project + :user user)] + (-> (sv/novelty message) + (p/then #(http/ok (rsp %)))))) + +;; --- Delete Project + +(defn delete-project + [{user :identity params :route-params}] + (let [message {:id (uuid/from-string (:id params)) + :type :delete-project + :user user}] + (-> (sv/novelty message) + (p/then (fn [v] (http/no-content)))))) + + +;; --- Retrieve project + +(defn retrieve-project-by-share-token + [{params :route-params}] + (let [message {:token (:token params) + :type :retrieve-project-by-share-token}] + (->> (sv/query message) + (p/map #(http/ok (rsp %)))))) diff --git a/backend/src/uxbox/frontend/svgparse.clj b/backend/src/uxbox/frontend/svgparse.clj new file mode 100644 index 000000000..f8198f38c --- /dev/null +++ b/backend/src/uxbox/frontend/svgparse.clj @@ -0,0 +1,22 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.frontend.svgparse + "A frontend exposed endpoints for svgparse functionality." + (:require [clojure.spec :as s] + [promesa.core :as p] + [catacumba.http :as http] + [uxbox.util.spec :as us] + [uxbox.services :as sv] + [uxbox.util.response :refer (rsp)] + [uxbox.util.uuid :as uuid])) + +(defn parse + [{body :body :as context}] + (let [message {:data (slurp body) + :type :parse-svg}] + (->> (sv/query message) + (p/map #(http/ok (rsp %)))))) diff --git a/backend/src/uxbox/frontend/users.clj b/backend/src/uxbox/frontend/users.clj new file mode 100644 index 000000000..31467d1a0 --- /dev/null +++ b/backend/src/uxbox/frontend/users.clj @@ -0,0 +1,149 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.frontend.users + (:require [clojure.spec :as s] + [promesa.core :as p] + [catacumba.http :as http] + [storages.core :as st] + [storages.util :as path] + [uxbox.media :as media] + [uxbox.images :as images] + [uxbox.util.spec :as us] + [uxbox.services :as sv] + [uxbox.services.users :as svu] + [uxbox.util.response :refer (rsp)] + [uxbox.util.uuid :as uuid])) + +;; --- Helpers + +(defn- resolve-thumbnail + [user] + (let [opts {:src :photo + :dst :photo + :size [100 100] + :quality 90 + :format "jpg"}] + (images/populate-thumbnails user opts))) + +;; --- Retrieve Profile + +(defn retrieve-profile + [{user :identity}] + (let [message {:user user + :type :retrieve-profile}] + (->> (sv/query message) + (p/map resolve-thumbnail) + (p/map #(http/ok (rsp %)))))) + +;; --- Update Profile + +(s/def ::fullname string?) +(s/def ::metadata any?) +(s/def ::update-profile + (s/keys :req-un [::us/id ::us/username ::us/email + ::fullname ::metadata])) + +(defn update-profile + [{user :identity data :data}] + (let [data (us/conform ::update-profile data) + message (assoc data + :type :update-profile + :user user)] + (->> (sv/novelty message) + (p/map resolve-thumbnail) + (p/map #(http/ok (rsp %)))))) + +;; --- Update Password + +(s/def ::old-password ::us/password) +(s/def ::update-password + (s/keys :req-un [::us/password ::old-password])) + +(defn update-password + [{user :identity data :data}] + (let [data (us/conform ::update-password data) + message (assoc data + :type :update-profile-password + :user user)] + (-> (sv/novelty message) + (p/then #(http/ok (rsp %)))))) + +;; --- Update Profile Photo + +(s/def ::file ::us/uploaded-file) +(s/def ::update-photo (s/keys :req-un [::file])) + +(defn update-photo + [{user :identity data :data}] + (letfn [(store-photo [file] + (let [filename (path/base-name file) + storage media/images-storage] + (st/save storage filename file))) + (assign-photo [path] + (sv/novelty {:user user + :path (str path) + :type :update-profile-photo})) + (create-response [_] + (http/no-content))] + (let [{:keys [file]} (us/conform ::update-photo data)] + (->> (store-photo file) + (p/mapcat assign-photo) + (p/map create-response))))) + +;; --- Register User + +(s/def ::register + (s/keys :req-un [::us/username ::us/email ::us/password ::fullname])) + +(defn register-user + [{data :data}] + (let [data (us/conform ::register data) + message (assoc data :type :register-profile)] + (->> (sv/novelty message) + (p/map #(http/ok (rsp %)))))) + + +;; --- Request Password Recovery + +;; FIXME: rename for consistency + +(s/def ::request-recovery + (s/keys :req-un [::us/username])) + +(defn request-recovery + [{data :data}] + (let [data (us/conform ::request-recovery data) + message (assoc data :type :request-profile-password-recovery)] + (->> (sv/novelty message) + (p/map (fn [_] (http/no-content)))))) + +;; --- Password Recovery + +;; FIXME: rename for consistency + +(s/def ::token string?) +(s/def ::password-recovery + (s/keys :req-un [::token ::us/password])) + +(defn recover-password + [{data :data}] + (let [data (us/conform ::password-recovery data) + message (assoc data :type :recover-profile-password)] + (->> (sv/novelty message) + (p/map (fn [_] (http/no-content)))))) + +;; --- Valiadate Recovery Token + +(defn validate-recovery-token + [{params :route-params}] + (let [message {:type :validate-profile-password-recovery-token + :token (:token params)}] + (->> (sv/query message) + (p/map (fn [v] + (if v + (http/no-content) + (http/not-found ""))))))) diff --git a/backend/src/uxbox/images.clj b/backend/src/uxbox/images.clj new file mode 100644 index 000000000..af9faad68 --- /dev/null +++ b/backend/src/uxbox/images.clj @@ -0,0 +1,66 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.images + "Image postprocessing." + (:require [storages.core :as st] + [storages.util :as path] + [clojure.spec :as s] + [uxbox.util.spec :as us] + [uxbox.media :as media] + [uxbox.util.images :as images] + [uxbox.util.data :refer (dissoc-in)])) + +;; FIXME: add spec for thumbnail config + +(defn make-thumbnail + [path {:keys [size format quality] :as cfg}] + (let [parent (path/parent path) + [filename ext] (path/split-ext path) + + suffix-parts [(nth size 0) (nth size 1) quality format] + final-name (apply str filename "-" (interpose "." suffix-parts)) + final-path (path/path parent final-name) + + images-storage media/images-storage + thumbs-storage media/thumbnails-storage] + (if @(st/exists? thumbs-storage final-path) + (str (st/public-url thumbs-storage final-path)) + (if @(st/exists? images-storage path) + (let [datapath @(st/lookup images-storage path) + content (images/thumbnail datapath cfg) + path @(st/save thumbs-storage final-path content)] + (str (st/public-url thumbs-storage path))) + nil)))) + +(defn populate-thumbnail + [entry {:keys [src dst] :as cfg}] + (assert (map? entry) "`entry` should be map") + + (let [src (if (vector? src) src [src]) + dst (if (vector? dst) dst [dst]) + src (get-in entry src)] + (if (empty? src) + entry + (assoc-in entry dst (make-thumbnail src cfg))))) + +(defn populate-thumbnails + [entry & settings] + (reduce populate-thumbnail entry settings)) + +(defn populate-urls + [entry storage src dst] + (assert (map? entry) "`entry` should be map") + (assert (st/storage? storage) "`storage` should be a valid storage instance.") + (let [src (if (vector? src) src [src]) + dst (if (vector? dst) dst [dst]) + value (get-in entry src)] + (if (empty? value) + entry + (let [url (str (st/public-url storage value))] + (-> entry + (dissoc-in src) + (assoc-in dst url)))))) diff --git a/backend/src/uxbox/locks.clj b/backend/src/uxbox/locks.clj new file mode 100644 index 000000000..aaa881068 --- /dev/null +++ b/backend/src/uxbox/locks.clj @@ -0,0 +1,21 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.locks + "Advirsory locks for specific handling concurrent modifications + on particular objects in the database." + (:require [suricatta.core :as sc]) + (:import clojure.lang.Murmur3)) + +(defn- uuid->long + [v] + (Murmur3/hashUnencodedChars (str v))) + +(defn acquire! + [conn v] + (let [id (uuid->long v)] + (sc/execute conn ["select pg_advisory_xact_lock(?);" id]) + nil)) diff --git a/backend/src/uxbox/main.clj b/backend/src/uxbox/main.clj new file mode 100644 index 000000000..a5ae1346c --- /dev/null +++ b/backend/src/uxbox/main.clj @@ -0,0 +1,92 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.main + (:require [clojure.tools.namespace.repl :as repl] + [clojure.walk :refer [macroexpand-all]] + [clojure.pprint :refer [pprint]] + [clojure.test :as test] + [clojure.java.io :as io] + [mount.core :as mount] + [buddy.core.codecs :as codecs] + [buddy.core.codecs.base64 :as b64] + [buddy.core.nonce :as nonce] + [uxbox.config :as cfg] + [uxbox.migrations] + [uxbox.db] + [uxbox.frontend] + [uxbox.scheduled-jobs]) + (:gen-class)) + +;; --- Development Stuff + +(defn- start + [] + (mount/start)) + +(defn- start-minimal + [] + (-> (mount/only #{#'uxbox.config/config + #'uxbox.db/datasource + #'uxbox.migrations/migrations}) + (mount/start))) + +(defn- stop + [] + (mount/stop)) + +(defn- refresh + [] + (stop) + (repl/refresh)) + +(defn- refresh-all + [] + (stop) + (repl/refresh-all)) + +(defn- go + "starts all states defined by defstate" + [] + (start) + :ready) + +(defn- reset + [] + (stop) + (repl/refresh :after 'uxbox.main/start)) + +(defn make-secret + [] + (-> (nonce/random-bytes 64) + (b64/encode true) + (codecs/bytes->str))) + +;; --- Entry point (only for uberjar) + +(defn test-vars + [& vars] + (repl/refresh) + (test/test-vars + (map (fn [sym] + (require (symbol (namespace sym))) + (resolve sym)) + vars))) + +(defn test-ns + [ns] + (repl/refresh) + (test/test-ns ns)) + +(defn test-all + ([] (test/run-all-tests #"^uxbox.tests.*")) + ([re] (test/run-all-tests re))) + +;; --- Entry point (only for uberjar) + +(defn -main + [& args] + (mount/start)) diff --git a/backend/src/uxbox/media.clj b/backend/src/uxbox/media.clj new file mode 100644 index 000000000..1a88fac27 --- /dev/null +++ b/backend/src/uxbox/media.clj @@ -0,0 +1,39 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.media + "A media storage impl for uxbox." + (:require [mount.core :as mount :refer (defstate)] + [clojure.java.io :as io] + [cuerdas.core :as str] + [storages.core :as st] + [storages.fs.local :refer (filesystem)] + [storages.fs.misc :refer (hashed scoped)] + [uxbox.config :refer (config)])) + +;; --- State + +(defstate static-storage + :start (let [{:keys [basedir baseuri]} (:static config)] + (filesystem {:basedir basedir :baseuri baseuri}))) + +(defstate media-storage + :start (let [{:keys [basedir baseuri]} (:media config)] + (filesystem {:basedir basedir :baseuri baseuri}))) + +(defstate images-storage + :start (-> media-storage + (scoped "images") + (hashed))) + +(defstate thumbnails-storage + :start (scoped media-storage "thumbs")) + +;; --- Public Api + +(defn resolve-asset + [path] + (str (st/public-url static-storage path))) diff --git a/backend/src/uxbox/migrations.clj b/backend/src/uxbox/migrations.clj new file mode 100644 index 000000000..4036f9317 --- /dev/null +++ b/backend/src/uxbox/migrations.clj @@ -0,0 +1,78 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.migrations + (:require [mount.core :as mount :refer (defstate)] + [migrante.core :as mg :refer (defmigration)] + [uxbox.db :as db] + [uxbox.config :as cfg] + [uxbox.util.template :as tmpl])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Migrations +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmigration utils-0000 + "Create a initial version of txlog table." + :up (mg/resource "migrations/0000.main.up.sql")) + +(defmigration txlog-0001 + "Create a initial version of txlog table." + :up (mg/resource "migrations/0001.txlog.up.sql")) + +(defmigration auth-0002 + "Create initial auth related tables." + :up (mg/resource "migrations/0002.auth.up.sql")) + +(defmigration projects-0003 + "Create initial tables for projects." + :up (mg/resource "migrations/0003.projects.up.sql")) + +(defmigration pages-0004 + "Create initial tables for pages." + :up (mg/resource "migrations/0004.pages.up.sql")) + +(defmigration kvstore-0005 + "Create initial tables for kvstore." + :up (mg/resource "migrations/0005.kvstore.up.sql")) + +(defmigration emails-queue-0006 + "Create initial tables for emails queue." + :up (mg/resource "migrations/0006.emails.up.sql")) + +(defmigration images-0007 + "Create initial tables for image collections." + :up (mg/resource "migrations/0007.images.up.sql")) + +(defmigration icons-0008 + "Create initial tables for image collections." + :up (mg/resource "migrations/0008.icons.up.sql")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Entry point +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def +migrations+ + {:name :uxbox-main + :steps [[:0000 utils-0000] + [:0001 txlog-0001] + [:0002 auth-0002] + [:0003 projects-0003] + [:0004 pages-0004] + [:0005 kvstore-0005] + [:0006 emails-queue-0006] + [:0007 images-0007] + [:0008 icons-0008]]}) + +(defn- migrate + [] + (let [options (:migrations cfg/config {})] + (with-open [mctx (mg/context db/datasource options)] + (mg/migrate mctx +migrations+) + nil))) + +(defstate migrations + :start (migrate)) diff --git a/backend/src/uxbox/portation.clj b/backend/src/uxbox/portation.clj new file mode 100644 index 000000000..1cef68743 --- /dev/null +++ b/backend/src/uxbox/portation.clj @@ -0,0 +1,114 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.portation + "Support for export/import operations of projects." + (:refer-clojure :exclude [with-open]) + (:require [clojure.java.io :as io] + [suricatta.core :as sc] + [storages.util :as path] + [uxbox.db :as db] + [uxbox.sql :as sql] + [uxbox.util.uuid :as uuid] + [uxbox.util.closeable :refer (with-open)] + [uxbox.util.tempfile :as tmpfile] + [uxbox.util.transit :as t] + [uxbox.util.snappy :as snappy])) + +;; --- Export + +(defn- write-project + [conn writer id] + (let [sql (sql/get-project-by-id {:id id}) + result (sc/fetch-one conn sql)] + (when-not result + (ex-info "No project found with specified id" {:id id})) + (t/write! writer {::type ::project ::payload result}))) + +(defn- write-pages + [conn writer id] + (let [sql (sql/get-pages-for-project {:project id}) + results (sc/fetch conn sql)] + (run! #(t/write! writer {::type ::page ::payload %}) results))) + +(defn- write-pages-history + [conn writer id] + (let [sql (sql/get-page-history-for-project {:project id}) + results (sc/fetch conn sql)] + (run! #(t/write! writer {::type ::page-history ::payload %}) results))) + +(defn- write-data + [path id] + (with-open [ostream (io/output-stream path) + zstream (snappy/output-stream ostream) + conn (db/connection)] + (let [writer (t/writer zstream {:type :msgpack})] + (sc/atomic conn + (write-project conn writer id) + (write-pages conn writer id) + (write-pages-history conn writer id))))) + +(defn export + "Given an id, returns a path to a temporal file with the exported + bundle of the specified project." + [id] + (let [path (tmpfile/create)] + (write-data path id) + path)) + +;; --- Import + +(defn- read-entry + [reader] + (try + (t/read! reader) + (catch RuntimeException e + (let [cause (.getCause e)] + (if (instance? java.io.EOFException cause) + ::eof + (throw e)))))) + +(defn- persist-project + [conn project] + (let [sql (sql/create-project project)] + (sc/execute conn sql))) + +(defn- persist-page + [conn page] + (let [sql (sql/create-page page)] + (sc/execute conn sql))) + +(defn- persist-page-history + [conn history] + (let [sql (sql/create-page-history history)] + (sc/execute conn sql))) + +(defn- persist-entry + [conn entry] + (let [payload (::payload entry) + type (::type entry)] + (case type + ::project (persist-project conn payload) + ::page (persist-page conn payload) + ::page-history (persist-page-history conn payload)))) + +(defn- read-data + [conn reader] + (loop [entry (read-entry reader)] + (when (not= entry ::eof) + (persist-entry conn entry) + (recur (read-entry reader))))) + +(defn import! + "Given a path to the previously exported bundle, try to import it." + [path] + (with-open [istream (io/input-stream (path/path path)) + zstream (snappy/input-stream istream) + conn (db/connection)] + (let [reader (t/reader zstream {:type :msgpack})] + (sc/atomic conn + (read-data conn reader) + nil)))) diff --git a/backend/src/uxbox/scheduled_jobs.clj b/backend/src/uxbox/scheduled_jobs.clj new file mode 100644 index 000000000..952c6eeac --- /dev/null +++ b/backend/src/uxbox/scheduled_jobs.clj @@ -0,0 +1,23 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.scheduled-jobs + "Time-based scheduled jobs." + (:require [mount.core :as mount :refer (defstate)] + [uxbox.config :as cfg] + [uxbox.db] + [uxbox.util.quartz :as qtz])) + +(defn- initialize + [] + (let [nss #{'uxbox.scheduled-jobs.garbage + 'uxbox.scheduled-jobs.emails}] + (-> (qtz/scheduler) + (qtz/start! {:search-on nss})))) + +(defstate scheduler + :start (initialize) + :stop (qtz/stop! scheduler)) diff --git a/backend/src/uxbox/scheduled_jobs/emails.clj b/backend/src/uxbox/scheduled_jobs/emails.clj new file mode 100644 index 000000000..abe4c7976 --- /dev/null +++ b/backend/src/uxbox/scheduled_jobs/emails.clj @@ -0,0 +1,123 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.scheduled-jobs.emails + "Email sending async tasks." + (:require [clojure.tools.logging :as log] + [suricatta.core :as sc] + [postal.core :as postal] + [uxbox.db :as db] + [uxbox.config :as cfg] + [uxbox.sql :as sql] + [uxbox.util.quartz :as qtz] + [uxbox.util.blob :as blob] + [uxbox.util.transit :as t] + [uxbox.util.data :as data])) + +;; --- Impl details + +(defn- decode-email-data + [{:keys [data] :as result}] + (merge result (when data + {:data (-> data blob/decode t/decode)}))) + +(defn- fetch-pending-emails + [conn] + (let [sqlv (sql/get-pending-emails)] + (->> (sc/fetch conn sqlv) + (map data/normalize-attrs) + (map decode-email-data)))) + +(defn- fetch-immediate-emails + [conn] + (let [sqlv (sql/get-immediate-emails)] + (->> (sc/fetch conn sqlv) + (map data/normalize-attrs) + (map decode-email-data)))) + +(defn- fetch-failed-emails + [conn] + (let [sqlv (sql/get-pending-emails)] + (->> (sc/fetch conn sqlv) + (map data/normalize-attrs) + (map decode-email-data)))) + +(defn- mark-email-as-sent + [conn id] + (let [sqlv (sql/mark-email-as-sent {:id id})] + (sc/execute conn sqlv))) + +(defn- mark-email-as-failed + [conn id] + (let [sqlv (sql/mark-email-as-failed {:id id})] + (sc/execute conn sqlv))) + +(defn- send-email-to-console + [{:keys [id data] :as entry}] + (println "******** start email:" id "**********") + (println (->> (:body data) + (filter #(= (:uxbox.emails.core/type %) :text/plain)) + (first) + (:content))) + (println "********** end email:" id "**********") + {:error :SUCCESS}) + +(defn- send-email + [{:keys [id data] :as entry}] + (let [config (:smtp cfg/config) + result (if (:noop config) + (send-email-to-console entry) + (postal/send-message config data))] + (if (= (:error result) :SUCCESS) + (log/debug "Message" id "sent successfully.") + (log/warn "Message" id "failed with:" (:message result))) + (if (= (:error result) :SUCCESS) + true + false))) + +(defn- send-emails + [conn entries] + (loop [entries entries] + (if-let [entry (first entries)] + (do (if (send-email entry) + (mark-email-as-sent conn (:id entry)) + (mark-email-as-failed conn (:id entry))) + (recur (rest entries)))))) + +;; --- Jobs + +(defn send-immediate-emails + {::qtz/interval (* 60 1 1000) ;; every 1min + ::qtz/repeat? true + ::qtz/job true} + [] + (log/info "task-send-immediate-emails...") + (with-open [conn (db/connection)] + (sc/atomic conn + (->> (fetch-immediate-emails conn) + (send-emails conn))))) + +(defn send-pending-emails + {::qtz/interval (* 60 5 1000) ;; every 5min + ::qtz/repeat? true + ::qtz/job true} + [] + (with-open [conn (db/connection)] + (sc/atomic conn + (->> (fetch-pending-emails conn) + (send-emails conn))))) + +(defn send-failed-emails + "Job that resends failed to send messages." + {::qtz/interval (* 60 5 1000) ;; every 5min + ::qtz/repeat? true + ::qtz/job true} + [] + (log/info "task-send-failed-emails...") + (with-open [conn (db/connection)] + (sc/atomic conn + (->> (fetch-failed-emails conn) + (send-emails conn))))) diff --git a/backend/src/uxbox/scheduled_jobs/garbage.clj b/backend/src/uxbox/scheduled_jobs/garbage.clj new file mode 100644 index 000000000..fedfaa180 --- /dev/null +++ b/backend/src/uxbox/scheduled_jobs/garbage.clj @@ -0,0 +1,28 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.scheduled-jobs.garbage + "Garbage Collector related tasks." + (:require [suricatta.core :as sc] + [uxbox.db :as db] + [uxbox.util.quartz :as qtz])) + +;; --- Delete projects + +;; TODO: move inline sql into resources/sql directory + +(defn clean-deleted-projects + "Task that cleans the deleted projects." + {::qtz/repeat? true + ::qtz/interval (* 1000 3600 24) + ::qtz/job true} + [] + (with-open [conn (db/connection)] + (sc/atomic conn + (let [sql (str "DELETE FROM projects " + " WHERE deleted_at is not null AND " + " (now()-deleted_at)::interval > '10 day'::interval;")] + (sc/execute conn sql))))) diff --git a/backend/src/uxbox/services.clj b/backend/src/uxbox/services.clj new file mode 100644 index 000000000..30d095ae8 --- /dev/null +++ b/backend/src/uxbox/services.clj @@ -0,0 +1,61 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.services + "Main namespace for access to all uxbox services." + (:require [suricatta.core :as sc] + [executors.core :as exec] + [promesa.core :as p] + [uxbox.db :as db] + [uxbox.services.core :as core] + [uxbox.util.transit :as t] + [uxbox.util.blob :as blob])) + +;; Load relevant subnamespaces with the implementation +(load "services/auth") +(load "services/projects") +(load "services/pages") +(load "services/images") +(load "services/icons") +(load "services/kvstore") + +;; --- Implementation + +(def ^:private encode (comp blob/encode t/encode)) + +(defn- insert-txlog + [data] + (with-open [conn (db/connection)] + (let [sql (str "INSERT INTO txlog (payload) VALUES (?)") + sqlv [sql (encode data)]] + (sc/execute conn sqlv)))) + +(defn- handle-novelty + [data] + (let [rs (core/novelty data) + rs (if (p/promise? rs) rs (p/resolved rs))] + (p/map (fn [v] + (insert-txlog data) + v) rs))) + +(defn- handle-query + [data] + (let [result (core/query data)] + (if (p/promise? result) + result + (p/resolved result)))) + +;; --- Public Api + +(defn novelty + [data] + (->> (exec/submit (partial handle-novelty data)) + (p/mapcat identity))) + +(defn query + [data] + (->> (exec/submit (partial handle-query data)) + (p/mapcat identity))) diff --git a/backend/src/uxbox/services/auth.clj b/backend/src/uxbox/services/auth.clj new file mode 100644 index 000000000..00d3659af --- /dev/null +++ b/backend/src/uxbox/services/auth.clj @@ -0,0 +1,47 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.services.auth + (:require [clojure.spec :as s] + [suricatta.core :as sc] + [buddy.hashers :as hashers] + [buddy.sign.jwt :as jwt] + [buddy.core.hash :as hash] + [uxbox.config :as cfg] + [uxbox.util.spec :as us] + [uxbox.db :as db] + [uxbox.services.core :as core] + [uxbox.services.users :as users] + [uxbox.util.exceptions :as ex])) + +;; --- Login + +(defn- check-user-password + [user password] + (hashers/check password (:password user))) + +(defn generate-token + [user] + (let [data {:id (:id user)} + opts (:auth-options cfg/config)] + (jwt/encrypt data cfg/secret opts))) + +(s/def ::scope string?) +(s/def ::login + (s/keys :req-un [::us/username ::us/password ::scope])) + +(defmethod core/novelty :login + [{:keys [username password scope] :as params}] + (s/assert ::login params) + (with-open [conn (db/connection)] + (let [user (users/find-user-by-username-or-email conn username)] + (when-not user + (ex/raise :type :validation + :code ::wrong-credentials)) + (if (check-user-password user password) + {:token (generate-token user)} + (ex/raise :type :validation + :code ::wrong-credentials))))) diff --git a/backend/src/uxbox/services/core.clj b/backend/src/uxbox/services/core.clj new file mode 100644 index 000000000..2b2bd7296 --- /dev/null +++ b/backend/src/uxbox/services/core.clj @@ -0,0 +1,27 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.services.core + (:require [clojure.walk :as walk] + [cuerdas.core :as str] + [uxbox.util.exceptions :as ex])) + +(defmulti novelty :type) + +(defmulti query :type) + +(defmethod novelty :default + [{:keys [type] :as data}] + (ex/raise :code ::not-implemented + :message-category :novelty + :message-type type)) + +(defmethod query :default + [{:keys [type] :as data}] + (ex/raise :code ::not-implemented + :message-category :query + :message-type type)) + diff --git a/backend/src/uxbox/services/icons.clj b/backend/src/uxbox/services/icons.clj new file mode 100644 index 000000000..6c478eb0c --- /dev/null +++ b/backend/src/uxbox/services/icons.clj @@ -0,0 +1,228 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.services.icons + "Icons library related services." + (:require [clojure.spec :as s] + [suricatta.core :as sc] + [uxbox.util.spec :as us] + [uxbox.sql :as sql] + [uxbox.db :as db] + [uxbox.services.core :as core] + [uxbox.util.exceptions :as ex] + [uxbox.util.transit :as t] + [uxbox.util.uuid :as uuid] + [uxbox.util.blob :as blob] + [uxbox.util.data :as data]) + (:import ratpack.form.UploadedFile + org.apache.commons.io.FilenameUtils)) + +;; --- Helpers & Specs + +(s/def ::user uuid?) +(s/def ::collection (s/nilable uuid?)) +(s/def ::width (s/and number? pos?)) +(s/def ::height (s/and number? pos?)) +(s/def ::view-box (s/and (s/coll-of number?) + #(= 4 (count %)) + vector?)) + +(s/def ::content string?) +(s/def ::mimetype string?) +(s/def ::metadata + (s/keys :opt-un [::width ::height ::view-box ::mimetype])) + +(defn decode-metadata + [{:keys [metadata] :as data}] + (if metadata + (assoc data :metadata (-> metadata blob/decode t/decode)) + data)) + +;; --- Create Collection + +(defn create-collection + [conn {:keys [id user name]}] + (let [id (or id (uuid/random)) + params {:id id :user user :name name} + sqlv (sql/create-icon-collection params)] + (-> (sc/fetch-one conn sqlv) + (data/normalize)))) + +(s/def ::create-icon-collection + (s/keys :req-un [::user ::us/name] + :opt-un [::us/id])) + +(defmethod core/novelty :create-icon-collection + [params] + (s/assert ::create-icon-collection params) + (with-open [conn (db/connection)] + (create-collection conn params))) + +;; --- Update Collection + +(defn update-collection + [conn {:keys [id user name version]}] + (let [sqlv (sql/update-icon-collection {:id id + :user user + :name name + :version version})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize)))) + +(s/def ::update-icon-collection + (s/keys :req-un [::user ::us/name ::us/version] + :opt-un [::us/id])) + +(defmethod core/novelty :update-icon-collection + [params] + (s/assert ::update-icon-collection params) + (with-open [conn (db/connection)] + (sc/apply-atomic conn update-collection params))) + +;; --- Copy Icon + +(s/def ::copy-icon + (s/keys :req-un [:us/id ::collection ::user])) + +(defn- retrieve-icon + [conn {:keys [user id]}] + (let [sqlv (sql/get-icon {:user user :id id})] + (some->> (sc/fetch-one conn sqlv) + (data/normalize-attrs)))) + +(declare create-icon) + +(defn- copy-icon + [conn {:keys [user id collection]}] + (let [icon (retrieve-icon conn {:id id :user user})] + (when-not icon + (ex/raise :type :validation + :code ::icon-does-not-exists)) + (let [params (dissoc icon :id)] + (create-icon conn params)))) + +(defmethod core/novelty :copy-icon + [params] + (s/assert ::copy-icon params) + (with-open [conn (db/connection)] + (sc/apply-atomic conn copy-icon params))) + +;; --- List Collections + +(defn get-collections-by-user + [conn user] + (let [sqlv (sql/get-icon-collections {:user user})] + (->> (sc/fetch conn sqlv) + (map data/normalize)))) + +(defmethod core/query :list-icon-collections + [{:keys [user] :as params}] + (s/assert ::user user) + (with-open [conn (db/connection)] + (get-collections-by-user conn user))) + +;; --- Delete Collection + +(defn delete-collection + [conn {:keys [id user]}] + (let [sqlv (sql/delete-icon-collection {:id id :user user})] + (pos? (sc/execute conn sqlv)))) + +(s/def ::delete-icon-collection + (s/keys :req-un [::user] + :opt-un [::us/id])) + +(defmethod core/novelty :delete-icon-collection + [params] + (s/assert ::delete-icon-collection params) + (with-open [conn (db/connection)] + (delete-collection conn params))) + +;; --- Create Icon (Upload) + +(defn create-icon + [conn {:keys [id user name collection + metadata content] :as params}] + (let [id (or id (uuid/random)) + params {:id id + :name name + :content content + :metadata (-> metadata t/encode blob/encode) + :collection collection + :user user} + sqlv (sql/create-icon params)] + (some-> (sc/fetch-one conn sqlv) + (data/normalize) + (decode-metadata)))) + +(s/def ::create-icon + (s/keys :req-un [::user ::us/name ::metadata ::content] + :opt-un [::us/id ::collection])) + +(defmethod core/novelty :create-icon + [params] + (s/assert ::create-icon params) + (with-open [conn (db/connection)] + (create-icon conn params))) + +;; --- Update Icon + +(defn update-icon + [conn {:keys [id name version user collection]}] + (let [sqlv (sql/update-icon {:id id + :collection collection + :name name + :user user + :version version})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize) + (decode-metadata)))) + +(s/def ::update-icon + (s/keys :req-un [::us/id ::user ::us/name ::us/version ::collection])) + +(defmethod core/novelty :update-icon + [params] + (s/assert ::update-icon params) + (with-open [conn (db/connection)] + (update-icon conn params))) + +;; --- Delete Icon + +(defn delete-icon + [conn {:keys [user id]}] + (let [sqlv (sql/delete-icon {:id id :user user})] + (pos? (sc/execute conn sqlv)))) + +(s/def ::delete-icon + (s/keys :req-un [::user] + :opt-un [::us/id])) + +(defmethod core/novelty :delete-icon + [params] + (s/assert ::delete-icon params) + (with-open [conn (db/connection)] + (delete-icon conn params))) + +;; --- List Icons + +(defn get-icons-by-user + [conn user collection] + (let [sqlv (if collection + (sql/get-icons-by-collection {:user user :collection collection}) + (sql/get-icons {:user user}))] + (->> (sc/fetch conn sqlv) + (map data/normalize) + (map decode-metadata)))) + +(s/def ::list-icons + (s/keys :req-un [::user ::collection])) + +(defmethod core/query :list-icons + [{:keys [user collection] :as params}] + (s/assert ::list-icons params) + (with-open [conn (db/connection)] + (get-icons-by-user conn user collection))) diff --git a/backend/src/uxbox/services/images.clj b/backend/src/uxbox/services/images.clj new file mode 100644 index 000000000..1e24accad --- /dev/null +++ b/backend/src/uxbox/services/images.clj @@ -0,0 +1,236 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.services.images + "Images library related services." + (:require [clojure.spec :as s] + [promesa.core :as p] + [suricatta.core :as sc] + [buddy.core.codecs :as codecs] + [storages.core :as st] + [storages.util :as path] + [uxbox.config :as ucfg] + [uxbox.util.spec :as us] + [uxbox.sql :as sql] + [uxbox.db :as db] + [uxbox.media :as media] + [uxbox.services.core :as core] + [uxbox.util.exceptions :as ex] + [uxbox.util.transit :as t] + [uxbox.util.uuid :as uuid] + [uxbox.util.data :as data]) + (:import ratpack.form.UploadedFile + org.apache.commons.io.FilenameUtils)) + +(s/def ::width integer?) +(s/def ::height integer?) +(s/def ::mimetype string?) +(s/def ::user uuid?) +(s/def ::path string?) +(s/def ::collection (s/nilable uuid?)) + +;; --- Create Collection + +(defn create-collection + [conn {:keys [id user name]}] + (let [id (or id (uuid/random)) + params {:id id :user user :name name} + sqlv (sql/create-image-collection params)] + (-> (sc/fetch-one conn sqlv) + (data/normalize-attrs)))) + +(s/def ::create-image-collection + (s/keys :req-un [::user ::us/name] + :opt-un [::us/id])) + +(defmethod core/novelty :create-image-collection + [params] + (s/assert ::create-image-collection params) + (with-open [conn (db/connection)] + (create-collection conn params))) + +;; --- Update Collection + +(defn update-collection + [conn {:keys [id user name version]}] + (let [sqlv (sql/update-image-collection {:id id + :user user + :name name + :version version})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize-attrs)))) + +(s/def ::update-image-collection + (s/keys :req-un [::user ::us/name ::us/version] + :opt-un [::us/id])) + +(defmethod core/novelty :update-image-collection + [params] + (s/assert ::update-image-collection params) + (with-open [conn (db/connection)] + (update-collection conn params))) + +;; --- List Collections + +(defn get-collections-by-user + [conn user] + (let [sqlv (sql/get-image-collections {:user user})] + (->> (sc/fetch conn sqlv) + (map data/normalize-attrs)))) + +(defmethod core/query :list-image-collections + [{:keys [user] :as params}] + (s/assert ::user user) + (with-open [conn (db/connection)] + (get-collections-by-user conn user))) + +;; --- Delete Collection + +(defn delete-collection + [conn {:keys [id user]}] + (let [sqlv (sql/delete-image-collection {:id id :user user})] + (pos? (sc/execute conn sqlv)))) + +(s/def ::delete-image-collection + (s/keys :req-un [::user] + :opt-un [::us/id])) + +(defmethod core/novelty :delete-image-collection + [params] + (s/assert ::delete-image-collection params) + (with-open [conn (db/connection)] + (delete-collection conn params))) + +;; --- Retrieve Image + +(defn retrieve-image + [conn {:keys [user id]}] + (let [sqlv (sql/get-image {:user user :id id})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize-attrs)))) + +(s/def ::retrieve-image + (s/keys :req-un [::user ::us/id])) + +(defmethod core/query :retrieve-image + [params] + (s/assert ::retrieve-image params) + (with-open [conn (db/connection)] + (retrieve-image conn params))) + +;; --- Create Image (Upload) + +(defn create-image + [conn {:keys [id user name path collection + height width mimetype]}] + (let [id (or id (uuid/random)) + sqlv (sql/create-image {:id id + :name name + :mimetype mimetype + :path path + :width width + :height height + :collection collection + :user user})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize-attrs)))) + +(s/def ::create-image + (s/keys :req-un [::user ::us/name ::path ::width ::height ::mimetype] + :opt-un [::us/id])) + +(defmethod core/novelty :create-image + [params] + (s/assert ::create-image params) + (with-open [conn (db/connection)] + (create-image conn params))) + +;; --- Update Image + +(defn update-image + [conn {:keys [id name version user collection]}] + (let [sqlv (sql/update-image {:id id + :collection collection + :name name + :user user + :version version})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize-attrs)))) + +(s/def ::update-image + (s/keys :req-un [::user ::us/name ::us/version ::collection] + :opt-un [::us/id])) + +(defmethod core/novelty :update-image + [params] + (s/assert ::update-image params) + (with-open [conn (db/connection)] + (update-image conn params))) + +;; --- Copy Image + +(s/def ::copy-image + (s/keys :req-un [::us/id ::collection ::user])) + +(declare retrieve-image) + +(defn- copy-image + [conn {:keys [user id collection]}] + (let [image (retrieve-image conn {:id id :user user}) + storage media/images-storage] + (when-not image + (ex/raise :type :validation + :code ::image-does-not-exists)) + (let [path @(st/lookup storage (:path image)) + filename (path/base-name path) + path @(st/save storage filename path) + image (assoc image + :path (str path) + :collection collection) + image (dissoc image :id)] + (create-image conn image)))) + +(defmethod core/novelty :copy-image + [params] + (s/assert ::copy-image params) + (with-open [conn (db/connection)] + (sc/apply-atomic conn copy-image params))) + +;; --- Delete Image + +(defn delete-image + [conn {:keys [user id]}] + (let [sqlv (sql/delete-image {:id id :user user})] + (pos? (sc/execute conn sqlv)))) + +(s/def ::delete-image + (s/keys :req-un [::user] + :opt-un [::us/id])) + +(defmethod core/novelty :delete-image + [params] + (s/assert ::delete-image params) + (with-open [conn (db/connection)] + (delete-image conn params))) + +;; --- List Images + +(defn get-images-by-user + [conn user collection] + (let [sqlv (if collection + (sql/get-images-by-collection {:user user :collection collection}) + (sql/get-images {:user user}))] + (->> (sc/fetch conn sqlv) + (map data/normalize-attrs)))) + +(s/def ::list-images + (s/keys :req-un [::user ::collection])) + +(defmethod core/query :list-images + [{:keys [user collection] :as params}] + (s/assert ::list-images params) + (with-open [conn (db/connection)] + (get-images-by-user conn user collection))) diff --git a/backend/src/uxbox/services/kvstore.clj b/backend/src/uxbox/services/kvstore.clj new file mode 100644 index 000000000..4f5c1ae2a --- /dev/null +++ b/backend/src/uxbox/services/kvstore.clj @@ -0,0 +1,87 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.services.kvstore + (:require [clojure.spec :as s] + [suricatta.core :as sc] + [buddy.core.codecs :as codecs] + [uxbox.config :as ucfg] + [uxbox.sql :as sql] + [uxbox.db :as db] + [uxbox.util.spec :as us] + [uxbox.services.core :as core] + [uxbox.util.time :as dt] + [uxbox.util.data :as data] + [uxbox.util.transit :as t] + [uxbox.util.blob :as blob] + [uxbox.util.uuid :as uuid])) + +(s/def ::version integer?) +(s/def ::key string?) +(s/def ::value any?) +(s/def ::user uuid?) + +(defn decode-value + [{:keys [value] :as data}] + (if value + (assoc data :value (-> value blob/decode t/decode)) + data)) + +;; --- Update KVStore + +(s/def ::update-kvstore + (s/keys :req-un [::key ::value ::user ::version])) + +(defn update-kvstore + [conn {:keys [user key value version] :as data}] + (let [opts {:user user + :key key + :version version + :value (-> value t/encode blob/encode)} + sqlv (sql/update-kvstore opts)] + (some->> (sc/fetch-one conn sqlv) + (data/normalize-attrs) + (decode-value)))) + +(defmethod core/novelty :update-kvstore + [params] + (s/assert ::update-kvstore params) + (with-open [conn (db/connection)] + (sc/apply-atomic conn update-kvstore params))) + +;; --- Retrieve KVStore + +(s/def ::retrieve-kvstore + (s/keys :req-un [::key ::user])) + +(defn retrieve-kvstore + [conn {:keys [user key] :as params}] + (let [sqlv (sql/retrieve-kvstore params)] + (some->> (sc/fetch-one conn sqlv) + (data/normalize-attrs) + (decode-value)))) + +(defmethod core/query :retrieve-kvstore + [params] + (s/assert ::retrieve-kvstore params) + (with-open [conn (db/connection)] + (retrieve-kvstore conn params))) + +;; --- Delete KVStore + +(s/def ::delete-kvstore + (s/keys :req-un [::key ::user])) + +(defn delete-kvstore + [conn {:keys [user key] :as params}] + (let [sqlv (sql/delete-kvstore params)] + (pos? (sc/execute conn sqlv)))) + +(defmethod core/novelty :delete-kvstore + [params] + (s/assert ::delete-kvstore params) + (with-open [conn (db/connection)] + (sc/apply-atomic conn delete-kvstore params))) diff --git a/backend/src/uxbox/services/pages.clj b/backend/src/uxbox/services/pages.clj new file mode 100644 index 000000000..4dc6d2eb2 --- /dev/null +++ b/backend/src/uxbox/services/pages.clj @@ -0,0 +1,226 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.services.pages + (:require [clojure.spec :as s] + [suricatta.core :as sc] + [buddy.core.codecs :as codecs] + [uxbox.config :as ucfg] + [uxbox.sql :as sql] + [uxbox.db :as db] + [uxbox.util.spec :as us] + [uxbox.services.core :as core] + [uxbox.services.auth :as usauth] + [uxbox.util.time :as dt] + [uxbox.util.data :as data] + [uxbox.util.transit :as t] + [uxbox.util.blob :as blob] + [uxbox.util.uuid :as uuid])) + +(declare decode-page-data) +(declare decode-page-metadata) +(declare encode-data) + +(s/def ::data any?) +(s/def ::user uuid?) +(s/def ::project uuid?) +(s/def ::metadata any?) +(s/def ::max integer?) +(s/def ::pinned boolean?) +(s/def ::since integer?) + +;; --- Create Page + +(defn create-page + [conn {:keys [id user project name data metadata] :as params}] + (let [opts {:id (or id (uuid/random)) + :user user + :project project + :name name + :data (-> data t/encode blob/encode) + :metadata (-> metadata t/encode blob/encode)} + sqlv (sql/create-page opts)] + (->> (sc/fetch-one conn sqlv) + (data/normalize-attrs) + (decode-page-data) + (decode-page-metadata)))) + +(s/def ::create-page + (s/keys :req-un [::data ::user ::project ::us/name ::metadata] + :opt-un [::us/id])) + +(defmethod core/novelty :create-page + [params] + (s/assert ::create-page params) + (with-open [conn (db/connection)] + (create-page conn params))) + +;; --- Update Page + +(defn update-page + [conn {:keys [id user project name + data version metadata] :as params}] + (let [opts {:id (or id (uuid/random)) + :user user + :project project + :name name + :version version + :data (-> data t/encode blob/encode) + :metadata (-> metadata t/encode blob/encode)} + sqlv (sql/update-page opts)] + (some-> (sc/fetch-one conn sqlv) + (data/normalize-attrs) + (decode-page-data) + (decode-page-metadata)))) + +(s/def ::update-page + (s/merge ::create-page (s/keys :req-un [::us/version]))) + +(defmethod core/novelty :update-page + [params] + (s/assert ::update-page params) + (with-open [conn (db/connection)] + (update-page conn params))) + +;; --- Update Page Metadata + +(defn update-page-metadata + [conn {:keys [id user project name + version metadata] :as params}] + (let [opts {:id (or id (uuid/random)) + :user user + :project project + :name name + :version version + :metadata (-> metadata t/encode blob/encode)} + sqlv (sql/update-page-metadata opts)] + (some-> (sc/fetch-one conn sqlv) + (data/normalize-attrs) + (decode-page-data) + (decode-page-metadata)))) + +(s/def ::update-page-metadata + (s/keys :req-un [::user ::project ::us/name ::us/version ::metadata] + :opt-un [::us/id ::data])) + +(defmethod core/novelty :update-page-metadata + [params] + (s/assert ::update-page-metadata params) + (with-open [conn (db/connection)] + (update-page-metadata conn params))) + +;; --- Delete Page + +(defn delete-page + [conn {:keys [id user] :as params}] + (let [sqlv (sql/delete-page {:id id :user user})] + (pos? (sc/execute conn sqlv)))) + +(s/def ::delete-page + (s/keys :req-un [::user ::us/id])) + +(defmethod core/novelty :delete-page + [params] + (s/assert ::delete-page params) + (with-open [conn (db/connection)] + (delete-page conn params))) + +;; --- List Pages by Project + +(defn get-pages-for-project + [conn project] + (let [sqlv (sql/get-pages-for-project {:project project})] + (->> (sc/fetch conn sqlv) + (map data/normalize-attrs) + (map decode-page-data) + (map decode-page-metadata)))) + +(defn get-pages-for-user-and-project + [conn {:keys [user project]}] + (let [sqlv (sql/get-pages-for-user-and-project + {:user user :project project})] + (->> (sc/fetch conn sqlv) + (map data/normalize-attrs) + (map decode-page-data) + (map decode-page-metadata)))) + +(s/def ::list-pages-by-project + (s/keys :req-un [::user ::project])) + +(defmethod core/query :list-pages-by-project + [params] + (s/assert ::list-pages-by-project params) + (with-open [conn (db/connection)] + (get-pages-for-user-and-project conn params))) + +;; --- Page History (Query) + +(defn get-page-history + [conn {:keys [id user since max pinned] + :or {since Long/MAX_VALUE max 10}}] + (let [sqlv (sql/get-page-history {:user user + :page id + :since since + :max max + :pinned pinned})] + (->> (sc/fetch conn sqlv) + (map data/normalize-attrs) + (map decode-page-data)))) + +(s/def ::list-page-history + (s/keys :req-un [::us/id ::user] + :opt-un [::max ::pinned ::since])) + +(defmethod core/query :list-page-history + [params] + (s/assert ::list-page-history params) + (with-open [conn (db/connection)] + (get-page-history conn params))) + +;; --- 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-> (sc/fetch-one conn sqlv) + (data/normalize-attrs) + (decode-page-data)))) + +(s/def ::label string?) +(s/def ::update-page-history + (s/keys :req-un [::user ::us/id ::pinned ::label])) + +(defmethod core/novelty :update-page-history + [params] + (s/assert ::update-page-history params) + (with-open [conn (db/connection)] + (update-page-history conn params))) + +;; --- Helpers + +(defn- decode-page-metadata + [{:keys [metadata] :as result}] + (s/assert ::us/bytes metadata) + (merge result (when metadata + {:metadata (-> metadata blob/decode t/decode)}))) + +(defn- decode-page-data + [{:keys [data] :as result}] + (s/assert ::us/bytes data) + (merge result (when data + {:data (-> data blob/decode t/decode)}))) + +(defn get-page-by-id + [conn id] + (s/assert ::us/id id) + (let [sqlv (sql/get-page-by-id {:id id})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize-attrs) + (decode-page-data) + (decode-page-metadata)))) diff --git a/backend/src/uxbox/services/projects.clj b/backend/src/uxbox/services/projects.clj new file mode 100644 index 000000000..e958917e0 --- /dev/null +++ b/backend/src/uxbox/services/projects.clj @@ -0,0 +1,143 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.services.projects + (:require [clojure.spec :as s] + [suricatta.core :as sc] + [buddy.core.codecs :as codecs] + [uxbox.config :as ucfg] + [uxbox.sql :as sql] + [uxbox.db :as db] + [uxbox.util.spec :as us] + [uxbox.services.core :as core] + [uxbox.services.pages :as pages] + [uxbox.util.data :as data] + [uxbox.util.transit :as t] + [uxbox.util.blob :as blob] + [uxbox.util.uuid :as uuid])) + +(s/def ::token string?) +(s/def ::data string?) +(s/def ::user uuid?) +(s/def ::project uuid?) + +;; --- Create Project + +(defn create-project + [conn {:keys [id user name] :as data}] + (let [id (or id (uuid/random)) + sqlv (sql/create-project {:id id :user user :name name})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize)))) + +(s/def ::create-project + (s/keys :req-un [::user ::us/name] + :opt-un [::us/id])) + +(defmethod core/novelty :create-project + [params] + (s/assert ::create-project params) + (with-open [conn (db/connection)] + (create-project conn params))) + +;; --- Update Project + +(defn- update-project + [conn {:keys [name version id user] :as data}] + (let [sqlv (sql/update-project {:name name + :version version + :id id + :user user})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize)))) + +(s/def ::update-project + (s/merge ::create-project (s/keys :req-un [::us/version]))) + +(defmethod core/novelty :update-project + [params] + (s/assert ::update-project params) + (with-open [conn (db/connection)] + (update-project conn params))) + +;; --- Delete Project + +(defn- delete-project + [conn {:keys [id user] :as data}] + (let [sqlv (sql/delete-project {:id id :user user})] + (pos? (sc/execute conn sqlv)))) + +(s/def ::delete-project + (s/keys :req-un [::us/id ::user])) + +(defmethod core/novelty :delete-project + [params] + (s/assert ::delete-project params) + (with-open [conn (db/connection)] + (delete-project conn params))) + +;; --- List Projects + +(declare decode-page-metadata) +(declare decode-page-data) + +(defn get-projects + [conn user] + (let [sqlv (sql/get-projects {:user user})] + (->> (sc/fetch conn sqlv) + (map data/normalize) + + ;; This is because the project comes with + ;; the first page preloaded and it need + ;; to be decoded. + (map decode-page-metadata) + (map decode-page-data)))) + +(defmethod core/query :list-projects + [{:keys [user] :as params}] + (s/assert ::user user) + (with-open [conn (db/connection)] + (get-projects conn user))) + +;; --- Retrieve Project by share token + +(defn- get-project-by-share-token + [conn token] + (let [sqlv (sql/get-project-by-share-token {:token token}) + project (some-> (sc/fetch-one conn sqlv) + (data/normalize))] + (when-let [id (:id project)] + (let [pages (vec (pages/get-pages-for-project conn id))] + (assoc project :pages pages))))) + +(defmethod core/query :retrieve-project-by-share-token + [{:keys [token]}] + (s/assert ::token token) + (with-open [conn (db/connection)] + (get-project-by-share-token conn token))) + +;; --- Retrieve share tokens + +(defn get-share-tokens-for-project + [conn project] + (s/assert ::project project) + (let [sqlv (sql/get-share-tokens-for-project {:project project})] + (->> (sc/fetch conn sqlv) + (map data/normalize)))) + +;; Helpers + +(defn- decode-page-metadata + [{:keys [page-metadata] :as result}] + (merge result (when page-metadata + {:page-metadata (-> page-metadata blob/decode t/decode)}))) + +(defn- decode-page-data + [{:keys [page-data] :as result}] + (merge result (when page-data + {:page-data (-> page-data blob/decode t/decode)}))) + + diff --git a/backend/src/uxbox/services/svgparse.clj b/backend/src/uxbox/services/svgparse.clj new file mode 100644 index 000000000..a41b6a2cf --- /dev/null +++ b/backend/src/uxbox/services/svgparse.clj @@ -0,0 +1,95 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.services.svgparse + (:require [clojure.spec :as s] + [cuerdas.core :as str] + [uxbox.util.spec :as us] + [uxbox.services.core :as core] + [uxbox.util.exceptions :as ex]) + (:import org.jsoup.Jsoup + java.io.InputStream)) + +(s/def ::content string?) +(s/def ::width number?) +(s/def ::height number?) +(s/def ::name string?) +(s/def ::view-box (s/coll-of number? :min-count 4 :max-count 4)) +(s/def ::svg-entity (s/keys :req-un [::content ::width ::height ::view-box] + :opt-un [::name])) + +;; --- Implementation + +(defn- parse-double + [data] + {:pre [(string? data)]} + (Double/parseDouble data)) + +(defn- parse-viewbox + [data] + {:pre [(string? data)]} + (mapv parse-double (str/split data #"\s+"))) + +(defn- assoc-attr + [acc attr] + (let [key (.getKey attr) + val (.getValue attr)] + (case key + "width" (assoc acc :width (parse-double val)) + "height" (assoc acc :height (parse-double val)) + "viewbox" (assoc acc :view-box (parse-viewbox val)) + "sodipodi:docname" (assoc acc :name val) + acc))) + +(defn- parse-attrs + [element] + (let [attrs (.attributes element)] + (reduce assoc-attr {} attrs))) + +(defn- parse-svg + [data] + (try + (let [document (Jsoup/parse data) + svgelement (some-> (.body document) + (.getElementsByTag "svg") + (first)) + innerxml (.html svgelement) + attrs (parse-attrs svgelement)] + (merge {:content innerxml} attrs)) + (catch java.lang.IllegalArgumentException e + (ex/raise :type :validation + :code ::invalid-input + :message "Input does not seems to be a valid svg.")) + (catch java.lang.NullPointerException e + (ex/raise :type :validation + :code ::invalid-input + :message "Input does not seems to be a valid svg.")) + (catch Exception e + (.printStackTrace e) + (ex/raise :code ::unexpected)))) + +;; --- Public Api + +(defn parse-string + "Parse SVG from a string." + [data] + {:pre [(string? data)]} + (let [result (parse-svg data)] + (if (s/valid? ::svg-entity result) + result + (ex/raise :type :validation + :code ::invalid-result + :message "The result does not conform valid svg entity.")))) + +(defn parse + [data] + {:pre [(instance? InputStream data)]} + (parse-string (slurp data))) + +(defmethod core/query :parse-svg + [{:keys [data] :as params}] + {:pre [(string? data)]} + (parse-string data)) diff --git a/backend/src/uxbox/services/users.clj b/backend/src/uxbox/services/users.clj new file mode 100644 index 000000000..5b4e8d810 --- /dev/null +++ b/backend/src/uxbox/services/users.clj @@ -0,0 +1,317 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.services.users + (:require [clojure.spec :as s] + [mount.core :as mount :refer (defstate)] + [suricatta.core :as sc] + [buddy.hashers :as hashers] + [buddy.sign.jwe :as jwe] + [uxbox.sql :as sql] + [uxbox.db :as db] + [uxbox.util.spec :as us] + [uxbox.emails :as emails] + [uxbox.services.core :as core] + [uxbox.util.transit :as t] + [uxbox.util.exceptions :as ex] + [uxbox.util.data :as data] + [uxbox.util.blob :as blob] + [uxbox.util.uuid :as uuid] + [uxbox.util.token :as token])) + +(declare decode-user-data) +(declare trim-user-attrs) +(declare find-user-by-id) +(declare find-full-user-by-id) +(declare find-user-by-username-or-email) + +(s/def ::user uuid?) +(s/def ::fullname string?) +(s/def ::metadata any?) +(s/def ::old-password string?) +(s/def ::path string?) + +;; --- Retrieve User Profile (own) + +(defmethod core/query :retrieve-profile + [{:keys [user] :as params}] + (s/assert ::user user) + (with-open [conn (db/connection)] + (some-> (find-user-by-id conn (:user params)) + (decode-user-data)))) + +;; --- Update User Profile (own) + +(defn- check-profile-existence! + [conn {:keys [id username email]}] + (let [sqlv1 (sql/user-with-email-exists? {:id id :email email}) + sqlv2 (sql/user-with-username-exists? {:id id :username username})] + (when (:val (sc/fetch-one conn sqlv1)) + (ex/raise :type :validation + :code ::email-already-exists)) + (when (:val (sc/fetch-one conn sqlv2)) + (ex/raise ::username-already-exists)))) + +(defn- update-profile + [conn {:keys [id username email fullname metadata] :as params}] + (check-profile-existence! conn params) + (let [metadata (-> metadata t/encode blob/encode) + sqlv (sql/update-profile {:username username + :fullname fullname + :metadata metadata + :email email + :id id})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize-attrs) + (trim-user-attrs) + (decode-user-data) + (dissoc :password)))) + +(s/def ::update-profile + (s/keys :req-un [::us/id ::us/username ::us/email ::fullname ::metadata])) + +(defmethod core/novelty :update-profile + [params] + (s/assert ::update-profile params) + (with-open [conn (db/connection)] + (sc/apply-atomic conn update-profile params))) + +;; --- Update Password + +(defn update-password + [conn {:keys [user password]}] + (let [password (hashers/encrypt password) + sqlv (sql/update-profile-password {:id user :password password})] + (pos? (sc/execute conn sqlv)))) + +(defn- validate-old-password + [conn {:keys [user old-password] :as params}] + (let [user (find-full-user-by-id conn user)] + (when-not (hashers/check old-password (:password user)) + (ex/raise :type :validation + :code ::old-password-not-match)) + params)) + +(s/def ::update-password + (s/keys :req-un [::user ::us/password ::old-password])) + +(defmethod core/novelty :update-profile-password + [params] + (s/assert ::update-password params) + (with-open [conn (db/connection)] + (->> params + (validate-old-password conn) + (update-password conn)))) + +;; --- Update Photo + +(defn update-photo + [conn {:keys [user path]}] + (let [sqlv (sql/update-profile-photo {:id user :photo path})] + (pos? (sc/execute conn sqlv)))) + +(s/def ::update-photo + (s/keys :req-un [::user ::path])) + +(defmethod core/novelty :update-profile-photo + [params] + (s/assert ::update-photo params) + (with-open [conn (db/connection)] + (update-photo conn params))) + +;; --- Create User + +(s/def ::create-user + (s/keys :req-un [::metadata ::fullname ::us/email ::us/password] + :opt-un [::us/id])) + +(defn create-user + [conn {:keys [id username password email fullname metadata] :as data}] + (s/assert ::create-user data) + (let [id (or id (uuid/random)) + metadata (-> metadata t/encode blob/encode) + password (hashers/encrypt password) + sqlv (sql/create-profile {:id id + :fullname fullname + :username username + :email email + :password password + :metadata metadata})] + (->> (sc/fetch-one conn sqlv) + (data/normalize-attrs) + (trim-user-attrs) + (decode-user-data)))) + +;; --- Register User + +(defn- check-user-registred! + "Check if the user identified by username or by email + is already registred in the platform." + [conn {:keys [username email]}] + (let [sqlv1 (sql/user-with-email-exists? {:email email}) + sqlv2 (sql/user-with-username-exists? {:username username})] + (when (:val (sc/fetch-one conn sqlv1)) + (ex/raise :type :validation + :code ::email-already-exists)) + (when (:val (sc/fetch-one conn sqlv2)) + (ex/raise :type :validation + :code ::username-already-exists)))) + +(defn- register-user + "Create the user entry onthe database with limited input + filling all the other fields with defaults." + [conn {:keys [username fullname email password] :as params}] + (check-user-registred! conn params) + (let [metadata (-> nil t/encode blob/encode) + password (hashers/encrypt password) + sqlv (sql/create-profile {:id (uuid/random) + :fullname fullname + :username username + :email email + :password password + :metadata metadata})] + (sc/execute conn sqlv) + (emails/send! {:email/name :users/register + :email/to (:email params) + :email/priority :high + :name (:fullname params)}) + nil)) + +(s/def ::register + (s/keys :req-un [::us/username ::us/email ::us/password ::fullname])) + +(defmethod core/novelty :register-profile + [params] + (s/assert ::register params) + (with-open [conn (db/connection)] + (sc/apply-atomic conn register-user params))) + +;; --- Password Recover + +(defn- recovery-token-exists? + "Checks if the token exists in the system. Just + return `true` or `false`." + [conn token] + (let [sqlv (sql/recovery-token-exists? {:token token}) + result (sc/fetch-one conn sqlv)] + (:token_exists result))) + +(defn- retrieve-user-for-recovery-token + "Retrieve a user id (uuid) for the given token. If + no user is found, an exception is raised." + [conn token] + (let [sqlv (sql/get-recovery-token {:token token}) + data (sc/fetch-one conn sqlv)] + (or (:user data) + (ex/raise :type :validation + :code ::invalid-token)))) + +(defn- mark-token-as-used + [conn token] + (let [sqlv (sql/mark-recovery-token-used {:token token})] + (pos? (sc/execute conn sqlv)))) + +(defn- recover-password + "Given a token and password, resets the password + to corresponding user or raise an exception." + [conn {:keys [token password]}] + (let [user (retrieve-user-for-recovery-token conn token)] + (update-password conn {:user user :password password}) + (mark-token-as-used conn token) + nil)) + +(defn- create-recovery-token + "Creates a new recovery token for specified user and return it." + [conn userid] + (let [token (token/random) + sqlv (sql/create-recovery-token {:user userid + :token token})] + (sc/execute conn sqlv) + token)) + +(defn- retrieve-user-for-password-recovery + [conn username] + (let [user (find-user-by-username-or-email conn username)] + (when-not user + (ex/raise :type :validation :code ::user-does-not-exists)) + user)) + +(defn- request-password-recovery + "Creates a new recovery password token and sends it via email + to the correspondig to the given username or email address." + [conn username] + (let [user (retrieve-user-for-password-recovery conn username) + token (create-recovery-token conn (:id user))] + (emails/send! {:email/name :users/password-recovery + :email/to (:email user) + :name (:fullname user) + :token token}) + token)) + +(defmethod core/query :validate-profile-password-recovery-token + [{:keys [token]}] + (s/assert ::us/token token) + (with-open [conn (db/connection)] + (recovery-token-exists? conn token))) + +(defmethod core/novelty :request-profile-password-recovery + [{:keys [username]}] + (s/assert ::us/username username) + (with-open [conn (db/connection)] + (sc/atomic conn + (request-password-recovery conn username)))) + +(s/def ::recover-password + (s/keys :req-un [::us/token ::us/password])) + +(defmethod core/novelty :recover-profile-password + [params] + (s/assert ::recover-password params) + (with-open [conn (db/connection)] + (sc/apply-atomic conn recover-password params))) + +;; --- Query Helpers + +(defn find-full-user-by-id + "Find user by its id. This function is for internal + use only because it returns a lot of sensitive information. + If no user is found, `nil` is returned." + [conn id] + (let [sqlv (sql/get-profile {:id id})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize-attrs)))) + +(defn find-user-by-id + "Find user by its id. If no user is found, `nil` is returned." + [conn id] + (let [sqlv (sql/get-profile {:id id})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize-attrs) + (trim-user-attrs) + (dissoc :password)))) + +(defn find-user-by-username-or-email + "Finds a user in the database by username and email. If no + user is found, `nil` is returned." + [conn username] + (let [sqlv (sql/get-profile-by-username {:username username})] + (some-> (sc/fetch-one conn sqlv) + (data/normalize-attrs) + (trim-user-attrs)))) + +;; --- Attrs Helpers + +(defn- decode-user-data + [{:keys [metadata] :as result}] + (merge result (when metadata + {:metadata (-> metadata blob/decode t/decode)}))) + +(defn trim-user-attrs + "Only selects a publicy visible user attrs." + [user] + (select-keys user [:id :username :fullname + :password :metadata :email + :created-at :photo])) diff --git a/backend/src/uxbox/sql.clj b/backend/src/uxbox/sql.clj new file mode 100644 index 000000000..a6e87be93 --- /dev/null +++ b/backend/src/uxbox/sql.clj @@ -0,0 +1,18 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.sql + (:require [hugsql.core :as hugsql])) + +(hugsql/def-sqlvec-fns "sql/projects.sql" {:quoting :ansi :fn-suffix ""}) +(hugsql/def-sqlvec-fns "sql/pages.sql" {:quoting :ansi :fn-suffix ""}) +(hugsql/def-sqlvec-fns "sql/users.sql" {:quoting :ansi :fn-suffix ""}) +(hugsql/def-sqlvec-fns "sql/emails.sql" {:quoting :ansi :fn-suffix ""}) +(hugsql/def-sqlvec-fns "sql/images.sql" {:quoting :ansi :fn-suffix ""}) +(hugsql/def-sqlvec-fns "sql/icons.sql" {:quoting :ansi :fn-suffix ""}) +(hugsql/def-sqlvec-fns "sql/kvstore.sql" {:quoting :ansi :fn-suffix ""}) +(hugsql/def-sqlvec-fns "sql/workers.sql" {:quoting :ansi :fn-suffix ""}) + diff --git a/backend/src/uxbox/util/blob.clj b/backend/src/uxbox/util/blob.clj new file mode 100644 index 000000000..e1b96eff8 --- /dev/null +++ b/backend/src/uxbox/util/blob.clj @@ -0,0 +1,21 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.blob + "A generic blob storage encoding. Mainly used for + page data, page options and txlog payload storage." + (:require [uxbox.util.snappy :as snappy])) + +(defn encode + "Encode data into compressed blob." + [data] + (snappy/compress data)) + +(defn decode + "Decode blob into string." + [^bytes data] + (snappy/uncompress data)) + diff --git a/backend/src/uxbox/util/cli.clj b/backend/src/uxbox/util/cli.clj new file mode 100644 index 000000000..43289e51f --- /dev/null +++ b/backend/src/uxbox/util/cli.clj @@ -0,0 +1,12 @@ +(ns uxbox.util.cli + "Command line interface helpers.") + +(defn exit! + ([] (exit! 0)) + ([code] + (System/exit code))) + +(defmacro print-err! + [& args] + `(binding [*out* *err*] + (println ~@args))) diff --git a/backend/src/uxbox/util/closeable.clj b/backend/src/uxbox/util/closeable.clj new file mode 100644 index 000000000..58d52a3c5 --- /dev/null +++ b/backend/src/uxbox/util/closeable.clj @@ -0,0 +1,31 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.closeable + "A closeable abstraction. A drop in replacement for + clojure builtin `with-open` syntax abstraction." + (:refer-clojure :exclude [with-open])) + +(defprotocol ICloseable + (-close [_] "Close the resource.")) + +(defmacro with-open + [bindings & body] + {:pre [(vector? bindings) + (even? (count bindings)) + (pos? (count bindings))]} + (reduce (fn [acc bindings] + `(let ~(vec bindings) + (try + ~acc + (finally + (-close ~(first bindings)))))) + `(do ~@body) + (reverse (partition 2 bindings)))) + +(extend-protocol ICloseable + java.lang.AutoCloseable + (-close [this] (.close this))) diff --git a/backend/src/uxbox/util/data.clj b/backend/src/uxbox/util/data.clj new file mode 100644 index 000000000..88cf1785d --- /dev/null +++ b/backend/src/uxbox/util/data.clj @@ -0,0 +1,52 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.data + "Data transformations utils." + (:require [clojure.walk :as walk] + [cuerdas.core :as str])) + +(defn dissoc-in + [m [k & ks :as keys]] + (if ks + (if-let [nextmap (get m k)] + (let [newmap (dissoc-in nextmap ks)] + (if (seq newmap) + (assoc m k newmap) + (dissoc m k))) + m) + (dissoc m k))) + +(defn normalize-attrs + "Recursively transforms all map keys from strings to keywords." + [m] + (letfn [(tf [[k v]] + (let [ks (-> (name k) + (str/replace "_" "-"))] + [(keyword ks) v])) + (walker [x] + (if (map? x) + (into {} (map tf) x) + x))] + (walk/postwalk walker m))) + +(defn strip-delete-attrs + [m] + (dissoc m :deleted-at)) + +(defn normalize + "Perform a common normalization transformation + for a entity (database retrieved) data structure." + [m] + (-> m normalize-attrs strip-delete-attrs)) + +(defn deep-merge + [& maps] + (letfn [(merge' [& maps] + (if (every? map? maps) + (apply merge-with merge' maps) + (last maps)))] + (apply merge' (remove nil? maps)))) diff --git a/backend/src/uxbox/util/exceptions.clj b/backend/src/uxbox/util/exceptions.clj new file mode 100644 index 000000000..6d22b9b36 --- /dev/null +++ b/backend/src/uxbox/util/exceptions.clj @@ -0,0 +1,21 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.exceptions + "A helpers for work with exceptions.") + +(defn error + [& {:keys [type code message] :or {type :unexpected} :as payload}] + {:pre [(keyword? type) (keyword? code)]} + (let [message (if message + (str message " / " (pr-str code) "") + (pr-str code)) + payload (assoc payload :type type)] + (ex-info message payload))) + +(defmacro raise + [& args] + `(throw (error ~@args))) diff --git a/backend/src/uxbox/util/images.clj b/backend/src/uxbox/util/images.clj new file mode 100644 index 000000000..dffc0a9be --- /dev/null +++ b/backend/src/uxbox/util/images.clj @@ -0,0 +1,44 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.images + "Images transformation utils." + (:require [clojure.java.io :as io]) + (:import org.im4java.core.IMOperation + org.im4java.core.ConvertCmd + org.im4java.process.Pipe + java.io.ByteArrayInputStream + java.io.ByteArrayOutputStream)) + +;; Related info on how thumbnails generation +;; http://www.imagemagick.org/Usage/thumbnails/ + +(defn thumbnail + ([input] (thumbnail input nil)) + ([input {:keys [size quality format] + :or {format "jpg" + quality 92 + size [200 200]} + :as opts}] + {:pre [(vector? size)]} + (with-open [out (ByteArrayOutputStream.) + in (io/input-stream input)] + (let [[width height] size + pipe (Pipe. in out) + op (doto (IMOperation.) + (.addRawArgs ^java.util.List ["-"]) + (.autoOrient) + ;; (.thumbnail (int width) (int height) "^") + ;; (.gravity "center") + ;; (.extent (int width) (int height)) + (.resize (int width) (int height) "^") + (.quality (double quality)) + (.addRawArgs ^java.util.List [(str format ":-")])) + cmd (doto (ConvertCmd.) + (.setInputProvider pipe) + (.setOutputConsumer pipe))] + (.run cmd op (make-array Object 0)) + (ByteArrayInputStream. (.toByteArray out)))))) diff --git a/backend/src/uxbox/util/quartz.clj b/backend/src/uxbox/util/quartz.clj new file mode 100644 index 000000000..e74cca37a --- /dev/null +++ b/backend/src/uxbox/util/quartz.clj @@ -0,0 +1,139 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.quartz + "A lightweight abstraction layer for quartz job scheduling library." + (:import java.util.Properties + org.quartz.Scheduler + org.quartz.SchedulerException + org.quartz.impl.StdSchedulerFactory + org.quartz.Job + org.quartz.JobBuilder + org.quartz.JobDataMap + org.quartz.JobExecutionContext + org.quartz.TriggerBuilder + org.quartz.CronScheduleBuilder + org.quartz.SimpleScheduleBuilder + org.quartz.PersistJobDataAfterExecution + org.quartz.DisallowConcurrentExecution)) + +;; --- Implementation + +(defn- map->props + [data] + (let [p (Properties.)] + (run! (fn [[k v]] (.setProperty p (name k) (str v))) (seq data)) + p)) + +(deftype JobImpl [] + Job + (execute [_ context] + (let [^JobDataMap data (.. context getJobDetail getJobDataMap) + args (.get data "arguments") + state (.get data "state") + callable (.get data "callable")] + (if state + (apply callable state args) + (apply callable args))))) + +(defn- resolve-var + [sym] + (let [ns (symbol (namespace sym)) + func (symbol (name sym))] + (require ns) + (resolve func))) + +(defn- build-trigger + [opts] + (let [repeat? (::repeat? opts true) + interval (::interval opts 1000) + cron (::cron opts) + group (::group opts "uxbox") + schdl (if cron + (CronScheduleBuilder/cronSchedule cron) + (let [schdl (SimpleScheduleBuilder/simpleSchedule) + schdl (if (number? repeat?) + (.withRepeatCount schdl repeat?) + (.repeatForever schdl))] + (.withIntervalInMilliseconds schdl interval))) + name (str (:name opts) "-trigger") + bldr (doto (TriggerBuilder/newTrigger) + (.startNow) + (.withIdentity name group) + (.withSchedule schdl))] + (.build bldr))) + +(defn- build-job-detail + [fvar args] + (let [opts (meta fvar) + state (::state opts) + group (::group opts "uxbox") + name (str (:name opts)) + data {"callable" @fvar + "arguments" (into [] args) + "state" (if state (atom state) nil)} + bldr (doto (JobBuilder/newJob JobImpl) + (.storeDurably false) + (.usingJobData (JobDataMap. data)) + (.withIdentity name group))] + (.build bldr))) + +(defn- make-scheduler-props + [{:keys [name daemon? threads thread-priority] + :or {name "uxbox-scheduler" + daemon? true + threads 1 + thread-priority Thread/MIN_PRIORITY}}] + (map->props + {"org.quartz.threadPool.threadCount" threads + "org.quartz.threadPool.threadPriority" thread-priority + "org.quartz.threadPool.makeThreadsDaemons" (if daemon? "true" "false") + "org.quartz.scheduler.instanceName" name + "org.quartz.scheduler.makeSchedulerThreadDaemon" (if daemon? "true" "false")})) + +;; --- Public Api + +(defn scheduler + "Create a new scheduler instance." + ([] (scheduler nil)) + ([opts] + (let [props (make-scheduler-props opts) + factory (StdSchedulerFactory. props)] + (.getScheduler factory)))) + +(declare schedule!) + +(defn start! + ([schd] + (start! schd nil)) + ([schd {:keys [delay search-on]}] + ;; Start the scheduler + (if (number? delay) + (.startDelayed schd (int delay)) + (.start schd)) + + (when (coll? search-on) + (run! (fn [ns] + (require ns) + (doseq [v (vals (ns-publics ns))] + (when (::job (meta v)) + (schedule! schd v)))) + search-on)) + schd)) + +(defn stop! + [scheduler] + (.shutdown ^Scheduler scheduler true)) + +;; TODO: add proper handling of `:delay` option that should allow +;; execute a task firstly delayed until some milliseconds or at certain time. + +(defn schedule! + [schd f & args] + (let [vf (if (symbol? f) (resolve-var f) f) + job (build-job-detail vf args) + trigger (build-trigger (meta vf))] + (.scheduleJob ^Scheduler schd job trigger))) diff --git a/backend/src/uxbox/util/response.clj b/backend/src/uxbox/util/response.clj new file mode 100644 index 000000000..086b78f70 --- /dev/null +++ b/backend/src/uxbox/util/response.clj @@ -0,0 +1,66 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.response + "A lightweigt reponse type definition. + + At first instance it allows set the appropriate + content-type headers and encode the body using + the builtin transit abstraction. + + In future it will allow easy adapt for the content + negotiation that is coming to catacumba." + (:require [catacumba.impl.handlers :as ch] + [catacumba.impl.context :as ctx] + [buddy.core.hash :as hash] + [buddy.core.codecs :as codecs] + [buddy.core.codecs.base64 :as b64] + [uxbox.util.transit :as t]) + (:import ratpack.handling.Context + ratpack.http.Response + ratpack.http.Request + ratpack.http.Headers + ratpack.http.MutableHeaders)) + +(defn digest + [^bytes data] + (-> (hash/blake2b-256 data) + (b64/encode true) + (codecs/bytes->str))) + +(defn- etag-match? + [^Request request ^String new-tag] + (let [^Headers headers (.getHeaders request)] + (when-let [etag (.get headers "if-none-match")] + (= etag new-tag)))) + +(deftype Rsp [data] + ch/ISend + (-send [_ ctx] + (let [^Response response (ctx/get-response* ctx) + ^Request request (ctx/get-request* ctx) + ^MutableHeaders headers (.getHeaders response) + ^String method (.. request getMethod getName toLowerCase) + data (t/encode data)] + (if (= method "get") + (let [etag (digest data)] + (if (etag-match? request etag) + (do + (.set headers "etag" etag) + (.status response 304) + (.send response)) + (do + (.set headers "content-type" "application/transit+json") + (.set headers "etag" etag) + (ch/-send data ctx)))) + (do + (.set headers "content-type" "application/transit+json") + (ch/-send data ctx)))))) + +(defn rsp + "A shortcut for create a response instance." + [data] + (Rsp. data)) diff --git a/backend/src/uxbox/util/snappy.clj b/backend/src/uxbox/util/snappy.clj new file mode 100644 index 000000000..731e06c96 --- /dev/null +++ b/backend/src/uxbox/util/snappy.clj @@ -0,0 +1,40 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.snappy + "A lightweight abstraction layer for snappy compression library." + (:require [buddy.core.codecs :as codecs]) + (:import org.xerial.snappy.Snappy + org.xerial.snappy.SnappyFramedInputStream + org.xerial.snappy.SnappyFramedOutputStream + + java.io.OutputStream + java.io.InputStream)) + + +(defn compress + "Compress data unsing snappy compression algorithm." + [data] + (-> (codecs/to-bytes data) + (Snappy/compress))) + +(defn uncompress + "Uncompress data using snappy compression algorithm." + [data] + (-> (codecs/to-bytes data) + (Snappy/uncompress))) + +(defn input-stream + "Create a Snappy framed input stream." + [^InputStream istream] + (SnappyFramedInputStream. istream)) + +(defn output-stream + "Create a Snappy framed output stream." + ([ostream] + (output-stream ostream nil)) + ([^OutputStream ostream {:keys [block-size] :or {block-size 65536}}] + (SnappyFramedOutputStream. ostream (int block-size) 1.0))) diff --git a/backend/src/uxbox/util/spec.clj b/backend/src/uxbox/util/spec.clj new file mode 100644 index 000000000..5acecd424 --- /dev/null +++ b/backend/src/uxbox/util/spec.clj @@ -0,0 +1,113 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.spec + (:refer-clojure :exclude [keyword uuid vector boolean map set]) + (:require [clojure.spec :as s] + [cuerdas.core :as str] + [uxbox.util.exceptions :as ex]) + (:import java.time.Instant)) + +;; --- Constants + +(def email-rx + #"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$") + +(def uuid-rx + #"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$") + +;; --- Public Api + +(defn conform + [spec data] + (let [result (s/conform spec data)] + (if (= result ::s/invalid) + (ex/raise :type :validation + :code ::invalid + :message (s/explain-str spec data) + :context (s/explain-data spec data)) + result))) + +;; --- Predicates + +(defn email? + [v] + (and string? + (re-matches email-rx v))) + +(defn instant? + [v] + (instance? Instant v)) + +(defn path? + [v] + (instance? java.nio.file.Path v)) + +(defn regex? + [v] + (instance? java.util.regex.Pattern v)) + +;; --- Conformers + +(defn- uuid-conformer + [v] + (cond + (uuid? v) v + (string? v) + (cond + (re-matches uuid-rx v) + (java.util.UUID/fromString v) + + (str/empty? v) + nil + + :else + ::s/invalid) + :else ::s/invalid)) + +(defn- integer-conformer + [v] + (cond + (integer? v) v + (string? v) + (if (re-matches #"^[-+]?\d+$" v) + (Long/parseLong v) + ::s/invalid) + :else ::s/invalid)) + +(defn boolean-conformer + [v] + (cond + (boolean? v) v + (string? v) + (if (re-matches #"^(?:t|true|false|f|0|1)$" v) + (contains? #{"t" "true" "1"} v) + ::s/invalid) + :else ::s/invalid)) + +(defn boolean-unformer + [v] + (if v "true" "false")) + +;; --- Default Specs + +(s/def ::integer-string (s/conformer integer-conformer str)) +(s/def ::uuid-string (s/conformer uuid-conformer str)) +(s/def ::boolean-string (s/conformer boolean-conformer boolean-unformer)) +(s/def ::positive-integer #(< 0 % Long/MAX_VALUE)) +(s/def ::uploaded-file #(instance? ratpack.form.UploadedFile %)) +(s/def ::uuid uuid?) +(s/def ::bytes bytes?) +(s/def ::path path?) + +(s/def ::id ::uuid-string) +(s/def ::name string?) +(s/def ::username string?) +(s/def ::password string?) +(s/def ::version integer?) +(s/def ::email email?) +(s/def ::token string?) + diff --git a/backend/src/uxbox/util/tempfile.clj b/backend/src/uxbox/util/tempfile.clj new file mode 100644 index 000000000..273c72212 --- /dev/null +++ b/backend/src/uxbox/util/tempfile.clj @@ -0,0 +1,17 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.tempfile + "A temporal file abstractions." + (:require [storages.core :as st] + [storages.util :as path]) + (:import [java.nio.file Files])) + +(defn create + "Create a temporal file." + [& {:keys [suffix prefix]}] + (->> (path/make-file-attrs "rwxr-xr-x") + (Files/createTempFile prefix suffix))) diff --git a/backend/src/uxbox/util/template.clj b/backend/src/uxbox/util/template.clj new file mode 100644 index 000000000..8579f4163 --- /dev/null +++ b/backend/src/uxbox/util/template.clj @@ -0,0 +1,56 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.template + "A lightweight abstraction over mustache.java template engine. + The documentation can be found: http://mustache.github.io/mustache.5.html" + (:require [clojure.walk :as walk] + [clojure.java.io :as io]) + (:import java.io.StringReader + java.io.StringWriter + java.util.HashMap + com.github.mustachejava.DefaultMustacheFactory + com.github.mustachejava.Mustache)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Impl +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^:private + ^DefaultMustacheFactory + +mustache-factory+ (DefaultMustacheFactory.)) + +(defprotocol ITemplate + "A basic template rendering abstraction." + (-render [template context])) + +(extend-type Mustache + ITemplate + (-render [template context] + (with-out-str + (let [scope (HashMap. (walk/stringify-keys context))] + (.execute template *out* scope))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Public Api +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn render-string + "Render string as mustache template." + ([^String template] + (render-string template {})) + ([^String template context] + (let [reader (StringReader. template) + template (.compile +mustache-factory+ reader "example")] + (-render template context)))) + +(defn render + "Load a file from the class path and render + it using mustache template." + ([^String path] + (render path {})) + ([^String path context] + (render-string (slurp (io/resource path)) context))) diff --git a/backend/src/uxbox/util/time.clj b/backend/src/uxbox/util/time.clj new file mode 100644 index 000000000..2be5ca7eb --- /dev/null +++ b/backend/src/uxbox/util/time.clj @@ -0,0 +1,76 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.time + (:require [suricatta.proto :as proto] + [cognitect.transit :as t]) + (:import java.time.Instant + java.sql.Timestamp)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Serialization Layer conversions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(declare from-string) + +(def ^:private write-handler + (t/write-handler + (constantly "m") + (fn [v] (str (.toEpochMilli v))))) + +(def ^:private read-handler + (t/read-handler + (fn [v] (-> (Long/parseLong v) + (Instant/ofEpochMilli))))) + +(def +read-handlers+ + {"m" read-handler}) + +(def +write-handlers+ + {Instant write-handler}) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Persistence Layer Conversions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(extend-protocol proto/IParamType + Instant + (-render [self ctx] + (if (proto/-inline? ctx) + (str "'" (.toString self) "'::timestamptz") + "?::timestamptz")) + + (-bind [self ctx] + (when-not (proto/-inline? ctx) + (let [stmt (proto/-statement ctx) + idx (proto/-next-bind-index ctx) + obj (Timestamp/from self)] + (.setTimestamp stmt idx obj))))) + +(extend-protocol proto/ISQLType + Timestamp + (-convert [self] + (.toInstant self))) + +(defmethod print-method Instant + [mv ^java.io.Writer writer] + (.write writer (str "#instant \"" (.toString mv) "\""))) + +(defmethod print-dup Instant [o w] + (print-method o w)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Helpers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn from-string + [s] + {:pre [(string? s)]} + (Instant/parse s)) + +(defn now + [] + (Instant/now)) diff --git a/backend/src/uxbox/util/token.clj b/backend/src/uxbox/util/token.clj new file mode 100644 index 000000000..605c9af1f --- /dev/null +++ b/backend/src/uxbox/util/token.clj @@ -0,0 +1,24 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.token + "Facilities for generate random tokens." + (:require [buddy.core.nonce :as nonce] + [buddy.core.hash :as hash] + [buddy.core.codecs :as codecs] + [buddy.core.codecs.base64 :as b64])) + +(defn random + "Returns a 32 bytes randomly generated token + with 1024 random seed. The output is encoded + using urlsafe variant of base64." + [] + (-> (nonce/random-bytes 1024) + (hash/blake2b-256) + (b64/encode true) + (codecs/bytes->str))) + + diff --git a/backend/src/uxbox/util/transit.clj b/backend/src/uxbox/util/transit.clj new file mode 100644 index 000000000..b8b0a0e26 --- /dev/null +++ b/backend/src/uxbox/util/transit.clj @@ -0,0 +1,71 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.transit + (:require [cognitect.transit :as t] + [catacumba.handlers.parse :as cparse] + [uxbox.util.time :as dt]) + (:import ratpack.http.TypedData + ratpack.handling.Context + java.io.ByteArrayInputStream + java.io.ByteArrayOutputStream)) + +;; --- Handlers + +(def ^:private +reader-handlers+ + dt/+read-handlers+) + +(def ^:private +write-handlers+ + dt/+write-handlers+) + +;; --- Low-Level Api + +(defn reader + ([istream] + (reader istream nil)) + ([istream {:keys [type] :or {type :json}}] + (t/reader istream type {:handlers +reader-handlers+}))) + +(defn read! + "Read value from streamed transit reader." + [reader] + (t/read reader)) + +(defn writer + ([ostream] + (writer ostream nil)) + ([ostream {:keys [type] :or {type :json}}] + (t/writer ostream type {:handlers +write-handlers+}))) + +(defn write! + [writer data] + (t/write writer data)) + + +;; --- Catacumba Extension + +(defmethod cparse/parse-body :application/transit+json + [^Context ctx ^TypedData body] + (let [reader (reader (.getInputStream body) {:type :json})] + (read! reader))) + +;; --- High-Level Api + +(defn decode + ([data] + (decode data nil)) + ([data opts] + (with-open [input (ByteArrayInputStream. data)] + (read! (reader input opts))))) + +(defn encode + ([data] + (encode data nil)) + ([data opts] + (with-open [out (ByteArrayOutputStream.)] + (let [w (writer out opts)] + (write! w data) + (.toByteArray out))))) diff --git a/backend/src/uxbox/util/uuid.clj b/backend/src/uxbox/util/uuid.clj new file mode 100644 index 000000000..8fe024ce1 --- /dev/null +++ b/backend/src/uxbox/util/uuid.clj @@ -0,0 +1,25 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.uuid + (:require [clj-uuid :as uuid]) + (:import java.util.UUID)) + +(def ^:const zero uuid/+null+) + +(def random + "Alias for clj-uuid/v4." + uuid/v4) + +(defn namespaced + [ns data] + (uuid/v5 ns data)) + +(defn from-string + "Parse string uuid representation into proper UUID instance." + [s] + (UUID/fromString s)) + diff --git a/backend/src/uxbox/util/workers.clj b/backend/src/uxbox/util/workers.clj new file mode 100644 index 000000000..fac845b84 --- /dev/null +++ b/backend/src/uxbox/util/workers.clj @@ -0,0 +1,74 @@ +;; 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) 2016 Andrey Antukh + +(ns uxbox.util.workers + "A distributed asynchronous tasks queue implementation on top + of PostgreSQL reliable advirsory locking mechanism." + (:require [suricatta.core :as sc] + [uxbox.db :as db] + [uxbox.sql :as sql])) + +(defn- poll-for-task + [conn queue] + (let [sql (sql/acquire-task {:queue queue})] + (sc/fetch-one conn sql))) + +(defn- mark-task-done + [conn {:keys [id]}] + (let [sql (sql/mark-task-done {:id id})] + (sc/execute conn sql))) + +(defn- mark-task-failed + [conn {:keys [id]} error] + (let [sql (sql/mark-task-done {:id id :error (.getMessage error)})] + (sc/execute conn sql))) + +(defn- watch-unit + [conn queue callback] + (let [task (poll-for-task conn queue)] + (if (nil? task) + (Thread/sleep 1000) + (try + (sc/atomic conn + (callback conn task) + (mark-task-done conn task)) + (catch Exception e + (mark-task-failed conn task e)))))) + +(defn- watch-loop + "Watch tasks on the specified queue and executes a + callback for each task is received. + NOTE: This function blocks the current thread." + [queue callback] + (try + (loop [] + (with-open [conn (db/connection)] + (sc/atomic conn (watch-unit conn queue callback))) + (recur)) + (catch InterruptedException e + ;; just ignoring + ))) + +(defn watch! + [queue callback] + (let [runnable #(watch-loop queue callback) + thread (Thread. ^Runnable runnable)] + (.setDaemon thread true) + (.start thread) + (reify + java.lang.AutoCloseable + (close [_] + (.interrupt thread) + (.join thread 2000)) + + clojure.lang.IDeref + (deref [_] + (.join thread)) + + clojure.lang.IBlockingDeref + (deref [_ ms default] + (.join thread ms) + default)))) diff --git a/backend/test/storages/tests.clj b/backend/test/storages/tests.clj new file mode 100644 index 000000000..c29e26eb0 --- /dev/null +++ b/backend/test/storages/tests.clj @@ -0,0 +1,106 @@ +(ns storages.tests + (:require [clojure.test :as t] + [storages.core :as st] + [storages.fs.local :as fs] + [storages.fs.misc :as misc]) + (:import java.io.File + org.apache.commons.io.FileUtils)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Test Fixtures +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- clean-temp-directory + [next] + (next) + (let [directory (File. "/tmp/catacumba/")] + (FileUtils/deleteDirectory directory))) + +(t/use-fixtures :each clean-temp-directory) + +;; --- Tests: FileSystemStorage + +(t/deftest test-localfs-store-and-lookup + (let [storage (fs/filesystem {:basedir "/tmp/catacumba/test" + :baseuri "http://localhost:5050/"}) + rpath @(st/save storage "test.txt" "my content") + fpath @(st/lookup storage rpath) + fdata (slurp fpath)] + (t/is (= (str fpath) "/tmp/catacumba/test/test.txt")) + (t/is (= "my content" fdata)))) + +(t/deftest test-localfs-store-and-get-public-url + (let [storage (fs/filesystem {:basedir "/tmp/catacumba/test" + :baseuri "http://localhost:5050/"}) + rpath @(st/save storage "test.txt" "my content") + ruri (st/public-url storage rpath)] + (t/is (= (str ruri) "http://localhost:5050/test.txt")))) + +(t/deftest test-localfs-store-and-lookup-with-subdirs + (let [storage (fs/filesystem {:basedir "/tmp/catacumba/test" + :baseuri "http://localhost:5050/"}) + rpath @(st/save storage "somepath/test.txt" "my content") + fpath @(st/lookup storage rpath) + fdata (slurp fpath)] + (t/is (= (str fpath) "/tmp/catacumba/test/somepath/test.txt")) + (t/is (= "my content" fdata)))) + +(t/deftest test-localfs-store-and-delete-and-check + (let [storage (fs/filesystem {:basedir "/tmp/catacumba/test" + :baseuri "http://localhost:5050/"}) + rpath @(st/save storage "test.txt" "my content")] + (t/is @(st/delete storage rpath)) + (t/is (not @(st/exists? storage rpath))))) + +(t/deftest test-localfs-store-duplicate-file-raises-exception + (let [storage (fs/filesystem {:basedir "/tmp/catacumba/test" + :baseuri "http://localhost:5050/"})] + (t/is @(st/save storage "test.txt" "my content")) + (t/is (thrown? java.util.concurrent.ExecutionException + @(st/save storage "test.txt" "my content"))))) + +(t/deftest test-localfs-access-unauthorized-path + (let [storage (fs/filesystem {:basedir "/tmp/catacumba/test" + :baseuri "http://localhost:5050/"})] + (t/is (thrown? java.util.concurrent.ExecutionException + @(st/lookup storage "../test.txt"))) + (t/is (thrown? java.util.concurrent.ExecutionException + @(st/lookup storage "/test.txt"))))) + +;; --- Tests: ScopedPathStorage + +(t/deftest test-localfs-scoped-store-and-lookup + (let [storage (fs/filesystem {:basedir "/tmp/catacumba/test" + :baseuri "http://localhost:5050/"}) + storage (misc/scoped storage "some/prefix") + rpath @(st/save storage "test.txt" "my content") + fpath @(st/lookup storage rpath) + fdata (slurp fpath)] + (t/is (= (str fpath) "/tmp/catacumba/test/some/prefix/test.txt")) + (t/is (= "my content" fdata)))) + +(t/deftest test-localfs-scoped-store-and-delete-and-check + (let [storage (fs/filesystem {:basedir "/tmp/catacumba/test" + :baseuri "http://localhost:5050/"}) + storage (misc/scoped storage "some/prefix") + rpath @(st/save storage "test.txt" "my content")] + (t/is @(st/delete storage rpath)) + (t/is (not @(st/exists? storage rpath))))) + +(t/deftest test-localfs-scoped-store-duplicate-file-raises-exception + (let [storage (fs/filesystem {:basedir "/tmp/catacumba/test" + :baseuri "http://localhost:5050/"}) + storage (misc/scoped storage "some/prefix")] + (t/is @(st/save storage "test.txt" "my content")) + (t/is (thrown? java.util.concurrent.ExecutionException + @(st/save storage "test.txt" "my content"))))) + +(t/deftest test-localfs-scoped-access-unauthorized-path + (let [storage (fs/filesystem {:basedir "/tmp/catacumba/test" + :baseuri "http://localhost:5050/"}) + storage (misc/scoped storage "some/prefix")] + (t/is (thrown? java.util.concurrent.ExecutionException + @(st/lookup storage "../test.txt"))) + (t/is (thrown? java.util.concurrent.ExecutionException + @(st/lookup storage "/test.txt"))))) + diff --git a/backend/test/uxbox/tests/_files/sample.jpg b/backend/test/uxbox/tests/_files/sample.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2339746ec9a6ea2b767aca5c7df35ab54bb968e4 GIT binary patch literal 312043 zcmeEv2|!KH_xRlRUcE}Y3Q1nkqV>HNv`Dm}5LqgUme-cU6y)lJ(9D?%hzlXag9I=F;sj^|I6;5~z>fnbj&UFi{8VtG``}D~E+3~QjF$WP&75E zh6pbxlc3)>nK|2>qNXxgg*FkXQE3ykshZlF7gurA+hm^YV9d zL1>!0JH^M<*~QP7;zReP`vlM(RVcciu7R%l_(jE@u7}2=o#8C`R)HY9`(P+4l8$D{ zhs!S42Z!RqZY3iUcvLkt8gVO2)>y!k`rtHxv283Gi~woG^Dw2z0X!j|mDgFEz7Ky1 zU~)VwFAhkTyAKuwn5U2aD$uNsCnL^mgsK4E05BI6?7?v;PCNk-fmU-OLfhEjcs3ZN z_m!DOrDEw{O-gT(+Q0W1x8Nq{+l3h(Xq0Fwb839t{q+yGMmjs=((IPkor zRb3bI0Tq6$909ywAKsgQLWlRIe`FX!qJ4N^NgoAxoELYp08_!w0h)-fG-x~E#Q`r2 zx6uyB*BD@9fD-{01UL@hBs^abCKcf1zPwT)G6j&h^qBys1D+pXcOfVrz<6Cn1AGeL z{xaNR%l8@JAsQvX1=A-YZIJ0SzyyF#$s?p7fe?p28R_dI6z_o$7vS-FuY#V)-3LDa zSWur;SG52O0sSYS`2w(TAHL@p)Cu7I051-`R!pD7EZb&)M*xiXq5}Yr1Q<{M7{H@| zUJ~H50OQ7jZ!kPltpUj4@CnY7jRWQE;)DhQT!KAsl9`!_hc| z=c|S;OE;V0A>0^(>p*769aS^KI0YBjEM|yxVI_}Kcrp=PkUPt;;+eXQO{*EZ`gjD| z`Oqng=sv!#UY-=13ROjmEy16FM~--m^B8A?Jl&WD1uDoHZ{94lF9!-9I>3jWI|qs$ zHv^*g$LTZGG(!4-V6?EXyw|Vea^a5MV2rQa=D0%dGkBf%yMDfY-me@8g#jHN?svQZ z4aC>5YP>JWUwW9BIbybG7_0U1R_@R9duBWjeJF1zA3~8(P%WqJRccDkRzn<2un&*3yP zK`Y<}$I<(IF7VmiKxTmdnR$r8O+dm&qM@7rt33Vn!QLNQFJ#x^4ws(264>#qp2Vsj zc07BH4F!+qI~W~%dDzqZ9efzvMPb1Ftwv!_?K>D+ooT;A{}B8yq1)%RVh#-&sBbp- zgAe7V^_N{i8t#j{{?Omi-vfHr8!d+(%?&C?UnG?@}0QaFGFZqSi#3I!t8$ zm_Ie}rw0Di0J8?Jz!ld*_(-n=4j)1B3GTqi-v{Gi0X96Y1_$UNfXCyG&<6vEi0m+g zDd2EEe7%U%;zj^wko_=Tj>+Jd<$>Ad0Go7&4J7p{*3F{_Uj$Xt# zjTI5RNBA%qDkDT;`NO(V2f-I><8i`#iMrTw?hZ_lOA235vMdsYR-zy=BnazXK_me? z0;6Cj0pE`h1veIrgZ%>yCt8OBfnpi5Li3O*te>YsdGsJxRfwmfm1s5c0Dn19ScBC; zQy@kKt_vU@-yK0fJpuC9g#2kxDmf_AIKblj0MpPMWQklL{{;Zefbc}%Rs{M<%$-Gj z6pSMK^0!3hP(ppA4U$X*3OV5SMj=qXulZX+tPWE~O^^jQqroXcjSL5QheMvDpe=*} z0v!NO3DTMdJ2Sda6L`L|Kp~Ds0L@q?jVf9MlFeb}stFVdP%0@%QxYif_L~FcUW|-@ ze=?K_ZzTz^Vqhhhbqpl`VS{T4e4hwcCY&I-Am~C4rq04+a6BJUC2k@_k)p}VI6rfa z!Q!B$M#H=m-*du%X9Tk0P~xZ}Q+ZT4M{ov!T-?yP@l^0OoQjeN*X-`ACg>HLBv4z` zw~f)Ajz((uhNjEFAVkWq~b85vlU zkoi(3{Wnup##n^mj+rK5C*d6S5KDu5P>0CG;E6yBE<~ZQOID5W$*?k7f@08V^aMgU zTMUy>5c;1!v}!0SW`@6B8j0*S8U_RgnSpQ1a%CxGDLs&b%KKyf)WDw__)`Oe*1$eP zeCNK3rB;~>YW7eJ#HGNfBY+Blh6E$zY_yB1rv^>z7f53mjXQop^Q&l=c^niL%_vUT zy=9=Dj9+|^4hOWA0l?fa&fzW^U>=xxkYRjW2;-v&%m#QMU&B6n9>52{Y$hMu z3o``)Y%EO8y^%f^0dp=s{?QOJAISG#1>lmyKZsa5;u!@4>CX(1u_##t81z;O!h`_X zhd#o8L|aUZj1CdDVkCk;CkFo@Gnn?rBuN;FRLW6GF6HLs=OTeICnCy^WIx3)2XH}A z;K40(=FGan9GYt+g-ehVQYD0<<-V%2vLXm8!444|i6@B`m4^#!Ft`4&+PN-aL9R{5htPmE8(A24u19fuL>xXxdf7sCbX$=lFf)8q7Ik-^X zV&NIUlnD34Z0MB>VHR!!F?5LYfIjPwf}poYGcg`;f6V~=448eJ!HjnvP+Os;FlWc@ z3?>AtBlsUM0M-IwY?zh*5XJ?{;Ry5puQ~LmX9?x<2D~Yhy1&f+P)gjIGjT4+txpaX z7Edb(Vp*oYljGHnFS@@PyczJ=U->uoM1=5eM$y95_CJhd>#e5 zl^g8${?~N>r2l`fe(>E1ynRRawX4Jr{y)~ff71W!`|Dp_KY#AuzpnrPeEqPGT>T>* zu9tBd<P1exnTd~U%B;{tB+`31`g!w4bFoM*89N*xX( z#c;Ua`|Yp7BCyV6x2#h9|6lvd-S4wX{q@>k?tY(D>i_@iR@mX#3eO9$_GU-%;5)Q^ zFmt!If2Wqs7wX%&Wen`xeyQt)o!s9F3zF6{HejnzI$Dn1{Boi9cfBTfM9RQN}l8$z1UJLJKagMV5L05=)_ zMb?M^ZDqdU)Cbz%<7MT}x|+Z)SP1MOIYW8Yz@Cu_gnZ$jaVPNUu)`GwxBz4au|CKF zu7)L!<{S-1QJ#*RSLj<;J8)h}^xfhuaGA9~er-+8YN5o}VdZOu;@TYR_94{f*prJ8 zrDuT&Je7QZsVO{FQROe`pb`3p+e(vy>({QF-JnUp@wS6j1t&*UFGn)npLG7Ze(-t>-NH9s9TgJIk(O{JObdF1 z5-|mIUg#7y6*)&?7=AtpO`V@g!;5d+asp;Is(s#Q3m(^uMhCeE$1k z9#Ke~ghH?lAiofH2pfkAq}#CRC_y?6b3p|4B9Q+f;R3A*9!abr*$!2wwb$3y#P36)}MNK?81 zb4IJwZeYpCjrJV7gocrZL;F`DSC0MEH`q&L0RP4I!hH}ba1Jwq_IHA3-mHLM7t6!o zi7M$FEEQ?16=2uV6>2l~1~n14P&={rD4xTJ`U(4jGB}l}pD_aVC;$I&{ln9Ey@WfQ z2vrM3kUh}`sq5rjXa}vHT!^G;AIT4p8Hq&eA~(YU!1q)Jxf68e&pC$oaa^Hxk(&?~ zM=JdL-u{;yv_1|UngDu@o|AUaCZUi2CJuklSuG&>d7)I0yiDpmNPdv^f?SM7(mKdb z&~j1@wVT|EL^w)7jxH$GuQ^V^a|f-+gu|2C^9v5?zC1?KK9k!}C&`v3k9z(xkAEIV z0LmcKpPqXpaNbcBUM^<~T)d1dh)n z4{GNSIT|7Dcjz>!2YL%$yT8ukALrPBEC|a%@>b~W-sp?qGm!iXt&)5N@zb7@8&NH> zgxUxNGrJ{hY&>S`Qr6!0{0{4w9s)Uw*;SpT}H~xEr~XrcrzUZ5)p1Ds~?v zkHU_TEzmll)8r$loK{GFj+Vl6Qr)PLIF9*z)H9M8=yv>g*ibm|wthh3p~BhjA#uP+ z4;<+v@ge8&&*1RH#zW~2U?<44LBsDRA47q(A{fWippAP$@=?%opV1ML;D8)I4kdLG11SQ%B@+~xi#7kq4>tQ!^s2pOXD{$8imE$ur4+EMgY8YZ3|0u_F z)I-Pw$>T8@vMGuZ%7gh#yVL=YyqornoP+8K*0ct4HNr^k(EA&qRDZ?sg2`b*dkGvo zq-)UoU;TokKaWP>7)Ii!38SWe6UPLULeKyWCr;oZQ_&Hj43HcNJs>9_ic~%MGkMBpJf&b!Z&T)Fx&gz99L(k0T8;0m-jpSs?imER*bn zJYgoc7g@nty%I*XL$FqBL$gRTV2tWvNK;M>*6vB>GM$u%A`nKm@1@*4a=Bq5urrZGgiuQ<=6Oa{|sVBulYQGAv-8XZY7) z+EA1!11m<(#{Jj-KTQ9Li?(sN#ozjdpSxgP?NJ)Y8Vmaa81qyfF83FZoOKRE@;hG| zaf~r>eiJQ&I3;Jeyor`aWGPd(mS{!9C2Qf!h*tioKbAJrCT5``>i5Uc%$D!;S3;6% z<}ODzfIo48cUF=T8mnc!_WWTbB%x*zaxPZmr+$C_P~3l#VNA!*oIql5<^*a1uI6NO z4sVh-{HqS{?t}j^`Qha2P+&nedg-6QN^JP4Y!a};TtLGE`EoB|m7L2!0f4^df`PuNoKY{W3?B`Wr zgH_mI_Pp3(UL*)@L4xaasOzsV{`^Njek2=A>4W*;Uw&BD4j6Wrz2E)}#?SqTFk99T zN;x>p3#pF>UBm-gm>tG7G5*98C*av(aW*{u_J@8Nc6cc5`!gCzC`o_2v%~D|KADY% z9me~x2=puAzWeugV7w3a)3d{Pf9}WQeR44PuTH%Eb=))T6q*lj{uo6jz>^iW@YD$f zj)D*8z~H>m&*QkDUl_x?20T$XoM1WvE&Ofx`_pfK!rz_-%!hw@ImL8|(0!Ta|5~`e z-2ZOB@027w{x%gf18au%y^p{9;eXZkp9THz@ns1_q9^eX;a}t9&pG0=1Kh{YzVcAM z>v7EUu#(?n2{^6ppjfsSaI%tOrtptpzbg|2VKzO;iQMmSoSgb__VHUfE-va1I9^`L z4>%Ei^aD;@_($CMkw4<}6ad$O`EhiR{f7Ao@Qd(s@i1}Um4!fnQ^eolNF@CqaGab! zmYIu-@&lb9&yTplOsMEIBgz$+?av4 zw_OKsFD}A%5u}LW#KCZ(Lp)nP0~ycPm|^F*6^+3zVetfMVgzw89Hh5QRY5`F3%ZQ0 zC+HIsh(*M~aQb~`)wlMYRsTLrfV1oUvq5ioufP$Q>5#|*@Pf!7c(1@}SZEVS{4iT0 z4e^ZoKbwwYi1`kcj%$eN@eGkpV2E^6he$^dCc%mhKa*q#^VMhAA$b22nUjx9g!Zt- zJfU^SoID)(I|uM{6dW)UKk}C|*`d<$3^BdG8y)+-&JMG`j{*J%|K>doe?t9lv>19# z{3}KSdG@~ zAFMu=zDXZKZ0Y~E(GS+Xe^oz9&=+vWUW0#KKfgymSb6>`{on0BgY{2KSet!q(Qo_1 zVCgp!+39z|&Ht5qu>Sd&gPk6@+2{xQ8{0d4O1X(_p$K@w;*a^O8X&-%i++qJ4*EW$ zL9wtV8RXqZ)IqSEoRmSZJmkT!eB5XdTKLGyAlR{@Ozd|nw?6E5>m}xTbCB}t50Wm4 zG?)}55@irtE{?&lJY;6czAJ+O&tTXw_*ZBc0z;C5M1qy&AW{s5<>D9&%R^p1ND2Zx z=7V6z4A&pDWK8ULvJEcrPh0cHp7-B^3H|OJUGR+Q5AW>iMLZvH=ZIWkm)aLr2Ww!j zdLq-R!#iiGV3h{!e+rMk^6$UZ-awn+)FsQZm+{|;XZ1Mf;b2%W=)?gS|0YpCp7lMC z{vOK0!&ivbv8J>7`Y_fa_-o^j`DG2@d##T<%9`3fkX}vn$(L7mYR#8ew=mYNaP`=c z6<*Kza?~0)pE}I7ni6JNotvmE6E%kOaoz*PL(ZO0ci(+q8K@=GrL#R$UUsoQ_R94H zi5TR!t^TmKm|I2iZm!2OQzEw*yi_iBTGSDjSC%0yCzN?6du|Z_I#FlN-N~u5zT7C% zi$C+lm8P*Vn3v#pQbF=!wVUdy8@C= zUp^ZN>Ccsk@v^vGYwa!IHm&R72>;kty6=QBpUzg;+>1Fydo1%=T5N^4^Xtt03Wl8} zLAPbZx<0?XvSIU+J=HZ$%iAS>=JJ z?a6Voq@PS`^?Kf{*?VZe=1anixvF=p=|=MdcNU~d+4QzZCbboiaC&$yr~*fQo| zo?V7$lGboVugf0f>{DY`&U#&=v%|f9CD$c}=87#N6zzQzd)D(SJc+J(m=#q+yrR4F zLZsRIP??5DFB4LoMWWX16H#kyntwNY?=g*o0rvJ^?zP{zy^XqUHes zo@}Sj`ZMOuwM`FRZ&UZ;#FTtd9w8m0lkc7%5lay=d#jB4G58L0k>4U4W ziqgmmqxUbW(>EU8Xk~DJtxW61&k7IQKUi~TiPBmY+?gsNB0choDosPa{DUoFVUm-V zZK~#Wvm+D7Bvoi8lwIA@Y0Syp6PfTKM$zz~PS%DU=nQwk$oJ0DH@cGcGj?mAUn-Qr z$#HG<`w?;%0#-M8rN7La$d<_25VnQukYZe;vf#~Chlr-C4VUz%4WlLQxt%<}dwi<36{kzx%bBl?MQhVB zrMOkomdzYe$(g0PKK=_lj&E9P!<(v6Vhe6)eMa`>AHJ(o}7 zGxNA>6AIy-!s~+9V z!IP-G1=BwFTISX;`}J9Ar#uOFuUop!UT%(EZeB&)<*?Izs#kY<(Aub*rD6&@MZHE? zIQbn2Gu;r{#39v`#^sW;PeRmzAsKnbS-DL8Zsd-&F1jmEo?Por5ZHRGe9rTgW9B(; zd{r*gol~D4%sG#tZF~IbsQ3;8!-oqsrLqsHOnx?RQrkKGy9e*{bV#|{JHNMZax(Ln z>!56XF3zs5y9Hf*8n9^BZiilU*fyM}$ZyZ6b!X?zP;oqX@w)QXNoAfF5#KDk)r-r_ z+`@9Ke0Lj#EO_y0sR_;S%X=Qt*$J*E@6hTbD!n!RrYVeb%)`F`Qza}P64F}Tdb*ET%O_FuU=Zs%ne zr^(|Yubx+49$lR8yJq+&?ShY%`j&wc8AV!;lALpEZqct7yI7V*$gf&rGXK1I+M{9a zH)Nx4oaWkIUO(#T=>qu~7gta!40;w!S-&CjW#jPS{st#vU6y*P2AC0#dURQexW2gj z(=s>eg76Nmh*=*^-sq*FF#hLr-k4?`(`e5PmN$|)xw^kih7%Jt&u#p zHfQZT=i08x9UE^JeLixq?drH?_Omsc!xmIpZ4P%c3VJZNt17{NZh?8}{OMvLpR$Dm z1I`6^y2D3Lo~)RsG4X@8kUnisk#V=$-tC#v9{d5D-$t(pIP=$f@$o^Yl|IMw{KAjs38lr_>r{INZo2t=*`gQO#V_n#xi%b_(i?KB zmLqRf(N60d8Eac<$CBnGUbvmtbDsKs?$KEXE*G|CP@`L(hfJK_^NB`vp0d(sM@jX( z+L)&gVi?ofHocy6_U7$do{XhD>LNCaWFPCi_^?F9eZ#{B`%ihf`|mv%v#;#N{+9Ez zLkJRG_g%Q(E2{IHZvDU;&WVY}-l?#8TC(P0i6|*F-*qi)9}tJ-y01%3L%wXh&+d>K9JhT=NY} z_6<{f8^b9+Keb?q@kLMPcB3bzshh-aK2qo)t-X_aJZt0LCBrVf-L=|CaEF^ep{Y>& zNJHDF+fLU>Rv~3cHA2^<^E^}b>ZF@)H<^6I*kLrWfvQov{!Oj?NNmB?k$V*)s}1xg z$+hsgyRWkewk=3|UF~!&RIYbbU{RgW4cQ%6=P%;8XZGrBsnf)b28ZYB@t$^{zC$iI zQnyU%%<{!nXWHJFs-~^l>~B#wzB0%pa>v|F6VUaQM6!cgshUM>PI*fHZQ>s_zrT3h^O96)%IgQDN{i;UL|gf+cUoM?DLy0m{3UwFX|DiFDVx`%^Y_!^ z%TM!ef3|9zcl84sJEdnM=Wh2(Ysk1iGr@h+i7y8v4QGlyQ?enw zvN6aW_Iy%D+JdM9cGglMQM=?H1QwgzF!$O<-EgE_aE(Z3YKNGqP|K09iSu~mm7616 zYV73hop-SaI?vN;9vC)bhAB_&m}~x{3lg-qah2R2sjXo>-Q$|n=kgbfQqhX{dJnkd zXuVNAms^&(YrK@(@0RE+cCA$1XxOe8UqQjz?sCo39cDR=yyqX@eG$69EYiW($C5XC zo{jtIozq++7oT{fe=g$C#I0qiveqYKtF-G#WSd54uIy*hExv*JOv|H8}T z7v~PsM&^)e?|AVIt>cf?2?VdvpBTTCOr5R0xPIyN}1{Vead)~)T@g%vuZYo3`0KRLHg zqP{r!a=1TLNLO|K{Ejwku6NGzJxOycF25f>GS1`PDDy2(4b3BK#5>NP30!b=f@u_n00%K>>YA8bt6x7J-y`q%?-PRJ17GsSsBCTFuUe1!Z?J69t9~A9miIJrRNSbpeYu8*UDiCeDAtUU z95zboSoDTV(c7^5N{a}|`HE!KJ60*jw?B-_cX4^6n>5>A!n=9Zd!5q-bytSmPJ4fnpbo~tBOp}I>Q*4B}EZ3N3 zDwJ3Tq%ZZ1Ey;Q<#=j#ZC3330-P)$AF|?027y8o28Va<@k1W>WsF`L;cW5`{m}`Z# zcaC&hr!@X_x9Rw(2-)!75Eq%Ik4h1`vwL3ZXPwF{n|6ppx8%q)@x0TCSwgN!V;B{l1{!usv6>&3EJNlH)9jh1KhZ zRe3h(26NdpWV#fMHt~36F)Dod@*RQXDOxus@Xk0XRy=;#73+(}UE=;zLM>-b@X5E( z_-NSfWYcOc+p2n`>)=#}@WyjN`{WNB>q_2VFlk!EmuVBlNhkC(Pm^z$JgcZWIX$LD z@2alQqr-mDVl}hNK7Ob=TqC(}w1{xln?uWcdgTkAz0;j$sJi=^_@%8Mj%#VXJC<_Q z|74G4Rl=%*F%?KT*u2>*t^UoF@Q}0;vC?r-A_3(92 zxs=2RYiA_m!9lp3FNEKF`v|FvtAlh5r{zDK)MLftmE zl2dGwvc~-5H+XORKW^dEf73jBaY^R(18WlEYF^v)WQ82Lr=t}&{HE2i7rot%`C2#g zCMPy~O04zGO-Nd~Xa4bqE1%L6pY2fpIM(#uibZ0sGMl@5j!ihq5Ihi9xtWx@bn>J0 zy8C1EIzL}AAuM#CQgFi4JZ<5w=hX+!teRo7bhP*j3CG8mrst(3PAWaxJGOg#hQ=og z2k!9s<>@@3iUvnBI_|Ar^Y-GAlU%yymu}>hPrN!KDrl6<($(|~qwePk6_-U^_SHUH zkUu}3cHzCm`m#~Q_nss?S+17zv@_b*COcZ-yxrrwcIrnoXg5u_XKa*F&OW{Vu!VA! zR@J-aaMFaHD^^Es&v*0hoAV@gbmX%48#>P$X3im+PiUSq|7_!_Rxd3D^uj#CPjMz~ zVrR*n+VM&BSFyX-^Ep>!E_sLI%`xr_`AEOcYqhB^7*|2~lwqj${rUrX&3h+o+dKTUzy$*vp9Se6nrdm4 z8KQS@9E+d%;xuVh31fD#=I1Bd^s;E8TvHqFtZ&^a{BTS0-8-V0B`b%Wd9EOon~4a5 z*1YFe3JZ@GRW^7RReI~q%KMWZ)5ba6XwclDW~cl`R;d2UL;rHIt*bv;YviiGcV?9P z3$6`Jc0Q-9JnmE3yglpY9lBDKIR0+sF%k0f@o{gRx>S;;7(8#jgGJbD^_Y}SUH+zK z-C3%bSk08Kik;nu4?SLVeG1pgL!w&QqYm8&`!XZ)*4np8bK4IGydz&PRk7u^2ZWmj2hDQr<$8ptzJ3Z zW`i%6-0J)-9`GTI(&=|Hg>p&^uT@SzP;~Xqv?HfB1zZa1ST9`v>`^&q)9G8id2&Z< zqRw+TbX3SH#Xaar$)-3}ZCt?B^DtTex!TMckE#qo7hSQ&e8r)A zoo%_rt6$X}y;{k^`((6Lt;5S|S6|f%Vq@=R(w#>+YeX*C9KC0%WVF4t-I@z&VOT>@jpw#_;&6L5D`@u7R$tnT_H z7|>qFDZE;-yIo96XSNVygXo=%KyKNpd&|lnOgdd(mR5*zJ(?FzN|Ivq=vxDeH{eu&TY{6>-T+Zp*h#-T7IoiV71JDsp%pJa%vT zIqlV*86i7N_i=hl)i`Jk8|(ipx9lLvyk{zHQ$}01(PrtBnQs%iGE%n~V~r=TSZE~# zAJpDjW-~ozE|;F?!9^Y>0SzIhkxeGj;yy;o?pIg07W4Tz+b2nzhZSwi^@`3@z%)#3 z4GbD@zTtxJ&(R1Hi6_f-R4iggrfEJGOUXF+a=GvJk$zIu&kEA6rCqaD^mwv5Q$l9) z$W^Py8~fxJ?bchFrY!#`@ItN+Nm5B=+nw6%^SWDF+VxCCGMb(>4r@5HrfTZ~!__Ct zCMEa=HxN(OMzkF%%9#>DGICg=qI!R4a`MQJtp*I46K}`!ZZ|$~KX2ai?c$~Hc5#

tCliqln%OkY9 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

5$(q*d8>7k+kov3&# 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

oGb`O=lk1% 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 ?!8AXLFYbr?FpRW2ednss=V``_9sv~&Le8RdZP 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%0D;RQk-yQxLjECo=7Is1Qo$^}@c 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%0Awn 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 + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/backend/test/uxbox/tests/_files/sample2.svg b/backend/test/uxbox/tests/_files/sample2.svg new file mode 100644 index 000000000..4d3a3b5ea --- /dev/null +++ b/backend/test/uxbox/tests/_files/sample2.svg @@ -0,0 +1,54 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/backend/test/uxbox/tests/helpers.clj b/backend/test/uxbox/tests/helpers.clj new file mode 100644 index 000000000..bc248e7f9 --- /dev/null +++ b/backend/test/uxbox/tests/helpers.clj @@ -0,0 +1,209 @@ +(ns uxbox.tests.helpers + (:refer-clojure :exclude [await]) + (:require [clj-http.client :as http] + [buddy.hashers :as hashers] + [buddy.core.codecs :as codecs] + [catacumba.serializers :as sz] + [mount.core :as mount] + [storages.core :as st] + [suricatta.core :as sc] + [uxbox.services.auth :as usa] + [uxbox.services.users :as usu] + [uxbox.util.transit :as t] + [uxbox.migrations :as umg] + [uxbox.media :as media] + [uxbox.db :as db] + [uxbox.config :as cfg])) + +(def +base-url+ "http://localhost:5050") + +(defn state-init + [next] + (let [config (cfg/read-test-config)] + (-> (mount/only #{#'uxbox.config/config + #'uxbox.config/secret + #'uxbox.db/datasource + #'uxbox.migrations/migrations + #'uxbox.media/static-storage + #'uxbox.media/media-storage + #'uxbox.media/images-storage + #'uxbox.media/thumbnails-storage}) + (mount/swap {#'uxbox.config/config config}) + (mount/start)) + (try + (next) + (finally + (mount/stop))))) + +(defn database-reset + [next] + (state-init + (fn [] + (with-open [conn (db/connection)] + (let [sql (str "SELECT table_name " + " FROM information_schema.tables " + " WHERE table_schema = 'public' " + " AND table_name != 'migrations';") + result (->> (sc/fetch conn sql) + (map :table_name))] + (sc/execute conn (str "TRUNCATE " + (apply str (interpose ", " result)) + " CASCADE;")))) + (try + (next) + (finally + (st/clear! uxbox.media/media-storage) + (st/clear! uxbox.media/static-storage)))))) + +(defmacro await + [expr] + `(try + (deref ~expr) + (catch Exception e# + (.getCause e#)))) + +(defn- strip-response + [{:keys [status headers body]}] + (if (= (get headers "content-type") "application/transit+json") + [status (-> (codecs/str->bytes body) + (t/decode))] + [status body])) + +(defn http-get + ([user uri] (http-get user uri nil)) + ([user uri {:keys [query] :as opts}] + (let [headers (when user + {"Authorization" (str "Token " (usa/generate-token user))}) + params (merge {:headers headers} + (when query + {:query-params query}))] + (try + (strip-response (http/get uri params)) + (catch clojure.lang.ExceptionInfo e + (strip-response (ex-data e))))))) + +(defn http-post + ([uri params] + (http-post nil uri params)) + ([user uri {:keys [body] :as params}] + (let [body (-> (t/encode body) + (codecs/bytes->str)) + headers (merge + {"content-type" "application/transit+json"} + (when user + {"Authorization" (str "Token " (usa/generate-token user))})) + params {:headers headers :body body}] + (try + (strip-response (http/post uri params)) + (catch clojure.lang.ExceptionInfo e + (strip-response (ex-data e))))))) + +(defn http-multipart + [user uri params] + (let [headers (merge + (when user + {"Authorization" (str "Token " (usa/generate-token user))})) + params {:headers headers + :multipart params}] + (try + (strip-response (http/post uri params)) + (catch clojure.lang.ExceptionInfo e + (strip-response (ex-data e)))))) + +(defn http-put + ([uri params] + (http-put nil uri params)) + ([user uri {:keys [body] :as params}] + (let [body (-> (t/encode body) + (codecs/bytes->str)) + headers (merge + {"content-type" "application/transit+json"} + (when user + {"Authorization" (str "Token " (usa/generate-token user))})) + params {:headers headers :body body}] + (try + (strip-response (http/put uri params)) + (catch clojure.lang.ExceptionInfo e + (strip-response (ex-data e))))))) + +(defn http-delete + ([uri] + (http-delete nil uri)) + ([user uri] + (let [headers (when user + {"Authorization" (str "Token " (usa/generate-token user))}) + params {:headers headers}] + (try + (strip-response (http/delete uri params)) + (catch clojure.lang.ExceptionInfo e + (strip-response (ex-data e))))))) + +(defn- decode-response + [{:keys [status headers body] :as response}] + (if (= (get headers "content-type") "application/transit+json") + (assoc response :body (-> (codecs/str->bytes body) + (t/decode))) + response)) + +(defn request + [{:keys [path method body user headers raw?] + :or {raw? false} + :as request}] + {:pre [(string? path) (keyword? method)]} + (let [body (if (and body (not raw?)) + (-> (t/encode body) + (codecs/bytes->str)) + body) + headers (cond-> headers + body (assoc "content-type" "application/transit+json") + raw? (assoc "content-type" "application/octet-stream") + user (assoc "authorization" + (str "Token " (usa/generate-token user)))) + params {:headers headers :body body} + uri (str +base-url+ path)] + (try + (let [response (case method + :get (http/get uri (dissoc params :body)) + :post (http/post uri params) + :put (http/put uri params) + :delete (http/delete uri params))] + (decode-response response)) + (catch clojure.lang.ExceptionInfo e + (decode-response (ex-data e)))))) + +(defn create-user + "Helper for create users" + [conn i] + (let [data {:username (str "user" i) + :password (str "user" i) + :metadata (str i) + :fullname (str "User " i) + :email (str "user" i "@uxbox.io")}] + (usu/create-user conn data))) + +(defmacro try-on + [& body] + `(try + (let [result# (do ~@body)] + [nil result#]) + (catch Throwable e# + [e# nil]))) + +(defn exception? + [v] + (instance? Throwable v)) + +(defn ex-info? + [v] + (instance? clojure.lang.ExceptionInfo v)) + +(defn ex-of-type? + [e type] + (let [data (ex-data e)] + (= type (:type data)))) + +(defn ex-with-code? + [e code] + (let [data (ex-data e)] + (= code (:code data)))) + diff --git a/backend/test/uxbox/tests/test_auth.clj b/backend/test/uxbox/tests/test_auth.clj new file mode 100644 index 000000000..4771dfe7d --- /dev/null +++ b/backend/test/uxbox/tests/test_auth.clj @@ -0,0 +1,53 @@ +(ns uxbox.tests.test-auth + (:require [clojure.test :as t] + [promesa.core :as p] + [clj-http.client :as http] + [catacumba.testing :refer (with-server)] + [buddy.hashers :as hashers] + [uxbox.db :as db] + [uxbox.frontend :as uft] + [uxbox.services.users :as usu] + [uxbox.services :as usv] + [uxbox.tests.helpers :as th])) + +(t/use-fixtures :each th/database-reset) + +(t/deftest test-http-success-auth + (let [data {:username "user1" + :fullname "user 1" + :metadata "1" + :password "user1" + :email "user1@uxbox.io"} + user (with-open [conn (db/connection)] + (usu/create-user conn data))] + (with-server {:handler (uft/routes)} + (let [data {:username "user1" + :password "user1" + :metadata "1" + :scope "foobar"} + uri (str th/+base-url+ "/api/auth/token") + [status data] (th/http-post uri {:body data})] + ;; (println "RESPONSE:" status data) + (t/is (= status 200)) + (t/is (contains? data :token)))))) + +(t/deftest test-http-failed-auth + (let [data {:username "user1" + :fullname "user 1" + :metadata "1" + :password (hashers/encrypt "user1") + :email "user1@uxbox.io"} + user (with-open [conn (db/connection)] + (usu/create-user conn data))] + (with-server {:handler (uft/routes)} + (let [data {:username "user1" + :password "user2" + :metadata "2" + :scope "foobar"} + uri (str th/+base-url+ "/api/auth/token") + [status data] (th/http-post uri {:body data})] + ;; (println "RESPONSE:" status data) + (t/is (= 400 status)) + (t/is (= (:type data) :validation)) + (t/is (= (:code data) :uxbox.services.auth/wrong-credentials)))))) + diff --git a/backend/test/uxbox/tests/test_icons.clj b/backend/test/uxbox/tests/test_icons.clj new file mode 100644 index 000000000..e69d462d5 --- /dev/null +++ b/backend/test/uxbox/tests/test_icons.clj @@ -0,0 +1,163 @@ +(ns uxbox.tests.test-icons + (:require [clojure.test :as t] + [promesa.core :as p] + [suricatta.core :as sc] + [clj-uuid :as uuid] + [clojure.java.io :as io] + [catacumba.testing :refer (with-server)] + [buddy.core.codecs :as codecs] + [uxbox.db :as db] + [uxbox.sql :as sql] + [uxbox.frontend :as uft] + [uxbox.services.icons :as icons] + [uxbox.services :as usv] + [uxbox.tests.helpers :as th])) + +(t/use-fixtures :each th/database-reset) + +(t/deftest test-http-list-icon-collections + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + data {:user (:id user) + :name "coll1"} + coll (icons/create-collection conn data)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/icon-collections") + [status data] (th/http-get user uri)] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= 1 (count data)))))))) + +(t/deftest test-http-create-icon-collection + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/icon-collections") + data {:user (:id user) + :name "coll1"} + params {:body data} + [status data] (th/http-post user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 201 status)) + (t/is (= (:user data) (:id user))) + (t/is (= (:name data) "coll1"))))))) + +(t/deftest test-http-update-icon-collection + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + data {:user (:id user) + :name "coll1"} + coll (icons/create-collection conn data)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/icon-collections/" (:id coll)) + params {:body (assoc coll :name "coll2")} + [status data] (th/http-put user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= (:user data) (:id user))) + (t/is (= (:name data) "coll2"))))))) + +(t/deftest test-http-icon-collection-delete + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + data {:user (:id user) + :name "coll1" + :data #{1}} + coll (icons/create-collection conn data)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/icon-collections/" (:id coll)) + [status data] (th/http-delete user uri)] + (t/is (= 204 status)) + (let [sqlv (sql/get-icon-collections {:user (:id user)}) + result (sc/fetch conn sqlv)] + (t/is (empty? result)))))))) + +(t/deftest test-http-create-icon + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/icons") + data {:name "sample.jpg" + :content "" + :metadata {:width 200 + :height 200 + :view-box [0 0 200 200]} + :collection nil} + params {:body data} + [status data] (th/http-post user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 201 status)) + (t/is (= (:user data) (:id user))) + (t/is (= (:name data) "sample.jpg")) + (t/is (= (:metadata data) {:width 200 + :height 200 + :view-box [0 0 200 200]}))))))) + +(t/deftest test-http-update-icon + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + data {:user (:id user) + :name "test.svg" + :content "" + :metadata {} + :collection nil} + icon (icons/create-icon conn data)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/icons/" (:id icon)) + params {:body (assoc icon :name "my stuff")} + [status data] (th/http-put user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= (:user data) (:id user))) + (t/is (= (:name data) "my stuff"))))))) + +(t/deftest test-http-copy-icon + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + data {:user (:id user) + :name "test.svg" + :content "" + :metadata {} + :collection nil} + icon (icons/create-icon conn data)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/icons/copy") + body {:id (:id icon) :collection nil} + params {:body body} + [status data] (th/http-put user uri params)] + (println "RESPONSE:" status data) + (let [sqlv (sql/get-icons {:user (:id user) :collection nil}) + result (sc/fetch conn sqlv)] + (t/is (= 2 (count result))))))))) + +(t/deftest test-http-delete-icon + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + data {:user (:id user) + :name "test.svg" + :content "" + :metadata {} + :collection nil} + icon (icons/create-icon conn data)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/icons/" (:id icon)) + [status data] (th/http-delete user uri)] + (t/is (= 204 status)) + (let [sqlv (sql/get-icons {:user (:id user) :collection nil}) + result (sc/fetch conn sqlv)] + (t/is (empty? result)))))))) + +;; (t/deftest test-http-list-icons +;; (with-open [conn (db/connection)] +;; (let [user (th/create-user conn 1) +;; data {:user (:id user) +;; :name "test.png" +;; :path "some/path" +;; :collection nil} +;; icon (icons/create-icon conn data)] +;; (with-server {:handler (uft/routes)} +;; (let [uri (str th/+base-url+ "/api/library/icons") +;; [status data] (th/http-get user uri)] +;; (println "RESPONSE:" status data) +;; (t/is (= 200 status)) +;; (t/is (= 1 (count data)))))))) diff --git a/backend/test/uxbox/tests/test_images.clj b/backend/test/uxbox/tests/test_images.clj new file mode 100644 index 000000000..385183f61 --- /dev/null +++ b/backend/test/uxbox/tests/test_images.clj @@ -0,0 +1,169 @@ +(ns uxbox.tests.test-images + (:require [clojure.test :as t] + [promesa.core :as p] + [suricatta.core :as sc] + [clojure.java.io :as io] + [catacumba.testing :refer (with-server)] + [storages.core :as st] + [uxbox.db :as db] + [uxbox.sql :as sql] + [uxbox.media :as media] + [uxbox.frontend :as uft] + [uxbox.services.images :as images] + [uxbox.services :as usv] + [uxbox.tests.helpers :as th])) + +(t/use-fixtures :each th/database-reset) + +(t/deftest test-http-list-image-collections + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + data {:user (:id user) + :name "coll1"} + coll (images/create-collection conn data)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/image-collections") + [status data] (th/http-get user uri)] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= 1 (count data)))))))) + +(t/deftest test-http-create-image-collection + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/image-collections") + data {:user (:id user) + :name "coll1"} + params {:body data} + [status data] (th/http-post user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 201 status)) + (t/is (= (:user data) (:id user))) + (t/is (= (:name data) "coll1"))))))) + +(t/deftest test-http-update-image-collection + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + data {:user (:id user) + :name "coll1"} + coll (images/create-collection conn data)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/image-collections/" (:id coll)) + params {:body (assoc coll :name "coll2")} + [status data] (th/http-put user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= (:user data) (:id user))) + (t/is (= (:name data) "coll2"))))))) + +(t/deftest test-http-image-collection-delete + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + data {:user (:id user) + :name "coll1" + :data #{1}} + coll (images/create-collection conn data)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/image-collections/" (:id coll)) + [status data] (th/http-delete user uri)] + (t/is (= 204 status)) + (let [sqlv (sql/get-image-collections {:user (:id user)}) + result (sc/fetch conn sqlv)] + (t/is (empty? result)))))))) + +;; (t/deftest test-http-create-image +;; (with-open [conn (db/connection)] +;; (let [user (th/create-user conn 1)] +;; (with-server {:handler (uft/routes)} +;; (let [uri (str th/+base-url+ "/api/library/images") +;; params [{:name "sample.jpg" +;; :part-name "file" +;; :content (io/input-stream +;; (io/resource "uxbox/tests/_files/sample.jpg"))}] +;; [status data] (th/http-multipart user uri params)] +;; ;; (println "RESPONSE:" status data) +;; (t/is (= 201 status)) +;; (t/is (= (:user data) (:id user))) +;; (t/is (= (:name data) "sample.jpg"))))))) + +(t/deftest test-http-update-image + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + data {:user (:id user) + :name "test.png" + :path "some/path" + :width 100 + :height 100 + :mimetype "image/png" + :collection nil} + img (images/create-image conn data)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/images/" (:id img)) + params {:body (assoc img :name "my stuff")} + [status data] (th/http-put user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= (:user data) (:id user))) + (t/is (= (:name data) "my stuff"))))))) + +(t/deftest test-http-copy-image + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + storage media/images-storage + filename "sample.jpg" + rcs (io/resource "uxbox/tests/_files/sample.jpg") + path @(st/save storage filename rcs) + data {:user (:id user) + :name filename + :path (str path) + :width 100 + :height 100 + :mimetype "image/jpg" + :collection nil} + img (images/create-image conn data)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/images/copy") + body {:id (:id img) + :collection nil} + params {:body body} + [status data] (th/http-put user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status)) + (let [sqlv (sql/get-images {:user (:id user) :collection nil}) + result (sc/fetch conn sqlv)] + (t/is (= 2 (count result))))))))) + +(t/deftest test-http-delete-image + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + data {:user (:id user) + :name "test.png" + :path "some/path" + :width 100 + :height 100 + :mimetype "image/png" + :collection nil} + img (images/create-image conn data)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/library/images/" (:id img)) + [status data] (th/http-delete user uri)] + (t/is (= 204 status)) + (let [sqlv (sql/get-images {:user (:id user) :collection nil}) + result (sc/fetch conn sqlv)] + (t/is (empty? result)))))))) + +;; (t/deftest test-http-list-images +;; (with-open [conn (db/connection)] +;; (let [user (th/create-user conn 1) +;; data {:user (:id user) +;; :name "test.png" +;; :path "some/path" +;; :collection nil} +;; img (images/create-image conn data)] +;; (with-server {:handler (uft/routes)} +;; (let [uri (str th/+base-url+ "/api/library/images") +;; [status data] (th/http-get user uri)] +;; (println "RESPONSE:" status data) +;; (t/is (= 200 status)) +;; (t/is (= 1 (count data)))))))) diff --git a/backend/test/uxbox/tests/test_kvstore.clj b/backend/test/uxbox/tests/test_kvstore.clj new file mode 100644 index 000000000..c56c9fc99 --- /dev/null +++ b/backend/test/uxbox/tests/test_kvstore.clj @@ -0,0 +1,65 @@ +(ns uxbox.tests.test-kvstore + (:require [clojure.test :as t] + [promesa.core :as p] + [suricatta.core :as sc] + [catacumba.testing :refer (with-server)] + [buddy.core.codecs :as codecs] + [uxbox.db :as db] + [uxbox.util.uuid :as uuid] + [uxbox.frontend :as uft] + [uxbox.services.kvstore :as kvs] + [uxbox.tests.helpers :as th])) + +(t/use-fixtures :each th/database-reset) + +(t/deftest test-http-kvstore + (with-open [conn (db/connection)] + (let [{:keys [id] :as user} (th/create-user conn 1)] + + ;; Not exists at this moment + (t/is (nil? (kvs/retrieve-kvstore conn {:user id :key "foo" :version -1}))) + + ;; Creating new one should work as expected + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/kvstore") + body {:key "foo" :value "bar" :version -1} + params {:body body} + [status data] (th/http-put user uri params)] + (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= (:key data) "foo")) + (t/is (= (:value data) "bar")))) + + ;; Should exists + (let [data (kvs/retrieve-kvstore conn {:user id :key "foo"})] + (t/is (= (:key data) "foo")) + (t/is (= (:value data) "bar")) + + ;; Overwriting should work + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/kvstore") + body (assoc data :key "foo" :value "baz") + params {:body body} + [status data] (th/http-put user uri params)] + (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= (:key data) "foo")) + (t/is (= (:value data) "baz"))))) + + ;; Should exists and match the overwritten value + (let [data (kvs/retrieve-kvstore conn {:user id :key "foo"})] + (t/is (= (:key data) "foo")) + (t/is (= (:value data) "baz"))) + + ;; Delete should work + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/kvstore/foo") + [status data] (th/http-delete user uri)] + (println "RESPONSE:" status data) + (t/is (= 204 status)))) + + ;; Not exists at this moment + (t/is (nil? (kvs/retrieve-kvstore conn {:user id :key "foo"}))) + + ))) + diff --git a/backend/test/uxbox/tests/test_pages.clj b/backend/test/uxbox/tests/test_pages.clj new file mode 100644 index 000000000..98964b9f3 --- /dev/null +++ b/backend/test/uxbox/tests/test_pages.clj @@ -0,0 +1,224 @@ +(ns uxbox.tests.test-pages + (:require [clojure.test :as t] + [promesa.core :as p] + [suricatta.core :as sc] + [catacumba.testing :refer (with-server)] + [buddy.core.codecs :as codecs] + [uxbox.db :as db] + [uxbox.util.uuid :as uuid] + [uxbox.frontend :as uft] + [uxbox.services.projects :as uspr] + [uxbox.services.pages :as uspg] + [uxbox.services :as usv] + [uxbox.tests.helpers :as th])) + +(t/use-fixtures :each th/database-reset) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Frontend Test +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(t/deftest test-http-page-create + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + proj (uspr/create-project conn {:user (:id user) :name "proj1"})] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/pages") + params {:body {:project (:id proj) + :name "page1" + :data "1" + :metadata "1" + :width 200 + :height 200 + :layout "mobile"}} + [status data] (th/http-post user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 201 status)) + (t/is (= (:data (:body params)) (:data data))) + (t/is (= (:user data) (:id user))) + (t/is (= (:name data) "page1"))))))) + +(t/deftest test-http-page-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)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ (str "/api/pages/" (:id page))) + params {:body (assoc page :data "3")} + [status page'] (th/http-put user uri params)] + ;; (println "RESPONSE:" status page') + (t/is (= 200 status)) + (t/is (= "3" (:data page'))) + (t/is (= 1 (:version page'))) + (t/is (= (:user page') (:id user))) + (t/is (= (:name data) "page1"))))))) + +(t/deftest test-http-page-update-metadata + (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)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ (str "/api/pages/" (:id page) "/metadata")) + params {:body (assoc page :data "3")} + [status page'] (th/http-put user uri params)] + ;; (println "RESPONSE:" status page') + (t/is (= 200 status)) + (t/is (= "1" (:data page'))) + (t/is (= 1 (:version page'))) + (t/is (= (:user page') (:id user))) + (t/is (= (:name data) "page1"))))))) + +(t/deftest test-http-page-delete + (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)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ (str "/api/pages/" (:id page))) + [status response] (th/http-delete user uri)] + ;; (println "RESPONSE:" status response) + (t/is (= 204 status)) + (let [sqlv ["SELECT * FROM pages WHERE \"user\"=? AND deleted_at is null" + (:id user)] + result (sc/fetch conn sqlv)] + (t/is (empty? result)))))))) + +(t/deftest test-http-page-list-by-project + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + proj1 (uspr/create-project conn {:user (:id user) :name "proj1"}) + proj2 (uspr/create-project conn {:user (:id user) :name "proj2"}) + data {:user (:id user) + :version 0 + :data "1" + :metadata "2" + :name "page1" + :width 200 + :height 200 + :layout "mobil"} + page1 (uspg/create-page conn (assoc data :project (:id proj1))) + page2 (uspg/create-page conn (assoc data :project (:id proj2)))] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ (str "/api/projects/" (:id proj1) "/pages")) + [status response] (th/http-get user uri)] + ;; (println "RESPONSE:" status response) + (t/is (= 200 status)) + (t/is (= 1 (count response))) + (t/is (= (:id (first response)) (:id page1)))))))) + +(t/deftest test-http-page-history-retrieve + (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 100] + (let [page (uspg/get-page-by-id conn (:id data))] + (uspg/update-page conn (assoc page :data (str i))))) + + ;; Check inserted history + (let [sqlv ["SELECT * FROM pages_history WHERE page=?" (:id data)] + result (sc/fetch conn sqlv)] + (t/is (= (count result) 100))) + + ;; Check retrieve all items + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/pages/" (:id page) "/history") + [status result] (th/http-get user uri nil)] + ;; (println "RESPONSE:" status result) + (t/is (= (count result) 10)) + (t/is (= 200 status)) + (t/is (= 99 (:version (first result)))) + + (let [params {:query {:since (:version (last result)) + :max 20}} + [status result] (th/http-get user uri params)] + ;; (println "RESPONSE:" status result) + (t/is (= (count result) 20)) + (t/is (= 200 status)) + (t/is (= 89 (:version (first result)))))) + )))) + +(t/deftest test-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)] + + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ + "/api/pages/" (:id page) + "/history/" (:id item)) + params {:body {:label "test" :pinned true}} + [status data] (th/http-put user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= (:id data) (:id item)))))) + + (let [sql (str "SELECT * FROM pages_history " + " WHERE page=? AND pinned = true " + " ORDER BY created_at DESC") + result (sc/fetch-one conn [sql (:id data)])] + (t/is (= "test" (:label result))) + (t/is (= true (:pinned result))))))) diff --git a/backend/test/uxbox/tests/test_projects.clj b/backend/test/uxbox/tests/test_projects.clj new file mode 100644 index 000000000..963e158aa --- /dev/null +++ b/backend/test/uxbox/tests/test_projects.clj @@ -0,0 +1,92 @@ +(ns uxbox.tests.test-projects + (:require [clojure.test :as t] + [promesa.core :as p] + [suricatta.core :as sc] + [clj-uuid :as uuid] + [catacumba.testing :refer (with-server)] + [catacumba.serializers :as sz] + [uxbox.db :as db] + [uxbox.frontend :as uft] + [uxbox.services.projects :as uspr] + [uxbox.services.pages :as uspg] + [uxbox.services :as usv] + [uxbox.tests.helpers :as th])) + +(t/use-fixtures :each th/database-reset) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Frontend Test +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(t/deftest test-http-project-list + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + proj (uspr/create-project conn {:user (:id user) :name "proj1"})] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/projects") + [status data] (th/http-get user uri)] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= 1 (count data)))))))) + +(t/deftest test-http-project-create + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/projects") + params {:body {:name "proj1"}} + [status data] (th/http-post user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 201 status)) + (t/is (= (:user data) (:id user))) + (t/is (= (:name data) "proj1"))))))) + +(t/deftest test-http-project-update + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + proj (uspr/create-project conn {:user (:id user) :name "proj1"})] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/projects/" (:id proj)) + params {:body (assoc proj :name "proj2")} + [status data] (th/http-put user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= (:user data) (:id user))) + (t/is (= (:name data) "proj2"))))))) + +(t/deftest test-http-project-delete + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + proj (uspr/create-project conn {:user (:id user) :name "proj1"})] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/projects/" (:id proj)) + [status data] (th/http-delete user uri)] + (t/is (= 204 status)) + (let [sqlv ["SELECT * FROM projects WHERE \"user\"=? AND deleted_at is null" + (:id user)] + result (sc/fetch conn sqlv)] + (t/is (empty? result)))))))) + +(t/deftest test-http-project-retrieve-by-share-token + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + proj (uspr/create-project conn {:user (:id user) :name "proj1"}) + page (uspg/create-page conn {:id (uuid/v4) + :user (:id user) + :project (:id proj) + :version 0 + :data "1" + :options "2" + :name "page1" + :width 200 + :height 200 + :layout "mobil"}) + shares (uspr/get-share-tokens-for-project conn (:id proj))] + (with-server {:handler (uft/routes)} + (let [token (:token (first shares)) + uri (str th/+base-url+ "/api/projects-by-token/" token) + [status data] (th/http-get user uri)] + ;; (println "RESPONSE:" status data) + (t/is (= status 200)) + (t/is (vector? (:pages data))) + (t/is (= 1 (count (:pages data))))))))) diff --git a/backend/test/uxbox/tests/test_svgparse.clj b/backend/test/uxbox/tests/test_svgparse.clj new file mode 100644 index 000000000..4c3f7277e --- /dev/null +++ b/backend/test/uxbox/tests/test_svgparse.clj @@ -0,0 +1,89 @@ +(ns uxbox.tests.test-svgparse + (:require [clojure.test :as t] + [clojure.java.io :as io] + [catacumba.testing :refer [with-server]] + [uxbox.frontend :as uft] + [uxbox.services :as usv] + [uxbox.services.svgparse :as svg] + [uxbox.tests.helpers :as th])) + +(t/use-fixtures :each th/state-init) + +(t/deftest parse-svg-test + (t/testing "parsing valid svg 1" + (let [image (slurp (io/resource "uxbox/tests/_files/sample1.svg")) + result (svg/parse-string image)] + (t/is (contains? result :width)) + (t/is (contains? result :height)) + (t/is (contains? result :view-box)) + (t/is (contains? result :name)) + (t/is (contains? result :content)) + (t/is (= 500.0 (:width result))) + (t/is (= 500.0 (:height result))) + (t/is (= [0.0 0.0 500.00001 500.00001] (:view-box result))) + (t/is (= "lock.svg" (:name result))))) + + (t/testing "parsing valid svg 2" + (let [image (slurp (io/resource "uxbox/tests/_files/sample2.svg")) + result (svg/parse-string image)] + (t/is (contains? result :width)) + (t/is (contains? result :height)) + (t/is (contains? result :view-box)) + (t/is (contains? result :name)) + (t/is (contains? result :content)) + (t/is (= 500.0 (:width result))) + (t/is (= 500.0 (:height result))) + (t/is (= [0.0 0.0 500.0 500.00001] (:view-box result))) + (t/is (= "play.svg" (:name result))))) + + (t/testing "parsing invalid data 1" + (let [image (slurp (io/resource "uxbox/tests/_files/sample.jpg")) + [e result] (th/try-on (svg/parse-string image))] + (t/is (th/exception? e)) + (t/is (th/ex-info? e)) + (t/is (th/ex-with-code? e :uxbox.services.svgparse/invalid-input)))) + + (t/testing "parsing invalid data 2" + (let [[e result] (th/try-on (svg/parse-string ""))] + (t/is (th/exception? e)) + (t/is (th/ex-info? e)) + (t/is (th/ex-with-code? e :uxbox.services.svgparse/invalid-input)))) + + (t/testing "parsing invalid data 3" + (let [[e result] (th/try-on (svg/parse-string ""))] + (t/is (th/exception? e)) + (t/is (th/ex-info? e)) + (t/is (th/ex-with-code? e :uxbox.services.svgparse/invalid-result)))) + + (t/testing "valid http request" + (let [image (slurp (io/resource "uxbox/tests/_files/sample2.svg")) + path "/api/svg/parse"] + (with-server {:handler (uft/routes)} + (let [rsp (th/request {:method :post + :path path + :body image + :raw? true})] + (t/is (= 200 (:status rsp))) + (t/is (contains? (:body rsp) :width)) + (t/is (contains? (:body rsp) :height)) + (t/is (contains? (:body rsp) :view-box)) + (t/is (contains? (:body rsp) :name)) + (t/is (contains? (:body rsp) :content)) + (t/is (= 500.0 (:width (:body rsp)))) + (t/is (= 500.0 (:height (:body rsp)))) + (t/is (= [0.0 0.0 500.0 500.00001] (:view-box (:body rsp)))) + (t/is (= "play.svg" (:name (:body rsp)))))))) + + (t/testing "invalid http request" + (let [path "/api/svg/parse" + image ""] + (with-server {:handler (uft/routes)} + (let [rsp (th/request {:method :post + :path path + :body image + :raw? true})] + (t/is (= 400 (:status rsp))) + (t/is (= :validation (get-in rsp [:body :type]))) + (t/is (= ::svg/invalid-result (get-in rsp [:body :code]))))))) + + ) diff --git a/backend/test/uxbox/tests/test_txlog.clj b/backend/test/uxbox/tests/test_txlog.clj new file mode 100644 index 000000000..d8099664c --- /dev/null +++ b/backend/test/uxbox/tests/test_txlog.clj @@ -0,0 +1,19 @@ +(ns uxbox.tests.test-txlog + "A txlog and services abstraction generic tests." + (:require [clojure.test :as t] + [promesa.core :as p] + [uxbox.services.core :as usc] + [uxbox.services :as usv] + [uxbox.tests.helpers :as th])) + +(t/use-fixtures :each th/database-reset) + +(defmethod usc/novelty ::testype1 + [data] + true) + +(t/deftest txlog-spec1 + (let [data {:type ::testype1 :foo 1 :bar "baz"} + response (usv/novelty data)] + (t/is (p/promise? response)) + (t/is (= true @response)))) diff --git a/backend/test/uxbox/tests/test_users.clj b/backend/test/uxbox/tests/test_users.clj new file mode 100644 index 000000000..4d1773ead --- /dev/null +++ b/backend/test/uxbox/tests/test_users.clj @@ -0,0 +1,120 @@ +(ns uxbox.tests.test-users + (:require [clojure.test :as t] + [clojure.java.io :as io] + [promesa.core :as p] + [buddy.hashers :as hashers] + [clj-http.client :as http] + [suricatta.core :as sc] + [catacumba.testing :refer (with-server)] + [uxbox.db :as db] + [uxbox.frontend :as uft] + [uxbox.services.users :as usu] + [uxbox.services :as usv] + [uxbox.tests.helpers :as th])) + +(t/use-fixtures :each th/database-reset) + +(t/deftest test-http-retrieve-profile + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/profile/me") + [status data] (th/http-get user uri)] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= (:fullname data) "User 1")) + (t/is (= (:username data) "user1")) + (t/is (= (:metadata data) "1")) + (t/is (= (:email data) "user1@uxbox.io")) + (t/is (not (contains? data :password)))))))) + +(t/deftest test-http-update-profile + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/profile/me") + data (assoc user + :fullname "Full Name" + :username "user222" + :metadata "222" + :email "user222@uxbox.io") + [status data] (th/http-put user uri {:body data})] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status)) + (t/is (= (:fullname data) "Full Name")) + (t/is (= (:username data) "user222")) + (t/is (= (:metadata data) "222")) + (t/is (= (:email data) "user222@uxbox.io")) + (t/is (not (contains? data :password)))))))) + +(t/deftest test-http-update-profile-photo + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1)] + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/profile/me/photo") + params [{:name "sample.jpg" + :part-name "file" + :content (io/input-stream + (io/resource "uxbox/tests/_files/sample.jpg"))}] + [status data] (th/http-multipart user uri params)] + ;; (println "RESPONSE:" status data) + (t/is (= 204 status))))))) + +(t/deftest test-http-register-user + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/auth/register") + data {:fullname "Full Name" + :username "user222" + :email "user222@uxbox.io" + :password "user222"} + [status data] (th/http-post uri {:body data})] + ;; (println "RESPONSE:" status data) + (t/is (= 200 status))))) + +(t/deftest test-http-validate-recovery-token + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1)] + (with-server {:handler (uft/routes)} + (let [token (#'usu/request-password-recovery conn "user1") + uri1 (str th/+base-url+ "/api/auth/recovery/not-existing") + uri2 (str th/+base-url+ "/api/auth/recovery/" token) + [status1 data1] (th/http-get user uri1) + [status2 data2] (th/http-get user uri2)] + ;; (println "RESPONSE:" status1 data1) + ;; (println "RESPONSE:" status2 data2) + (t/is (= 404 status1)) + (t/is (= 204 status2))))))) + +(t/deftest test-http-request-password-recovery + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1) + sql "select * from user_pswd_recovery" + res (sc/fetch-one conn sql)] + + ;; Initially no tokens exists + (t/is (nil? res)) + + (with-server {:handler (uft/routes)} + (let [uri (str th/+base-url+ "/api/auth/recovery") + data {:username "user1"} + [status data] (th/http-post user uri {:body data})] + ;; (println "RESPONSE:" status data) + (t/is (= 204 status))) + + (let [res (sc/fetch-one conn sql)] + (t/is (not (nil? res))) + (t/is (= (:user res) (:id user)))))))) + +(t/deftest test-http-validate-recovery-token + (with-open [conn (db/connection)] + (let [user (th/create-user conn 1)] + (with-server {:handler (uft/routes)} + (let [token (#'usu/request-password-recovery conn (:username user)) + uri (str th/+base-url+ "/api/auth/recovery") + data {:token token :password "mytestpassword"} + [status data] (th/http-put user uri {:body data}) + + user' (usu/find-full-user-by-id conn (:id user))] + (t/is (= status 204)) + (t/is (hashers/check "mytestpassword" (:password user')))))))) + diff --git a/backend/vendor/executors/core.clj b/backend/vendor/executors/core.clj new file mode 100644 index 000000000..4bffc365b --- /dev/null +++ b/backend/vendor/executors/core.clj @@ -0,0 +1,176 @@ +;; 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) 2016 Andrey Antukh + +(ns executors.core + "A executos service abstraction layer." + (:import java.util.function.Supplier + java.util.concurrent.ForkJoinPool + java.util.concurrent.Future + java.util.concurrent.CompletableFuture + java.util.concurrent.ExecutorService + java.util.concurrent.TimeoutException + java.util.concurrent.ThreadFactory + java.util.concurrent.TimeUnit + java.util.concurrent.ScheduledExecutorService + java.util.concurrent.Executors)) + +(def ^:const +max-priority+ Thread/MAX_PRIORITY) +(def ^:const +min-priority+ Thread/MIN_PRIORITY) +(def ^:const +norm-priority+ Thread/NORM_PRIORITY) + +;; --- Protocols + +(defprotocol IExecutor + (^:private -execute [_ task] "Execute a task in a executor.") + (^:private -submit [_ task] "Submit a task and return a promise.")) + +(defprotocol IScheduledExecutor + (^:provate -schedule [_ ms task] "Schedule a task to execute in a future.")) + +(defprotocol IScheduledTask + "A cancellation abstraction." + (-cancel [_]) + (-cancelled? [_])) + +;; --- Implementation + +(defn- thread-factory-adapter + "Adapt a simple clojure function into a + ThreadFactory instance." + [func] + (reify ThreadFactory + (^Thread newThread [_ ^Runnable runnable] + (func runnable)))) + +(defn- thread-factory + [{:keys [daemon priority] + :or {daemon true + priority Thread/NORM_PRIORITY}}] + (thread-factory-adapter + (fn [runnable] + (let [thread (Thread. ^Runnable runnable)] + (.setDaemon thread daemon) + (.setPriority thread priority) + thread)))) + +(defn- resolve-thread-factory + [opts] + (cond + (map? opts) (thread-factory opts) + (fn? opts) (thread-factory-adapter opts) + (instance? ThreadFactory opts) opts + :else (throw (ex-info "Invalid thread factory" {})))) + +(deftype ScheduledTask [^Future fut] + clojure.lang.IDeref + (deref [_] + (.get fut)) + + clojure.lang.IBlockingDeref + (deref [_ ms default] + (try + (.get fut ms TimeUnit/MILLISECONDS) + (catch TimeoutException e + default))) + + clojure.lang.IPending + (isRealized [_] (and (.isDone fut) + (not (.isCancelled fut)))) + + IScheduledTask + (-cancelled? [_] + (.isCancelled fut)) + + (-cancel [_] + (when-not (.isCancelled fut) + (.cancel fut true)))) + +(extend-type ExecutorService + IExecutor + (-execute [this task] + (CompletableFuture/runAsync ^Runnable task this)) + + (-submit [this task] + (let [supplier (reify Supplier (get [_] (task)))] + (CompletableFuture/supplyAsync supplier this)))) + +(extend-type ScheduledExecutorService + IScheduledExecutor + (-schedule [this ms func] + (let [fut (.schedule this func ms TimeUnit/MILLISECONDS)] + (ScheduledTask. fut)))) + +;; --- Public Api (Pool Constructors) + +(defn common-pool + "Get the common pool." + [] + (ForkJoinPool/commonPool)) + +(defn cached + "A cached thread pool constructor." + ([] + (Executors/newCachedThreadPool)) + ([opts] + (let [factory (resolve-thread-factory opts)] + (Executors/newCachedThreadPool factory)))) + +(defn fixed + "A fixed thread pool constructor." + ([n] + (Executors/newFixedThreadPool (int n))) + ([n opts] + (let [factory (resolve-thread-factory opts)] + (Executors/newFixedThreadPool (int n) factory)))) + +(defn single-thread + "A single thread pool constructor." + ([] + (Executors/newSingleThreadExecutor)) + ([opts] + (let [factory (resolve-thread-factory opts)] + (Executors/newSingleThreadExecutor factory)))) + +(defn scheduled + "A scheduled thread pool constructo." + ([] (Executors/newScheduledThreadPool (int 1))) + ([n] (Executors/newScheduledThreadPool (int n))) + ([n opts] + (let [factory (resolve-thread-factory opts)] + (Executors/newScheduledThreadPool (int n) factory)))) + +;; --- Public Api (Task Execution) + +(defn execute + "Execute a task in a provided executor. + + A task is a plain clojure function or + jvm Runnable instance." + ([task] + (-> (common-pool) + (-execute task))) + ([executor task] + (-execute executor task))) + +(defn submit + "Submit a task to be executed in a provided executor + and return a promise that will be completed with + the return value of a task. + + A task is a plain clojure function." + ([task] + (-> (common-pool) + (-submit task))) + ([executor task] + (-submit executor task))) + +(defn schedule + "Schedule task exection for some time in the future." + ([ms task] + (-> (common-pool) + (-schedule ms task))) + ([executor ms task] + (-schedule executor ms task))) diff --git a/backend/vendor/storages/core.clj b/backend/vendor/storages/core.clj new file mode 100644 index 000000000..01aad5fcf --- /dev/null +++ b/backend/vendor/storages/core.clj @@ -0,0 +1,59 @@ +;; 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) 2016 Andrey Antukh + +(ns storages.core + "A storages abstraction layer." + (:require [storages.proto :as pt] + [storages.impl])) + +(defn save + "Perists a file or bytes in the storage. This function + returns a relative path where file is saved. + + The final file path can be different to the one provided + to this function and the behavior is totally dependen on + the storage implementation." + [storage path content] + (pt/-save storage path content)) + +(defn lookup + "Resolve provided relative path in the storage and return + the local filesystem absolute path to it. + This method may be not implemented in all storages." + [storage path] + {:pre [(satisfies? pt/ILocalStorage storage)]} + (pt/-lookup storage path)) + +(defn exists? + "Check if a relative `path` exists in the storage." + [storage path] + (pt/-exists? storage path)) + +(defn delete + "Delete a file from the storage." + [storage path] + (pt/-delete storage path)) + +(defn clear! + "Clear all contents of the storage." + [storage] + (pt/-clear storage)) + +(defn path + "Create path from string or more than one string." + ([fst] + (pt/-path fst)) + ([fst & more] + (pt/-path (cons fst more)))) + +(defn public-url + [storage path] + (pt/-public-uri storage path)) + +(defn storage? + "Return `true` if `v` implements IStorage protocol" + [v] + (satisfies? pt/IStorage v)) diff --git a/backend/vendor/storages/fs/local.clj b/backend/vendor/storages/fs/local.clj new file mode 100644 index 000000000..a6d964cde --- /dev/null +++ b/backend/vendor/storages/fs/local.clj @@ -0,0 +1,104 @@ +;; 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) 2016 Andrey Antukh + +(ns storages.fs.local + "A local filesystem storage implementation." + (:require [promesa.core :as p] + [clojure.java.io :as io] + [executors.core :as exec] + [storages.proto :as pt] + [storages.impl :as impl] + [storages.util :as util]) + (:import java.io.InputStream + java.io.OutputStream + java.net.URI + java.nio.file.Path + java.nio.file.Files)) + +(defn normalize-path + [^Path base ^Path path] + (if (util/absolute? path) + (throw (ex-info "Suspicios operation: absolute path not allowed." + {:path (str path)})) + (let [^Path fullpath (.resolve base path) + ^Path fullpath (.normalize fullpath)] + (when-not (.startsWith fullpath base) + (throw (ex-info "Suspicios operation: go to parent dir is not allowed." + {:path (str path)}))) + fullpath))) + +(defn- save + [base path content] + (let [^Path path (pt/-path path) + ^Path fullpath (normalize-path base path)] + (when-not (util/exists? (.getParent fullpath)) + (util/create-dir! (.getParent fullpath))) + (with-open [^InputStream source (pt/-input-stream content) + ^OutputStream dest (Files/newOutputStream + fullpath util/write-open-opts)] + (io/copy source dest) + path))) + +(defn- delete + [base path] + (let [path (->> (pt/-path path) + (normalize-path base))] + (Files/deleteIfExists ^Path path))) + +(defrecord FileSystemStorage [^Path base ^URI baseuri] + pt/IPublicStorage + (-public-uri [_ path] + (.resolve baseuri (str path))) + + pt/IStorage + (-save [_ path content] + (exec/submit (partial save base path content))) + + (-delete [_ path] + (exec/submit (partial delete base path))) + + (-exists? [this path] + (try + (p/resolved + (let [path (->> (pt/-path path) + (normalize-path base))] + (util/exists? path))) + (catch Exception e + (p/rejected e)))) + + pt/IClearableStorage + (-clear [_] + (util/delete-dir! base) + (util/create-dir! base)) + + pt/ILocalStorage + (-lookup [_ path'] + (try + (p/resolved + (->> (pt/-path path') + (normalize-path base))) + (catch Exception e + (p/rejected e))))) + +(defn filesystem + "Create an instance of local FileSystem storage providing an + absolute base path. + + If that path does not exists it will be automatically created, + if it exists but is not a directory, an exception will be + raised." + [{:keys [basedir baseuri] :as keys}] + (let [^Path basepath (pt/-path basedir) + ^URI baseuri (pt/-uri baseuri)] + (when (and (util/exists? basepath) + (not (util/directory? basepath))) + (throw (ex-info "File already exists." {}))) + + (when-not (util/exists? basepath) + (util/create-dir! basepath)) + + (->FileSystemStorage basepath baseuri))) + diff --git a/backend/vendor/storages/fs/misc.clj b/backend/vendor/storages/fs/misc.clj new file mode 100644 index 000000000..4dab9bf1f --- /dev/null +++ b/backend/vendor/storages/fs/misc.clj @@ -0,0 +1,111 @@ +;; 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) 2016 Andrey Antukh + +(ns storages.fs.misc + "A local filesystem storage implementation." + (:require [promesa.core :as p] + [cuerdas.core :as str] + [buddy.core.codecs :as codecs] + [buddy.core.codecs.base64 :as b64] + [buddy.core.nonce :as nonce] + [buddy.core.hash :as hash] + [storages.proto :as pt] + [storages.impl :as impl] + [storages.fs.local :as localfs]) + (:import java.io.InputStream + java.io.OutputStream + java.nio.file.Path + java.nio.file.Files)) + +;; --- Scoped Storage + +(defrecord ScopedPathStorage [storage ^Path prefix] + pt/IPublicStorage + (-public-uri [_ path] + (let [^Path path (pt/-path [prefix path])] + (pt/-public-uri storage path))) + + pt/IStorage + (-save [_ path content] + (let [^Path path (pt/-path [prefix path])] + (->> (pt/-save storage path content) + (p/map (fn [^Path path] + (.relativize prefix path)))))) + + (-delete [_ path] + (let [^Path path (pt/-path [prefix path])] + (pt/-delete storage path))) + + (-exists? [this path] + (let [^Path path (pt/-path [prefix path])] + (pt/-exists? storage path))) + + pt/ILocalStorage + (-lookup [_ path] + (->> (pt/-lookup storage "") + (p/map (fn [^Path base] + (let [base (pt/-path [base prefix])] + (->> (pt/-path path) + (localfs/normalize-path base)))))))) + +(defn scoped + "Create a composed storage instance that automatically prefixes + the path when content is saved. For the rest of methods it just + relies to the underlying storage. + + This is usefull for atomatically add sertain prefix to some + uploads." + [storage prefix] + (let [prefix (pt/-path prefix)] + (->ScopedPathStorage storage prefix))) + +;; --- Hashed Storage + +(defn- generate-path + [^Path path] + (let [name (str (.getFileName path)) + hash (-> (nonce/random-nonce 128) + (hash/blake2b-256) + (b64/encode true) + (codecs/bytes->str)) + tokens (re-seq #"[\w\d\-\_]{3}" hash) + path-tokens (take 6 tokens) + rest-tokens (drop 6 tokens) + path (pt/-path path-tokens) + frest (apply str rest-tokens)] + (pt/-path (list path frest name)))) + +(defrecord HashedStorage [storage] + pt/IPublicStorage + (-public-uri [_ path] + (pt/-public-uri storage path)) + + pt/IStorage + (-save [_ path content] + (let [^Path path (pt/-path path) + ^Path path (generate-path path)] + (pt/-save storage path content))) + + (-delete [_ path] + (pt/-delete storage path)) + + (-exists? [this path] + (pt/-exists? storage path)) + + pt/ILocalStorage + (-lookup [_ path] + (pt/-lookup storage path))) + +(defn hashed + "Create a composed storage instance that uses random + hash based directory tree distribution for the final + file path. + + This is usefull when you want to store files with + not predictable uris." + [storage] + (->HashedStorage storage)) + diff --git a/backend/vendor/storages/impl.clj b/backend/vendor/storages/impl.clj new file mode 100644 index 000000000..2ffdc0d70 --- /dev/null +++ b/backend/vendor/storages/impl.clj @@ -0,0 +1,115 @@ +;; 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) 2016 Andrey Antukh + +(ns storages.impl + "Implementation details and helpers." + (:require [storages.proto :as pt] + [storages.util :as util] + [buddy.core.codecs :as codecs] + [clojure.java.io :as io]) + (:import java.io.File + java.io.ByteArrayInputStream + java.io.InputStream + java.net.URL + java.net.URI + java.nio.file.Path + java.nio.file.Paths + java.nio.file.Files)) + +(extend-protocol pt/IContent + String + (-input-stream [v] + (ByteArrayInputStream. (codecs/str->bytes v))) + + Path + (-input-stream [v] + (io/input-stream v)) + + File + (-input-stream [v] + (io/input-stream v)) + + URI + (-input-stream [v] + (io/input-stream v)) + + URL + (-input-stream [v] + (io/input-stream v)) + + InputStream + (-input-stream [v] + v) + + ratpack.http.TypedData + (-input-stream [this] + (.getInputStream this))) + +(extend-protocol pt/IUri + URI + (-uri [v] v) + + String + (-uri [v] (URI. v))) + +(def ^:private empty-string-array + (make-array String 0)) + +(extend-protocol pt/IPath + Path + (-path [v] v) + + URI + (-path [v] (Paths/get v)) + + URL + (-path [v] (Paths/get (.toURI v))) + + String + (-path [v] (Paths/get v empty-string-array)) + + clojure.lang.Sequential + (-path [v] + (reduce #(.resolve %1 %2) + (pt/-path (first v)) + (map pt/-path (rest v))))) + +(defn- path->input-stream + [^Path path] + (Files/newInputStream path util/read-open-opts)) + +(defn- path->output-stream + [^Path path] + (Files/newOutputStream path util/write-open-opts)) + +(extend-type Path + io/IOFactory + (make-reader [path opts] + (let [^InputStream is (path->input-stream path)] + (io/make-reader is opts))) + (make-writer [path opts] + (let [^OutputStream os (path->output-stream path)] + (io/make-writer os opts))) + (make-input-stream [path opts] + (let [^InputStream is (path->input-stream path)] + (io/make-input-stream is opts))) + (make-output-stream [path opts] + (let [^OutputStream os (path->output-stream path)] + (io/make-output-stream os opts)))) + +(extend-type ratpack.http.TypedData + io/IOFactory + (make-reader [td opts] + (let [^InputStream is (.getInputStream td)] + (io/make-reader is opts))) + (make-writer [path opts] + (throw (UnsupportedOperationException. "read only object"))) + (make-input-stream [td opts] + (let [^InputStream is (.getInputStream td)] + (io/make-input-stream is opts))) + (make-output-stream [path opts] + (throw (UnsupportedOperationException. "read only object")))) + diff --git a/backend/vendor/storages/proto.clj b/backend/vendor/storages/proto.clj new file mode 100644 index 000000000..c80e6548a --- /dev/null +++ b/backend/vendor/storages/proto.clj @@ -0,0 +1,38 @@ +;; 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) 2016 Andrey Antukh + +(ns storages.proto + "A storage abstraction definition.") + +(defprotocol IUri + (-uri [_] "Coerce to uri.")) + +(defprotocol IPath + (-path [_] "Coerce to path.")) + +(defprotocol IContent + (-input-stream [_] "Coerce to input stream.")) + +(defprotocol IStorage + "A basic abstraction for storage access." + (-save [_ path content] "Persist the content under specified path.") + (-delete [_ path] "Delete the file by its path.") + (-exists? [_ path] "Check if file exists by path.")) + +(defprotocol IClearableStorage + (-clear [_] "clear all contents of the storage")) + +(defprotocol IPublicStorage + (-public-uri [_ path] "Get a public accessible uri for path.")) + +(defprotocol ILocalStorage + (-lookup [_ path] "Resolves the path to the local filesystem.")) + +(defprotocol IStorageIntrospection + (-accessed-time [_ path] "Return the last accessed time of the file.") + (-created-time [_ path] "Return the creation time of the file.") + (-modified-time [_ path] "Return the last modified time of the file.")) + diff --git a/backend/vendor/storages/util.clj b/backend/vendor/storages/util.clj new file mode 100644 index 000000000..0d367a398 --- /dev/null +++ b/backend/vendor/storages/util.clj @@ -0,0 +1,140 @@ +;; 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) 2016 Andrey Antukh + +(ns storages.util + "FileSystem related utils." + (:refer-clojure :exclude [name]) + (:require [storages.proto :as pt]) + (:import java.nio.file.Path + java.nio.file.Files + java.nio.file.LinkOption + java.nio.file.OpenOption + java.nio.file.StandardOpenOption + java.nio.file.SimpleFileVisitor + java.nio.file.FileVisitResult + java.nio.file.attribute.FileAttribute + java.nio.file.attribute.PosixFilePermissions + ratpack.form.UploadedFile)) + +;; --- Constants + +(def write-open-opts + (->> [#_StandardOpenOption/CREATE_NEW + StandardOpenOption/CREATE + StandardOpenOption/WRITE] + (into-array OpenOption))) + +(def read-open-opts + (->> [StandardOpenOption/READ] + (into-array OpenOption))) + +(def follow-link-opts + (into-array LinkOption [LinkOption/NOFOLLOW_LINKS])) + +;; --- Path Helpers + +(defn path + "Create path from string or more than one string." + ([fst] + (pt/-path fst)) + ([fst & more] + (pt/-path (cons fst more)))) + +(defn make-file-attrs + "Generate a array of `FileAttribute` instances + generated from `rwxr-xr-x` kind of expressions." + [^String expr] + (let [perms (PosixFilePermissions/fromString expr) + attr (PosixFilePermissions/asFileAttribute perms)] + (into-array FileAttribute [attr]))) + +(defn path? + "Return `true` if provided value is an instance of Path." + [v] + (instance? Path v)) + +(defn absolute? + "Return `true` if the provided path is absolute, `else` in case contrary. + The `path` parameter can be anything convertible to path instance." + [path] + (let [^Path path (pt/-path path)] + (.isAbsolute path))) + +(defn exists? + "Return `true` if the provided path exists, `else` in case contrary. + The `path` parameter can be anything convertible to path instance." + [path] + (let [^Path path (pt/-path path)] + (Files/exists path follow-link-opts))) + +(defn directory? + "Return `true` if the provided path is a directory, `else` in case contrary. + The `path` parameter can be anything convertible to path instance." + [path] + (let [^Path path (pt/-path path)] + (Files/isDirectory path follow-link-opts))) + +(defn parent + "Get parent path if it exists." + [path] + (.getParent ^Path (pt/-path path))) + +(defn base-name + "Get the file name." + [path] + (if (instance? UploadedFile path) + (.getFileName ^UploadedFile path) + (str (.getFileName ^Path (pt/-path path))))) + +(defn split-ext + "Returns a vector of `[name extension]`." + [path] + (let [base (base-name path) + i (.lastIndexOf base ".")] + (if (pos? i) + [(subs base 0 i) (subs base i)] + [base nil]))) + +(defn extension + "Return the extension part of a file." + [path] + (last (split-ext path))) + +(defn name + "Return the name part of a file." + [path] + (first (split-ext path))) + +(defn list-directory + [path] + (let [path (pt/-path path)] + (with-open [stream (Files/newDirectoryStream path)] + (vec stream)))) + +(defn list-files + [path] + (filter (complement directory?) (list-directory path))) + +;; --- Side-Effectfull Operations + +(defn create-dir! + "Create a new directory." + [path] + (let [^Path path (pt/-path path) + attrs (make-file-attrs "rwxr-xr-x")] + (Files/createDirectories path attrs))) + +(defn delete-dir! + [path] + (let [path (pt/-path path) + visitor (proxy [SimpleFileVisitor] [] + (visitFile [file attrs] + (Files/delete file) + FileVisitResult/CONTINUE) + (postVisitDirectory [dir exc] + (Files/delete dir) + FileVisitResult/CONTINUE))] + (Files/walkFileTree path visitor)))