diff --git a/backend/resources/log4j2.xml b/backend/resources/log4j2.xml index 74a3f2508b..865900cab9 100644 --- a/backend/resources/log4j2.xml +++ b/backend/resources/log4j2.xml @@ -5,6 +5,7 @@ + diff --git a/backend/resources/migrations/0001.main.up.sql b/backend/resources/migrations/0001.main.sql similarity index 100% rename from backend/resources/migrations/0001.main.up.sql rename to backend/resources/migrations/0001.main.sql diff --git a/backend/resources/migrations/0002.auth.up.sql b/backend/resources/migrations/0002.users.sql similarity index 70% rename from backend/resources/migrations/0002.auth.up.sql rename to backend/resources/migrations/0002.users.sql index 7a26c9e612..8833f7821e 100644 --- a/backend/resources/migrations/0002.auth.up.sql +++ b/backend/resources/migrations/0002.users.sql @@ -13,6 +13,28 @@ CREATE TABLE users ( metadata bytea NOT NULL ); +CREATE TABLE IF NOT EXISTS user_storage ( + user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, + + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + modified_at timestamptz NOT NULL DEFAULT clock_timestamp(), + + key text NOT NULL, + val bytea NOT NULL, + + PRIMARY KEY (key, user_id) +); + +CREATE TABLE user_tokens ( + user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, + token text NOT NULL, + + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + used_at timestamptz DEFAULT NULL, + + PRIMARY KEY (token, user_id) +); + CREATE TABLE sessions ( id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), @@ -24,6 +46,7 @@ CREATE TABLE sessions ( ); -- Insert a placeholder system user. + INSERT INTO users (id, fullname, username, email, photo, password, metadata) VALUES ('00000000-0000-0000-0000-000000000000'::uuid, 'System User', @@ -44,18 +67,6 @@ CREATE UNIQUE INDEX users_email_idx 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_id 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_id); - -CREATE UNIQUE INDEX user_pswd_recovery_token_idx - ON user_pswd_recovery USING btree (token); +CREATE TRIGGER user_storage_modified_at_tgr BEFORE UPDATE ON user_storage + FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); diff --git a/backend/resources/migrations/0003.projects.up.sql b/backend/resources/migrations/0003.projects.sql similarity index 100% rename from backend/resources/migrations/0003.projects.up.sql rename to backend/resources/migrations/0003.projects.sql diff --git a/backend/resources/migrations/0004.pages.up.sql b/backend/resources/migrations/0004.pages.sql similarity index 100% rename from backend/resources/migrations/0004.pages.up.sql rename to backend/resources/migrations/0004.pages.sql diff --git a/backend/resources/migrations/0006.emails.up.sql b/backend/resources/migrations/0005.emails.sql similarity index 100% rename from backend/resources/migrations/0006.emails.up.sql rename to backend/resources/migrations/0005.emails.sql diff --git a/backend/resources/migrations/0005.kvstore.up.sql b/backend/resources/migrations/0005.kvstore.up.sql deleted file mode 100644 index 0488414aad..0000000000 --- a/backend/resources/migrations/0005.kvstore.up.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE IF NOT EXISTS kvstore ( - user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, - - created_at timestamptz NOT NULL DEFAULT clock_timestamp(), - modified_at timestamptz NOT NULL DEFAULT clock_timestamp(), - - key text NOT NULL, - value bytea NOT NULL, - - PRIMARY KEY (key, user_id) -); - -CREATE TRIGGER kvstore_modified_at_tgr BEFORE UPDATE ON kvstore - FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); diff --git a/backend/resources/migrations/0007.images.up.sql b/backend/resources/migrations/0006.images.sql similarity index 100% rename from backend/resources/migrations/0007.images.up.sql rename to backend/resources/migrations/0006.images.sql diff --git a/backend/resources/migrations/0008.icons.up.sql b/backend/resources/migrations/0007.icons.sql similarity index 100% rename from backend/resources/migrations/0008.icons.up.sql rename to backend/resources/migrations/0007.icons.sql diff --git a/backend/resources/migrations/XXXX.workers.up.sql b/backend/resources/migrations/XXXX.workers.up.sql deleted file mode 100644 index a2cea7b302..0000000000 --- a/backend/resources/migrations/XXXX.workers.up.sql +++ /dev/null @@ -1 +0,0 @@ -select version(); diff --git a/backend/src/uxbox/http.clj b/backend/src/uxbox/http.clj index 75e87000e0..e7277a27cb 100644 --- a/backend/src/uxbox/http.clj +++ b/backend/src/uxbox/http.clj @@ -16,7 +16,6 @@ [uxbox.http.session :as session] [uxbox.http.handlers :as handlers] [uxbox.http.debug :as debug] - [uxbox.services.core :as sv] [vertx.core :as vc] [vertx.http :as vh] [vertx.web :as vw] @@ -62,6 +61,7 @@ (vw/assets "/static/*" {:root "resources/public/static"}) (vw/router routes))] + (log/info "Starting http server on" (:http-server-port cfg/config) "port.") (vh/server ctx {:handler handler :port (:http-server-port cfg/config)}))) diff --git a/backend/src/uxbox/http/debug.clj b/backend/src/uxbox/http/debug.clj index d73a65778b..5aa3a3406b 100644 --- a/backend/src/uxbox/http/debug.clj +++ b/backend/src/uxbox/http/debug.clj @@ -11,7 +11,6 @@ [promesa.core :as p] [uxbox.http.errors :as errors] [uxbox.http.session :as session] - [uxbox.services.core :as sv] [uxbox.util.uuid :as uuid])) (defn emails-list diff --git a/backend/src/uxbox/http/handlers.clj b/backend/src/uxbox/http/handlers.clj index 351f481bf8..3582bed1df 100644 --- a/backend/src/uxbox/http/handlers.clj +++ b/backend/src/uxbox/http/handlers.clj @@ -9,16 +9,18 @@ [promesa.core :as p] [uxbox.emails :as emails] [uxbox.http.session :as session] - [uxbox.services.core :as sv] + [uxbox.services.init] + [uxbox.services.mutations :as sm] + [uxbox.services.queries :as sq] [uxbox.util.uuid :as uuid])) (defn query-handler [req] (let [type (get-in req [:path-params :type]) data (merge (:params req) - {::sv/type (keyword type) + {::sq/type (keyword type) :user (:user req)})] - (-> (sv/query (with-meta data {:req req})) + (-> (sq/handle (with-meta data {:req req})) (p/then' (fn [result] {:status 200 :body result}))))) @@ -29,9 +31,9 @@ data (merge (:params req) (:body-params req) (:uploads req) - {::sv/type (keyword type) + {::sm/type (keyword type) :user (:user req)})] - (-> (sv/mutation (with-meta data {:req req})) + (-> (sm/handle (with-meta data {:req req})) (p/then' (fn [result] {:status 200 :body result}))))) @@ -39,7 +41,7 @@ [req] (let [data (:body-params req) user-agent (get-in req [:headers "user-agent"])] - (-> (sv/mutation (assoc data ::sv/type :login)) + (-> (sm/handle (assoc data ::sm/type :login)) (p/then #(session/create % user-agent)) (p/then' (fn [token] {:status 204 @@ -59,9 +61,9 @@ (defn register-handler [req] (let [data (merge (:body-params req) - {::sv/type :register-profile}) + {::sm/type :register-profile}) user-agent (get-in req [:headers "user-agent"])] - (-> (sv/mutation (with-meta data {:req req})) + (-> (sm/handle (with-meta data {:req req})) (p/then (fn [{:keys [id] :as user}] (session/create id user-agent))) (p/then' (fn [token] diff --git a/backend/src/uxbox/migrations.clj b/backend/src/uxbox/migrations.clj index f69bc67d18..098d42cf82 100644 --- a/backend/src/uxbox/migrations.clj +++ b/backend/src/uxbox/migrations.clj @@ -19,28 +19,25 @@ :steps [{:desc "Initial triggers and utils." :name "0001-main" - :fn (mg/resource "migrations/0001.main.up.sql")} + :fn (mg/resource "migrations/0001.main.sql")} {:desc "Initial auth related tables" - :name "0002-auth" - :fn (mg/resource "migrations/0002.auth.up.sql")} + :name "0002-users" + :fn (mg/resource "migrations/0002.users.sql")} {:desc "Initial projects tables" :name "0003-projects" - :fn (mg/resource "migrations/0003.projects.up.sql")} + :fn (mg/resource "migrations/0003.projects.sql")} {:desc "Initial pages tables" :name "0004-pages" - :fn (mg/resource "migrations/0004.pages.up.sql")} - {:desc "Initial kvstore tables" - :name "0005-kvstore" - :fn (mg/resource "migrations/0005.kvstore.up.sql")} + :fn (mg/resource "migrations/0004.pages.sql")} {:desc "Initial emails related tables" - :name "0006-emails" - :fn (mg/resource "migrations/0006.emails.up.sql")} + :name "0005-emails" + :fn (mg/resource "migrations/0005.emails.sql")} {:desc "Initial images tables" - :name "0007-images" - :fn (mg/resource "migrations/0007.images.up.sql")} + :name "0006-images" + :fn (mg/resource "migrations/0006.images.sql")} {:desc "Initial icons tables" - :name "0008-icons" - :fn (mg/resource "migrations/0008.icons.up.sql")} + :name "0007-icons" + :fn (mg/resource "migrations/0007.icons.sql")} ]}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/backend/src/uxbox/portation.clj b/backend/src/uxbox/portation.clj deleted file mode 100644 index 5d69863dc7..0000000000 --- a/backend/src/uxbox/portation.clj +++ /dev/null @@ -1,111 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 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] - [datoteka.core :as fs] - [uxbox.db :as db] - [uxbox.util.uuid :as uuid] - [uxbox.util.closeable :refer (with-open)] - [uxbox.util.transit :as t])) - -;; ;; --- 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 (fs/create-tempfile)] -;; (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 (fs/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/services/core.clj b/backend/src/uxbox/services/core.clj deleted file mode 100644 index 81dafe6c95..0000000000 --- a/backend/src/uxbox/services/core.clj +++ /dev/null @@ -1,71 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns uxbox.services.core - (:require - [clojure.tools.logging :as log] - [promesa.core :as p] - [vertx.core :as vc] - [uxbox.core :refer [system]] - [uxbox.util.uuid :as uuid] - [uxbox.util.dispatcher :as uds] - [uxbox.util.exceptions :as ex]) - (:import - java.util.Map - java.util.List - java.util.Map$Entry - java.util.HashMap)) - -;; (def context-interceptor -;; {:enter (fn [data] -;; (update data :request assoc ::ctx (vc/get-or-create-context system)))}) - -(def logging-interceptor - {:enter (fn [data] - (let [type (get-in data [:request ::type])] - (assoc data ::start-time (System/nanoTime)))) - :leave (fn [data] - (let [elapsed (- (System/nanoTime) (::start-time data)) - elapsed (str (quot elapsed 1000000) "ms") - type (get-in data [:request ::type])] - (log/info "service" type "processed in" elapsed) - data))}) - - -(uds/defservice query - {:dispatch-by ::type - :interceptors [uds/spec-interceptor - logging-interceptor - #_context-interceptor]}) - -(uds/defservice mutation - {:dispatch-by ::type - :interceptors [uds/spec-interceptor - #_context-interceptor]}) - -;; --- Helpers - -(defmacro defmutation - [key & rest] - `(uds/defmethod mutation ~key ~@rest)) - -(defmacro defquery - [key & rest] - `(uds/defmethod query ~key ~@rest)) - -(defn raise-not-found-if-nil - [v] - (if (nil? v) - (ex/raise :type :not-found - :hint "Object doest not exists.") - v)) - -(def constantly-nil (constantly nil)) - -(defn handle-on-context - [p] - (->> (vc/get-or-create-context system) - (vc/handle-on-context p))) diff --git a/backend/src/uxbox/services/init.clj b/backend/src/uxbox/services/init.clj new file mode 100644 index 0000000000..e3a846d2d3 --- /dev/null +++ b/backend/src/uxbox/services/init.clj @@ -0,0 +1,35 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2019 Andrey Antukh + +(ns uxbox.services.init + "A initialization of services." + (:require + [mount.core :as mount :refer [defstate]])) + +(defn- load-query-services + [] + (require 'uxbox.services.queries.icons) + (require 'uxbox.services.queries.images) + (require 'uxbox.services.queries.pages) + (require 'uxbox.services.queries.profiles) + (require 'uxbox.services.queries.projects) + (require 'uxbox.services.queries.user-storage)) + +(defn- load-mutation-services + [] + (require 'uxbox.services.mutations.auth) + (require 'uxbox.services.mutations.icons) + (require 'uxbox.services.mutations.images) + (require 'uxbox.services.mutations.projects) + (require 'uxbox.services.mutations.pages) + (require 'uxbox.services.mutations.profiles) + (require 'uxbox.services.mutations.user-storage)) + +(defstate query-services + :start (load-query-services)) + +(defstate mutation-services + :start (load-mutation-services)) diff --git a/backend/src/uxbox/services/kvstore.clj b/backend/src/uxbox/services/kvstore.clj deleted file mode 100644 index 7fee6d330e..0000000000 --- a/backend/src/uxbox/services/kvstore.clj +++ /dev/null @@ -1,70 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns uxbox.services.kvstore - (:require - [clojure.spec.alpha :as s] - [promesa.core :as p] - [uxbox.db :as db] - [uxbox.services.core :as sv] - [uxbox.util.blob :as blob] - [uxbox.util.data :as data] - [uxbox.util.spec :as us] - [uxbox.util.time :as dt] - [uxbox.util.uuid :as uuid])) - -(defn- decode-row - [{:keys [value] :as row}] - (when row - (cond-> row - value (assoc :value (blob/decode value))))) - -;; --- Update KVStore - -(s/def ::user ::us/uuid) -(s/def ::key ::us/string) -(s/def ::value any?) - -(s/def ::upsert-kvstore - (s/keys :req-un [::key ::value ::user])) - -(sv/defmutation ::upsert-kvstore - [{:keys [key value user] :as params}] - (let [sql "insert into kvstore (key, value, user_id) - values ($1, $2, $3) - on conflict (user_id, key) - do update set value = $2" - val (blob/encode value)] - (-> (db/query-one db/pool [sql key val user]) - (p/then' sv/constantly-nil)))) - -;; --- Retrieve KVStore - -(s/def ::kvstore-entry - (s/keys :req-un [::key ::user])) - -(sv/defquery ::kvstore-entry - [{:keys [key user]}] - (let [sql "select kv.* - from kvstore as kv - where kv.user_id = $2 - and kv.key = $1"] - (-> (db/query-one db/pool [sql key user]) - (p/then' sv/raise-not-found-if-nil) - (p/then' decode-row)))) - -;; --- Delete KVStore - -(s/def ::delete-kvstore - (s/keys :req-un [::key ::user])) - -(sv/defmutation ::delete-kvstore - [{:keys [user key] :as params}] - (let [sql "delete from kvstore - where user_id = $2 - and key = $1"] - (-> (db/query-one db/pool [sql key user]) - (p/then' sv/constantly-nil)))) diff --git a/backend/src/uxbox/services/mutations.clj b/backend/src/uxbox/services/mutations.clj new file mode 100644 index 0000000000..f62a08d898 --- /dev/null +++ b/backend/src/uxbox/services/mutations.clj @@ -0,0 +1,19 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2019 Andrey Antukh + +(ns uxbox.services.mutations + (:require + [uxbox.util.dispatcher :as uds])) + +(uds/defservice handle + {:dispatch-by ::type + :interceptors [uds/spec-interceptor + #_logging-interceptor + #_context-interceptor]}) + +(defmacro defmutation + [key & rest] + `(uds/defmethod handle ~key ~@rest)) diff --git a/backend/src/uxbox/services/auth.clj b/backend/src/uxbox/services/mutations/auth.clj similarity index 89% rename from backend/src/uxbox/services/auth.clj rename to backend/src/uxbox/services/mutations/auth.clj index 292cf36dd1..196340e208 100644 --- a/backend/src/uxbox/services/auth.clj +++ b/backend/src/uxbox/services/mutations/auth.clj @@ -4,33 +4,32 @@ ;; ;; Copyright (c) 2019 Andrey Antukh -(ns uxbox.services.auth +(ns uxbox.services.mutations.auth (:require [clojure.spec.alpha :as s] [buddy.hashers :as hashers] [promesa.core :as p] [uxbox.config :as cfg] [uxbox.db :as db] - [uxbox.services.core :as sc] - [uxbox.services.users :as users] + [uxbox.services.mutations :as sm] [uxbox.util.spec :as us] [uxbox.util.exceptions :as ex])) -(s/def ::username ::us/string) -(s/def ::password ::us/string) -(s/def ::scope ::us/string) - -(s/def ::login-params - (s/keys :req-un [::username ::password] - :opt-un [::scope])) - (def ^:private user-by-username-sql "select id, password from users where username=$1 or email=$1 and deleted_at is null") -(sc/defmutation :login +(s/def ::username ::us/string) +(s/def ::password ::us/string) +(s/def ::scope ::us/string) + +(s/def ::login + (s/keys :req-un [::username ::password] + :opt-un [::scope])) + +(sm/defmutation ::login {:doc "User login" :spec ::login-params} [{:keys [username password scope] :as params}] @@ -44,7 +43,7 @@ (when-not (check-password user password) (ex/raise :type :validation :code ::wrong-credentials)) - (:id user))] + {:id (:id user)})] (-> (db/query-one db/pool [user-by-username-sql username]) (p/then' check-user)))) diff --git a/backend/src/uxbox/services/icons.clj b/backend/src/uxbox/services/mutations/icons.clj similarity index 57% rename from backend/src/uxbox/services/icons.clj rename to backend/src/uxbox/services/mutations/icons.clj index 8348b1dd61..edc1b0330e 100644 --- a/backend/src/uxbox/services/icons.clj +++ b/backend/src/uxbox/services/mutations/icons.clj @@ -4,15 +4,15 @@ ;; ;; Copyright (c) 2019 Andrey Antukh -(ns uxbox.services.icons - "Icons library related services." +(ns uxbox.services.mutations.icons (:require [clojure.spec.alpha :as s] [promesa.core :as p] [uxbox.db :as db] - [uxbox.services.core :as sv] + [uxbox.services.mutations :as sm] + [uxbox.services.util :as su] + [uxbox.services.queries.icons :refer [decode-icon-row]] [uxbox.util.blob :as blob] - [uxbox.util.exceptions :as ex] [uxbox.util.spec :as us] [uxbox.util.uuid :as uuid])) @@ -36,75 +36,13 @@ (s/def ::metadata (s/keys :opt-un [::width ::height ::view-box ::mimetype])) -(defn- decode-icon-row - [{:keys [metadata] :as row}] - (when row - (cond-> row - metadata (assoc :metadata (blob/decode metadata))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Queries -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; --- Query: Collections - -(def ^:private icons-collections-sql - "select *, - (select count(*) from icons where collection_id = ic.id) as num_icons - from icons_collections as ic - where (ic.user_id = $1 or - ic.user_id = '00000000-0000-0000-0000-000000000000'::uuid) - and ic.deleted_at is null - order by ic.created_at desc") - -(s/def ::icons-collections - (s/keys :req-un [::user])) - -(sv/defquery :icons-collections - {:doc "Retrieve all icons collections for current user." - :spec ::icons-collections} - [{:keys [user] :as params}] - (let [sqlv [icons-collections-sql user]] - (db/query db/pool sqlv))) - -;; --- List Icons - -(def ^:private icons-by-collection-sql - "select * - from icons as i - where (i.user_id = $1 or - i.user_id = '00000000-0000-0000-0000-000000000000'::uuid) - and i.deleted_at is null - and case when $2::uuid is null then i.collection_id is null - else i.collection_id = $2::uuid - end - order by i.created_at desc") - -(s/def ::icons-by-collection - (s/keys :req-un [::user] - :opt-un [::collection-id])) - -(sv/defquery :icons-by-collection - {:doc "Retrieve icons for specified collection." - :spec ::icons-by-collection} - [{:keys [user collection-id] :as params}] - (let [sqlv [icons-by-collection-sql user collection-id]] - (-> (db/query db/pool sqlv) - (p/then' #(mapv decode-icon-row %))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Mutations -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; --- Mutation: Create Collection (s/def ::create-icons-collection (s/keys :req-un [::user ::name] :opt-un [::id])) -(sv/defmutation :create-icons-collection - {:doc "Create a new collection of icons." - :spec ::create-icons-collection} +(sm/defmutation ::create-icons-collection [{:keys [id user name] :as params}] (let [id (or id (uuid/next)) sql "insert into icons_collections (id, user_id, name) @@ -116,9 +54,7 @@ (s/def ::update-icons-collection (s/keys :req-un [::user ::name ::id])) -(sv/defmutation :update-icons-collection - {:doc "Update a collection of icons." - :spec ::update-icons-collection} +(sm/defmutation ::update-icons-collection [{:keys [id user name] :as params}] (let [sql "update icons_collections set name = $3 @@ -126,7 +62,7 @@ and user_id = $2 returning *"] (-> (db/query-one db/pool [sql id user name]) - (p/then' sv/raise-not-found-if-nil)))) + (p/then' su/raise-not-found-if-nil)))) ;; --- Copy Icon @@ -140,14 +76,12 @@ and (user_id = $2 or user_id = '00000000-0000-0000-0000-000000000000'::uuid)"] (-> (db/query-one conn [sql id user]) - (p/then' sv/raise-not-found-if-nil)))) + (p/then' su/raise-not-found-if-nil)))) (s/def ::copy-icon (s/keys :req-un [:us/id ::collection-id ::user])) -(sv/defmutation :copy-icon - {:doc "Copy an icon from one collection to other." - :spec ::copy-icon} +(sm/defmutation ::copy-icon [{:keys [user id collection-id] :as params}] (db/with-atomic [conn db/pool] (-> (retrieve-icon conn {:user user :id id}) @@ -161,9 +95,7 @@ (s/def ::delete-icons-collection (s/keys :req-un [::user ::id])) -(sv/defmutation :delete-icons-collection - {:doc "Delete a collection of icons." - :spec ::delete-icons-collection} +(sm/defmutation ::delete-icons-collection [{:keys [user id] :as params}] (let [sql "update icons_collections set deleted_at = clock_timestamp() @@ -171,8 +103,8 @@ and user_id = $2 returning id"] (-> (db/query-one db/pool [sql id user]) - (p/then' sv/raise-not-found-if-nil) - (p/then' sv/constantly-nil)))) + (p/then' su/raise-not-found-if-nil) + (p/then' su/constantly-nil)))) ;; --- Mutation: Create Icon (Upload) @@ -194,9 +126,7 @@ (s/keys :req-un [::user ::name ::metadata ::content] :opt-un [::id ::collection-id])) -(sv/defmutation :create-icon - {:doc "Create (upload) a new icon." - :spec ::create-icon} +(sm/defmutation ::create-icon [params] (create-icon db/pool params)) @@ -205,9 +135,7 @@ (s/def ::update-icon (s/keys :req-un [::id ::user ::name ::collection-id])) -(sv/defmutation :update-icon - {:doc "Update an icon entry." - :spec ::update-icon} +(sm/defmutation ::update-icon [{:keys [id name user collection-id] :as params}] (let [sql "update icons set name = $1, @@ -216,16 +144,14 @@ and user_id = $4 returning *"] (-> (db/query-one db/pool [sql name collection-id id user]) - (p/then' sv/raise-not-found-if-nil)))) + (p/then' su/raise-not-found-if-nil)))) ;; --- Mutation: Delete Icon (s/def ::delete-icon (s/keys :req-un [::user ::id])) -(sv/defmutation :delete-icon - {:doc "Delete an icon entry." - :spec ::delete-icon} +(sm/defmutation ::delete-icon [{:keys [id user] :as params}] (let [sql "update icons set deleted_at = clock_timestamp() @@ -233,5 +159,5 @@ and user_id = $2 returning id"] (-> (db/query-one db/pool [sql id user]) - (p/then' sv/raise-not-found-if-nil) - (p/then' sv/constantly-nil)))) + (p/then' su/raise-not-found-if-nil) + (p/then' su/constantly-nil)))) diff --git a/backend/src/uxbox/services/images.clj b/backend/src/uxbox/services/mutations/images.clj similarity index 66% rename from backend/src/uxbox/services/images.clj rename to backend/src/uxbox/services/mutations/images.clj index f5dbd83120..4b89383dda 100644 --- a/backend/src/uxbox/services/images.clj +++ b/backend/src/uxbox/services/mutations/images.clj @@ -4,8 +4,7 @@ ;; ;; Copyright (c) 2019 Andrey Antukh -(ns uxbox.services.images - "Images library related services." +(ns uxbox.services.mutations.images (:require [clojure.spec.alpha :as s] [datoteka.core :as fs] @@ -15,7 +14,8 @@ [uxbox.db :as db] [uxbox.media :as media] [uxbox.images :as images] - [uxbox.services.core :as sc] + [uxbox.services.mutations :as sm] + [uxbox.services.util :as su] [uxbox.util.blob :as blob] [uxbox.util.data :as data] [uxbox.util.exceptions :as ex] @@ -35,7 +35,7 @@ [row] (let [opts +thumbnail-options+] (-> (px/submit! #(images/populate-thumbnails row opts)) - (sc/handle-on-context)))) + (su/handle-on-context)))) (defn- populate-thumbnails [rows] @@ -58,9 +58,7 @@ (s/keys :req-un [::user ::us/name] :opt-un [::id])) -(sc/defmutation :create-image-collection - {:doc "Create image collection" - :spec ::create-image-collection} +(sm/defmutation ::create-image-collection [{:keys [id user name] :as params}] (let [sql "insert into images_collections (id, user_id, name) values ($1, $2, $3) returning *;"] @@ -71,9 +69,7 @@ (s/def ::update-images-collection (s/keys :req-un [::id ::user ::us/name])) -(sc/defmutation :update-images-collection - {:doc "Update image collection." - :spec ::update-images-collection} +(sm/defmutation ::update-images-collection [{:keys [id user name] :as params}] (let [sql "update images_collections set name = $3 @@ -82,30 +78,12 @@ returning *;"] (db/query-one db/pool [sql id user name]))) -;; --- List Collections - -(def ^:private images-collections-sql - "select *, - (select count(*) from images where collection_id = ic.id) as num_images - from images_collections as ic - where (ic.user_id = $1 or - ic.user_id = '00000000-0000-0000-0000-000000000000'::uuid) - and ic.deleted_at is null - order by ic.created_at desc;") - -(sc/defquery :images-collections - {:doc "Retrieve image collections for the current logged user"} - [{:keys [user] :as params}] - (db/query db/pool [images-collections-sql user])) - ;; --- Delete Collection (s/def ::delete-images-collection (s/keys :req-un [::user ::id])) -(sc/defmutation :delete-images-collection - {:doc "Delete an image collection" - :spec ::delete-images-collection} +(sm/defmutation ::delete-images-collection [{:keys [id user] :as params}] (let [sql "update images_collections set deleted_at = clock_timestamp() @@ -113,35 +91,16 @@ and user_id = $2 returning id"] (-> (db/query-one db/pool [sql id user]) - (p/then' sc/raise-not-found-if-nil)))) - -;; --- Retrieve Image - -(defn retrieve-image - [conn id] - (let [sql "select * from images - where id = $1 - and deleted_at is null;"] - (db/query-one conn [sql id]))) - -;; (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))) + (p/then' su/raise-not-found-if-nil)))) ;; --- Create Image (Upload) (defn- store-image-in-fs [{:keys [name path] :as upload}] - (prn "store-image-in-fs" upload) (let [filename (fs/name name) storage media/images-storage] (-> (ds/save storage filename path) - (vc/handle-on-context)))) + (su/handle-on-context)))) (def ^:private create-image-sql "insert into images (user_id, name, collection_id, path, width, height, mimetype) @@ -167,9 +126,7 @@ (s/keys :req-un [::user ::name ::file ::width ::height ::mimetype] :opt-un [::id ::collection-id])) -(sc/defmutation :create-image - {:doc "Create (upload) new image." - :spec ::create-image} +(sm/defmutation ::create-image [{:keys [file] :as params}] (when-not (valid-image-types? (:mtype file)) (ex/raise :type :validation @@ -192,9 +149,7 @@ and user_id = $4 returning *;") -(sc/defmutation :update-image - {:doc "Update a image entry." - :spec ::update-image} +(sm/defmutation ::update-image [{:keys [id name user collection-id] :as params}] (let [sql update-image-sql] (db/query-one db/pool [sql id collection-id name user]))) @@ -206,9 +161,7 @@ (s/def ::copy-image (s/keys :req-un [::id ::collection-id ::user])) -(sc/defmutation :copy-image - {:doc "Copy image from one collection to an other." - :spec ::copy-image} +(sm/defmutation ::copy-image [{:keys [user id collection-id] :as params}] (letfn [(copy-image [conn {:keys [path] :as image}] (-> (ds/lookup media/images-storage (:path image)) @@ -221,7 +174,7 @@ (db/with-atomic [conn db/pool] (-> (retrieve-image conn {:id id :user user}) - (p/then sc/raise-not-found-if-nil) + (p/then su/raise-not-found-if-nil) (p/then (partial copy-image conn)))))) ;; --- Delete Image @@ -237,9 +190,7 @@ (s/def ::delete-image (s/keys :req-un [::id ::user])) -(sc/defmutation :delete-image - {:doc "Delete image entry." - :spec ::delete-image} +(sm/defmutation ::delete-image [{:keys [user id] :as params}] (let [sql "update images set deleted_at = clock_timestamp() @@ -247,29 +198,3 @@ and user_id = $2 returning *"] (db/query-one db/pool [sql id user]))) - -;; --- Query Images by Collection (id) - -(def images-by-collection-sql - "select * from images - where (user_id = $1 or - user_id = '00000000-0000-0000-0000-000000000000'::uuid) - and deleted_at is null - and case when $2::uuid is null then collection_id is null - else collection_id = $2::uuid - end - order by created_at desc;") - -(s/def ::images-by-collection-query - (s/keys :req-un [::user] - :opt-un [::collection-id])) - -(sc/defquery :images-by-collection - {:doc "Get all images of a collection" - :spec ::images-by-collection-query} - [{:keys [user collection-id] :as params}] - (let [sqlv [images-by-collection-sql user collection-id]] - (-> (db/query db/pool sqlv) - (p/then populate-thumbnails) - (p/then #(mapv populate-urls %))))) - diff --git a/backend/src/uxbox/services/pages.clj b/backend/src/uxbox/services/mutations/pages.clj similarity index 58% rename from backend/src/uxbox/services/pages.clj rename to backend/src/uxbox/services/mutations/pages.clj index 4bfcd5abf4..16adcba3ce 100644 --- a/backend/src/uxbox/services/pages.clj +++ b/backend/src/uxbox/services/mutations/pages.clj @@ -4,22 +4,21 @@ ;; ;; Copyright (c) 2019 Andrey Antukh -(ns uxbox.services.pages +(ns uxbox.services.mutations.pages (:require [clojure.spec.alpha :as s] [promesa.core :as p] [uxbox.db :as db] [uxbox.util.spec :as us] - [uxbox.services.core :as sv] + [uxbox.services.mutations :as sm] + [uxbox.services.util :as su] + [uxbox.services.queries.pages :refer [decode-row]] [uxbox.util.sql :as sql] - [uxbox.util.time :as dt] [uxbox.util.blob :as blob] [uxbox.util.uuid :as uuid])) ;; --- Helpers & Specs -(declare decode-row) - ;; TODO: validate `:data` and `:metadata` (s/def ::id ::us/uuid) @@ -29,84 +28,23 @@ (s/def ::project-id ::us/uuid) (s/def ::metadata any?) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Queries -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; --- Query: Pages by Project - -(s/def ::pages-by-project - (s/keys :req-un [::user ::project-id])) - -(sv/defquery ::pages-by-project - [{:keys [user project-id] :as params}] - (let [sql "select pg.*, - pg.data, - pg.metadata - from pages as pg - where pg.user_id = $2 - and pg.project_id = $1 - and pg.deleted_at is null - order by pg.created_at asc;"] - (-> (db/query db/pool [sql project-id user]) - (p/then #(mapv decode-row %))))) - -;; --- Query: Page by Id - -(s/def ::page - (s/keys :req-un [::user ::id])) - -(sv/defquery ::page - [{:keys [user id] :as params}] - (let [sql "select pg.*, - pg.data, - pg.metadata - from pages as pg - where pg.user_id = $2 - and pg.id = $1 - and pg.deleted_at is null"] - (-> (db/query-one db/pool [sql id user]) - (p/then' decode-row)))) - -;; --- Query: Page History - -(s/def ::page-id ::us/uuid) -(s/def ::max ::us/integer) -(s/def ::pinned ::us/boolean) -(s/def ::since ::us/integer) - -(s/def ::page-history - (s/keys :req-un [::page-id ::user] - :opt-un [::max ::pinned ::since])) - -(sv/defquery ::page-history - [{:keys [page-id user since max pinned] :or {since Long/MAX_VALUE max 10}}] - (let [sql (-> (sql/from ["pages_history" "ph"]) - (sql/select "ph.*") - (sql/where ["ph.user_id = ?" user] - ["ph.page_id = ?" page-id] - ["ph.version < ?" since] - (when pinned - ["ph.pinned = ?" true])) - (sql/order "ph.version desc") - (sql/limit max))] - (-> (db/query db/pool (sql/fmt sql)) - (p/then (partial mapv decode-row))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Mutations -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; --- Mutation: Create Page +(declare create-page) + (s/def ::create-page (s/keys :req-un [::data ::user ::project-id ::name ::metadata] :opt-un [::id])) -(sv/defmutation ::create-page - [{:keys [id user project-id name data metadata]}] - (let [sql "insert into pages (id, user_id, project_id, name, data, metadata) - values ($1, $2, $3, $4, $5, $6) returning *" +(sm/defmutation ::create-page + [params] + (create-page db/pool params)) + +(defn create-page + [conn {:keys [id user project-id name data metadata] :as params}] + (let [sql "insert into pages (id, user_id, project_id, name, data, metadata, version) + values ($1, $2, $3, $4, $5, $6, 0) + returning *" id (or id (uuid/next)) data (blob/encode data) mdata (blob/encode metadata)] @@ -125,7 +63,7 @@ and deleted_at is null for update;"] (-> (db/query-one conn [sql id]) - (p/then' sv/raise-not-found-if-nil)))) + (p/then' su/raise-not-found-if-nil)))) (update-page [conn {:keys [id name version data metadata user]}] (let [sql "update pages @@ -136,15 +74,15 @@ where id = $5 and user_id = $6"] (-> (db/query-one conn [sql name version data metadata id user]) - (p/then' sv/constantly-nil)))) + (p/then' su/constantly-nil)))) (update-history [conn {:keys [user id version data metadata]}] (let [sql "insert into pages_history (user_id, page_id, version, data, metadata) values ($1, $2, $3, $4, $5)"] (-> (db/query-one conn [sql user id version data metadata]) - (p/then' sv/constantly-nil))))] + (p/then' su/constantly-nil))))] - (sv/defmutation ::update-page + (sm/defmutation ::update-page [{:keys [id data metadata] :as params}] (db/with-atomic [conn db/pool] (-> (select-for-update conn id) @@ -166,7 +104,7 @@ (s/def ::update-page-metadata (s/keys :req-un [::user ::project-id ::name ::metadata ::id])) -(sv/defmutation ::update-page-metadata +(sm/defmutation ::update-page-metadata [{:keys [id user project-id name metadata]}] (let [sql "update pages set name = $3, @@ -184,7 +122,7 @@ (s/def ::delete-page (s/keys :req-un [::user ::id])) -(sv/defmutation ::delete-page +(sm/defmutation ::delete-page [{:keys [id user]}] (let [sql "update pages set deleted_at = clock_timestamp() @@ -193,8 +131,8 @@ and deleted_at is null returning id"] (-> (db/query-one db/pool [sql id user]) - (p/then sv/raise-not-found-if-nil) - (p/then sv/constantly-nil)))) + (p/then su/raise-not-found-if-nil) + (p/then su/constantly-nil)))) ;; ;; --- Update Page History @@ -211,29 +149,9 @@ ;; (s/def ::update-page-history ;; (s/keys :req-un [::user ::id ::pinned ::label])) -;; (sv/defmutation :update-page-history +;; (sm/defmutation :update-page-history ;; {:doc "Update page history" ;; :spec ::update-page-history} ;; [params] ;; (with-open [conn (db/connection)] ;; (update-page-history conn params))) - -;; --- Helpers - -(defn- decode-row - [{:keys [data metadata] :as row}] - (when row - (cond-> row - data (assoc :data (blob/decode data)) - metadata (assoc :metadata (blob/decode metadata))))) - -;; select pg.* from pages as pg -;; where pg.id = :id -;; and pg.deleted_at is null; - -;; (defn get-page-by-id -;; [conn id] -;; (s/assert ::us/id id) -;; (let [sqlv (sql/get-page-by-id {:id id})] -;; (some-> (db/fetch-one conn sqlv) -;; (decode-row)))) diff --git a/backend/src/uxbox/services/users.clj b/backend/src/uxbox/services/mutations/profiles.clj similarity index 83% rename from backend/src/uxbox/services/users.clj rename to backend/src/uxbox/services/mutations/profiles.clj index a81c90dfc6..af221e7db9 100644 --- a/backend/src/uxbox/services/users.clj +++ b/backend/src/uxbox/services/mutations/profiles.clj @@ -4,7 +4,7 @@ ;; ;; Copyright (c) 2016 Andrey Antukh -(ns uxbox.services.users +(ns uxbox.services.mutations.profiles (:require [buddy.hashers :as hashers] [clojure.spec.alpha :as s] @@ -17,7 +17,12 @@ [uxbox.emails :as emails] [uxbox.images :as images] [uxbox.media :as media] - [uxbox.services.core :as sv] + [uxbox.services.mutations :as sm] + [uxbox.services.util :as su] + [uxbox.services.queries.profiles :refer [get-profile + decode-profile-row + strip-private-attrs + resolve-thumbnail]] [uxbox.util.blob :as blob] [uxbox.util.exceptions :as ex] [uxbox.util.spec :as us] @@ -27,9 +32,6 @@ ;; --- Helpers & Specs -(declare decode-profile-row) -(declare strip-private-attrs) - (s/def ::email ::us/email) (s/def ::fullname ::us/string) (s/def ::metadata any?) @@ -39,42 +41,6 @@ (s/def ::user ::us/uuid) (s/def ::username ::us/string) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Queries -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; --- Query: Profile (own) - -(defn- resolve-thumbnail - [user] - (let [opts {:src :photo - :dst :photo - :size [100 100] - :quality 90 - :format "jpg"}] - (-> (px/submit! #(images/populate-thumbnails user opts)) - (sv/handle-on-context)))) - -(defn- get-profile - [conn id] - (let [sql "select * from users where id=$1 and deleted_at is null"] - (-> (db/query-one db/pool [sql id]) - (p/then' decode-profile-row)))) - -(s/def ::profile - (s/keys :req-un [::user])) - -(sv/defquery :profile - {:doc "Retrieve the user profile." - :spec ::profile} - [{:keys [user] :as params}] - (-> (get-profile db/pool user) - (p/then' strip-private-attrs))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Mutations -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; --- Mutation: Update Profile (own) (defn- check-username-and-email! @@ -110,14 +76,14 @@ and deleted_at is null returning *"] (-> (db/query-one conn [sql id username email fullname (blob/encode metadata)]) - (p/then' sv/raise-not-found-if-nil) + (p/then' su/raise-not-found-if-nil) (p/then' decode-profile-row) (p/then' strip-private-attrs)))) (s/def ::update-profile (s/keys :req-un [::id ::username ::email ::fullname ::metadata])) -(sv/defmutation :update-profile +(sm/defmutation :update-profile {:doc "Update self profile." :spec ::update-profile} [params] @@ -144,13 +110,13 @@ and deleted_at is null returning id"] (-> (db/query-one conn [sql user password]) - (p/then' sv/raise-not-found-if-nil) - (p/then' sv/constantly-nil)))) + (p/then' su/raise-not-found-if-nil) + (p/then' su/constantly-nil)))) (s/def ::update-password (s/keys :req-un [::user ::us/password ::old-password])) -(sv/defmutation :update-password +(sm/defmutation :update-password {:doc "Update self password." :spec ::update-password} [params] @@ -168,7 +134,7 @@ (def valid-image-types? #{"image/jpeg", "image/png", "image/webp"}) -(sv/defmutation :update-profile-photo +(sm/defmutation :update-profile-photo {:doc "Update profile photo." :spec ::update-profile-photo} [{:keys [user file] :as params}] @@ -176,7 +142,7 @@ (let [filename (fs/name name) storage media/images-storage] (-> (ds/save storage filename path) - #_(sv/handle-on-context)))) + #_(su/handle-on-context)))) (update-user-photo [path] (let [sql "update users @@ -185,7 +151,7 @@ and deleted_at is null returning *"] (-> (db/query-one db/pool [sql (str path) user]) - (p/then' sv/raise-not-found-if-nil) + (p/then' su/raise-not-found-if-nil) (p/then' strip-private-attrs) (p/then resolve-thumbnail))))] @@ -216,33 +182,38 @@ :code ::username-or-email-already-exists)) params))))) -(defn- register-profile +(defn create-profile "Create the user entry on the database with limited input filling all the other fields with defaults." - [conn {:keys [username fullname email password] :as params}] - (let [metadata (blob/encode {}) + [conn {:keys [id username fullname email password metadata] :as params}] + (let [id (or id (uuid/next)) + metadata (blob/encode metadata) password (hashers/encrypt password) sqlv [create-user-sql - (uuid/next) + id fullname username email password metadata]] (-> (db/query-one conn sqlv) - (p/then' decode-profile-row) - (p/then' strip-private-attrs) - #_(p/then (fn [profile] + (p/then' decode-profile-row)))) + +(defn register-profile + [conn params] + (-> (create-profile conn params) + (p/then' strip-private-attrs) + #_(p/then (fn [profile] (-> (emails/send! {::emails/id :users/register ::emails/to (:email params) ::emails/priority :high :name (:fullname params)}) - (p/then' (constantly profile)))))))) + (p/then' (constantly profile))))))) (s/def ::register-profile (s/keys :req-un [::username ::email ::password ::fullname])) -(sv/defmutation :register-profile +(sm/defmutation :register-profile {:doc "Register new user." :spec ::register-profile} [params] @@ -366,16 +337,3 @@ ;; (some-> (db/fetch-one conn sqlv) ;; (trim-user-attrs)))) -;; --- Attrs Helpers - -(defn- decode-profile-row - [{:keys [metadata] :as row}] - (when row - (cond-> row - metadata (assoc :metadata (blob/decode metadata))))) - -(defn strip-private-attrs - "Only selects a publicy visible user attrs." - [profile] - (select-keys profile [:id :username :fullname :metadata - :email :created-at :photo])) diff --git a/backend/src/uxbox/services/mutations/projects.clj b/backend/src/uxbox/services/mutations/projects.clj new file mode 100644 index 0000000000..08c8f27fab --- /dev/null +++ b/backend/src/uxbox/services/mutations/projects.clj @@ -0,0 +1,83 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2019 Andrey Antukh + +(ns uxbox.services.mutations.projects + (:require + [clojure.spec.alpha :as s] + [promesa.core :as p] + [uxbox.db :as db] + [uxbox.util.spec :as us] + [uxbox.services.mutations :as sm] + [uxbox.services.util :as su] + [uxbox.util.blob :as blob] + [uxbox.util.uuid :as uuid])) + +;; --- Helpers & Specs + +(s/def ::id ::us/uuid) +(s/def ::name ::us/string) +(s/def ::token ::us/string) +(s/def ::user ::us/uuid) + +;; --- Mutation: Create Project + +(declare create-project) + +(s/def ::create-project + (s/keys :req-un [::user ::name] + :opt-un [::id])) + +;; TODO: create role on project creation (maybe in DB?) + +(sm/defmutation ::create-project + [{:keys [id user name] :as params}] + (let [id (or id (uuid/next)) + sql "insert into projects (id, user_id, name) + values ($1, $2, $3) returning *"] + (db/query-one db/pool [sql id user name]))) + +(defn create-project + [conn {:keys [id user name] :as params}] + (let [id (or id (uuid/next)) + sql "insert into projects (id, user_id, name) + values ($1, $2, $3) returning *"] + (db/query-one conn [sql id user name]))) + +;; --- Mutation: Update Project + +(s/def ::update-project + (s/keys :req-un [::user ::name ::id])) + +(sm/defmutation :update-project + {:doc "Update project." + :spec ::update-project} + [{:keys [id name user] :as params}] + (let [sql "update projects + set name = $3 + where id = $1 + and user_id = $2 + and deleted_at is null + returning *"] + (db/query-one db/pool [sql id user name]))) + +;; --- Mutation: Delete Project + +(s/def ::delete-project + (s/keys :req-un [::id ::user])) + +(sm/defmutation :delete-project + {:doc "Delete project" + :spec ::delete-project} + [{:keys [id user] :as params}] + (let [sql "update projects + set deleted_at = clock_timestamp() + where id = $1 + and user_id = $2 + and deleted_at is null + returning id"] + (-> (db/query-one db/pool [sql id user]) + (p/then' su/raise-not-found-if-nil) + (p/then' su/constantly-nil)))) diff --git a/backend/src/uxbox/services/mutations/user_storage.clj b/backend/src/uxbox/services/mutations/user_storage.clj new file mode 100644 index 0000000000..9328fbb5c2 --- /dev/null +++ b/backend/src/uxbox/services/mutations/user_storage.clj @@ -0,0 +1,48 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2019 Andrey Antukh + +(ns uxbox.services.mutations.user-storage + (:require + [clojure.spec.alpha :as s] + [promesa.core :as p] + [uxbox.db :as db] + [uxbox.services.mutations :as sm] + [uxbox.services.util :as su] + [uxbox.services.queries.user-storage :refer [decode-row]] + [uxbox.util.blob :as blob] + [uxbox.util.spec :as us])) + +;; --- Update + +(s/def ::user ::us/uuid) +(s/def ::key ::us/string) +(s/def ::val any?) + +(s/def ::upsert-user-storage-entry + (s/keys :req-un [::key ::val ::user])) + +(sm/defmutation ::upsert-user-storage-entry + [{:keys [key val user] :as params}] + (let [sql "insert into user_storage (key, val, user_id) + values ($1, $2, $3) + on conflict (user_id, key) + do update set val = $2" + val (blob/encode val)] + (-> (db/query-one db/pool [sql key val user]) + (p/then' su/constantly-nil)))) + +;; --- Delete KVStore + +(s/def ::delete-user-storage-entry + (s/keys :req-un [::key ::user])) + +(sm/defmutation ::delete-user-storage-entry + [{:keys [user key] :as params}] + (let [sql "delete from user_storage + where user_id = $2 + and key = $1"] + (-> (db/query-one db/pool [sql key user]) + (p/then' su/constantly-nil)))) diff --git a/backend/src/uxbox/services/projects.clj b/backend/src/uxbox/services/projects.clj deleted file mode 100644 index dba97996d0..0000000000 --- a/backend/src/uxbox/services/projects.clj +++ /dev/null @@ -1,133 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns uxbox.services.projects - (:require - [clojure.spec.alpha :as s] - [promesa.core :as p] - [uxbox.db :as db] - [uxbox.util.spec :as us] - [uxbox.services.core :as sv] - [uxbox.util.blob :as blob] - [uxbox.util.uuid :as uuid])) - -;; --- Helpers & Specs - -(s/def ::id ::us/uuid) -(s/def ::name ::us/string) -(s/def ::token ::us/string) -(s/def ::user ::us/uuid) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Queries -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; --- Query: Projects - -(s/def ::projects-query - (s/keys :req-un [::user])) - -(sv/defquery :projects - {:doc "Query all projects" - :spec ::projects-query} - [{:keys [user] :as params}] - (let [sql "select distinct on (p.id, p.created_at) - p.*, - array_agg(pg.id) over ( - partition by p.id - order by pg.created_at - range between unbounded preceding and unbounded following - ) as pages - from projects as p - right join pages as pg - on (pg.project_id = p.id) - where p.user_id = $1 - order by p.created_at asc"] - (-> (db/query db/pool [sql user]) - (p/then (fn [rows] - (mapv #(update % :pages vec) rows)))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Mutations -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; --- Mutation: Create Project - -(s/def ::create-project - (s/keys :req-un [::user ::name] - :opt-un [::id])) - -(sv/defmutation :create-project - {:doc "Create a project." - :spec ::create-project} - [{:keys [id user name] :as params}] - (let [id (or id (uuid/next)) - sql "insert into projects (id, user_id, name) - values ($1, $2, $3) returning *"] - (db/query-one db/pool [sql id user name]))) - -;; --- Mutation: Update Project - -(s/def ::update-project - (s/keys :req-un [::user ::name ::id])) - -(sv/defmutation :update-project - {:doc "Update project." - :spec ::update-project} - [{:keys [id name user] :as params}] - (let [sql "update projects - set name = $3 - where id = $1 - and user_id = $2 - and deleted_at is null - returning *"] - (db/query-one db/pool [sql id user name]))) - -;; --- Mutation: Delete Project - -(s/def ::delete-project - (s/keys :req-un [::id ::user])) - -(sv/defmutation :delete-project - {:doc "Delete project" - :spec ::delete-project} - [{:keys [id user] :as params}] - (let [sql "update projects - set deleted_at = clock_timestamp() - where id = $1 - and user_id = $2 - and deleted_at is null - returning id"] - (-> (db/query-one db/pool [sql id user]) - (p/then' sv/raise-not-found-if-nil) - (p/then' sv/constantly-nil)))) - - -;; --- 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-> (db/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})] -;; (db/fetch conn sqlv))) - diff --git a/backend/src/uxbox/services/queries.clj b/backend/src/uxbox/services/queries.clj new file mode 100644 index 0000000000..bfa48b6d2c --- /dev/null +++ b/backend/src/uxbox/services/queries.clj @@ -0,0 +1,19 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2019 Andrey Antukh + +(ns uxbox.services.queries + (:require + [uxbox.util.dispatcher :as uds])) + +(uds/defservice handle + {:dispatch-by ::type + :interceptors [uds/spec-interceptor + #_logging-interceptor + #_context-interceptor]}) + +(defmacro defquery + [key & rest] + `(uds/defmethod handle ~key ~@rest)) diff --git a/backend/src/uxbox/services/queries/icons.clj b/backend/src/uxbox/services/queries/icons.clj new file mode 100644 index 0000000000..77428755c7 --- /dev/null +++ b/backend/src/uxbox/services/queries/icons.clj @@ -0,0 +1,69 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2019 Andrey Antukh + +(ns uxbox.services.queries.icons + (:require + [clojure.spec.alpha :as s] + [promesa.core :as p] + [uxbox.db :as db] + [uxbox.services.queries :as sq] + [uxbox.util.blob :as blob] + [uxbox.util.exceptions :as ex] + [uxbox.util.spec :as us])) + +;; --- Helpers & Specs + +(s/def ::id ::us/uuid) +(s/def ::user ::us/uuid) +(s/def ::collection-id (s/nilable ::us/uuid)) + +(defn decode-icon-row + [{:keys [metadata] :as row}] + (when row + (cond-> row + metadata (assoc :metadata (blob/decode metadata))))) + +;; --- Query: Collections + +(def ^:private icons-collections-sql + "select *, + (select count(*) from icons where collection_id = ic.id) as num_icons + from icons_collections as ic + where (ic.user_id = $1 or + ic.user_id = '00000000-0000-0000-0000-000000000000'::uuid) + and ic.deleted_at is null + order by ic.created_at desc") + +(s/def ::icons-collections + (s/keys :req-un [::user])) + +(sq/defquery ::icons-collections + [{:keys [user] :as params}] + (let [sqlv [icons-collections-sql user]] + (db/query db/pool sqlv))) + +;; --- Icons By Collection ID + +(def ^:private icons-by-collection-sql + "select * + from icons as i + where (i.user_id = $1 or + i.user_id = '00000000-0000-0000-0000-000000000000'::uuid) + and i.deleted_at is null + and case when $2::uuid is null then i.collection_id is null + else i.collection_id = $2::uuid + end + order by i.created_at desc") + +(s/def ::icons-by-collection + (s/keys :req-un [::user] + :opt-un [::collection-id])) + +(sq/defquery ::icons-by-collection + [{:keys [user collection-id] :as params}] + (let [sqlv [icons-by-collection-sql user collection-id]] + (-> (db/query db/pool sqlv) + (p/then' #(mapv decode-icon-row %))))) diff --git a/backend/src/uxbox/services/queries/images.clj b/backend/src/uxbox/services/queries/images.clj new file mode 100644 index 0000000000..93a52f0dae --- /dev/null +++ b/backend/src/uxbox/services/queries/images.clj @@ -0,0 +1,109 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2019 Andrey Antukh + +(ns uxbox.services.queries.images + (:require + [clojure.spec.alpha :as s] + [promesa.core :as p] + [promesa.exec :as px] + [uxbox.db :as db] + [uxbox.media :as media] + [uxbox.images :as images] + [uxbox.services.queries :as sq] + [uxbox.services.util :as su] + [uxbox.util.blob :as blob] + [uxbox.util.data :as data] + [uxbox.util.exceptions :as ex] + [uxbox.util.spec :as us] + [uxbox.util.uuid :as uuid] + [vertx.core :as vc])) + +(def +thumbnail-options+ + {:src :path + :dst :thumbnail + :width 300 + :height 100 + :quality 92 + :format "webp"}) + +(defn populate-thumbnail + [row] + (let [opts +thumbnail-options+] + (-> (px/submit! #(images/populate-thumbnails row opts)) + (su/handle-on-context)))) + +(defn populate-thumbnails + [rows] + (if (empty? rows) + rows + (p/all (map populate-thumbnail rows)))) + +(defn populate-urls + [row] + (images/populate-urls row media/images-storage :path :url)) + +(s/def ::id ::us/uuid) +(s/def ::name ::us/string) +(s/def ::user ::us/uuid) +(s/def ::collection-id (s/nilable ::us/uuid)) + +(def ^:private images-collections-sql + "select *, + (select count(*) from images where collection_id = ic.id) as num_images + from images_collections as ic + where (ic.user_id = $1 or + ic.user_id = '00000000-0000-0000-0000-000000000000'::uuid) + and ic.deleted_at is null + order by ic.created_at desc;") + +(s/def ::images-collections + (s/keys :req-un [::user])) + +(sq/defquery ::images-collections + [{:keys [user] :as params}] + (db/query db/pool [images-collections-sql user])) + +;; --- Retrieve Image + +(defn retrieve-image + [conn id] + (let [sql "select * from images + where id = $1 + and deleted_at is null;"] + (db/query-one conn [sql id]))) + +;; (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))) + +;; --- Query Images by Collection (id) + +(def images-by-collection-sql + "select * from images + where (user_id = $1 or + user_id = '00000000-0000-0000-0000-000000000000'::uuid) + and deleted_at is null + and case when $2::uuid is null then collection_id is null + else collection_id = $2::uuid + end + order by created_at desc;") + +(s/def ::images-by-collection-query + (s/keys :req-un [::user] + :opt-un [::collection-id])) + +(sq/defquery ::images-by-collection + [{:keys [user collection-id] :as params}] + (let [sqlv [images-by-collection-sql user collection-id]] + (-> (db/query db/pool sqlv) + (p/then populate-thumbnails) + (p/then #(mapv populate-urls %))))) + diff --git a/backend/src/uxbox/services/queries/pages.clj b/backend/src/uxbox/services/queries/pages.clj new file mode 100644 index 0000000000..d432d4bcee --- /dev/null +++ b/backend/src/uxbox/services/queries/pages.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) 2019 Andrey Antukh + +(ns uxbox.services.queries.pages + (:require + [clojure.spec.alpha :as s] + [promesa.core :as p] + [uxbox.db :as db] + [uxbox.services.queries :as sq] + [uxbox.util.blob :as blob] + [uxbox.util.spec :as us] + [uxbox.util.sql :as sql])) + +;; --- Helpers & Specs + +(declare decode-row) + +(s/def ::id ::us/uuid) +(s/def ::user ::us/uuid) +(s/def ::project-id ::us/uuid) + +;; --- Query: Pages by Project + +(s/def ::pages-by-project + (s/keys :req-un [::user ::project-id])) + +(sq/defquery ::pages-by-project + [{:keys [user project-id] :as params}] + (let [sql "select pg.*, + pg.data, + pg.metadata + from pages as pg + where pg.user_id = $2 + and pg.project_id = $1 + and pg.deleted_at is null + order by pg.created_at asc;"] + (-> (db/query db/pool [sql project-id user]) + (p/then #(mapv decode-row %))))) + +;; --- Query: Page by Id + +(s/def ::page + (s/keys :req-un [::user ::id])) + +(sq/defquery ::page + [{:keys [user id] :as params}] + (let [sql "select pg.*, + pg.data, + pg.metadata + from pages as pg + where pg.user_id = $2 + and pg.id = $1 + and pg.deleted_at is null"] + (-> (db/query-one db/pool [sql id user]) + (p/then' decode-row)))) + +;; --- Query: Page History + +(s/def ::page-id ::us/uuid) +(s/def ::max ::us/integer) +(s/def ::pinned ::us/boolean) +(s/def ::since ::us/integer) + +(s/def ::page-history + (s/keys :req-un [::page-id ::user] + :opt-un [::max ::pinned ::since])) + +(sq/defquery ::page-history + [{:keys [page-id user since max pinned] :or {since Long/MAX_VALUE max 10}}] + (let [sql (-> (sql/from ["pages_history" "ph"]) + (sql/select "ph.*") + (sql/where ["ph.user_id = ?" user] + ["ph.page_id = ?" page-id] + ["ph.version < ?" since] + (when pinned + ["ph.pinned = ?" true])) + (sql/order "ph.version desc") + (sql/limit max))] + (-> (db/query db/pool (sql/fmt sql)) + (p/then (partial mapv decode-row))))) + +;; --- Helpers + +(defn decode-row + [{:keys [data metadata] :as row}] + (when row + (cond-> row + data (assoc :data (blob/decode data)) + metadata (assoc :metadata (blob/decode metadata))))) diff --git a/backend/src/uxbox/services/queries/profiles.clj b/backend/src/uxbox/services/queries/profiles.clj new file mode 100644 index 0000000000..52c72a7b5d --- /dev/null +++ b/backend/src/uxbox/services/queries/profiles.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.services.queries.profiles + (:require + [clojure.spec.alpha :as s] + [promesa.core :as p] + [promesa.exec :as px] + [uxbox.db :as db] + [uxbox.images :as images] + [uxbox.services.queries :as sq] + [uxbox.services.util :as su] + [uxbox.util.blob :as blob] + [uxbox.util.spec :as us])) + +;; --- Helpers & Specs + +(declare decode-profile-row) +(declare strip-private-attrs) + +(s/def ::email ::us/email) +(s/def ::fullname ::us/string) +(s/def ::metadata any?) +(s/def ::old-password ::us/string) +(s/def ::password ::us/string) +(s/def ::path ::us/string) +(s/def ::user ::us/uuid) +(s/def ::username ::us/string) + +;; --- Query: Profile (own) + +(defn resolve-thumbnail + [user] + (let [opts {:src :photo + :dst :photo + :size [100 100] + :quality 90 + :format "jpg"}] + (-> (px/submit! #(images/populate-thumbnails user opts)) + (su/handle-on-context)))) + +(defn get-profile + [conn id] + (let [sql "select * from users where id=$1 and deleted_at is null"] + (-> (db/query-one db/pool [sql id]) + (p/then' decode-profile-row)))) + +(s/def ::profile + (s/keys :req-un [::user])) + +(sq/defquery :profile + {:doc "Retrieve the user profile." + :spec ::profile} + [{:keys [user] :as params}] + (-> (get-profile db/pool user) + (p/then' strip-private-attrs))) + +;; --- Attrs Helpers + +(defn decode-profile-row + [{:keys [metadata] :as row}] + (when row + (cond-> row + metadata (assoc :metadata (blob/decode metadata))))) + +(defn strip-private-attrs + "Only selects a publicy visible user attrs." + [profile] + (select-keys profile [:id :username :fullname :metadata + :email :created-at :photo])) diff --git a/backend/src/uxbox/services/queries/projects.clj b/backend/src/uxbox/services/queries/projects.clj new file mode 100644 index 0000000000..ea6536d32f --- /dev/null +++ b/backend/src/uxbox/services/queries/projects.clj @@ -0,0 +1,48 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2019 Andrey Antukh + +(ns uxbox.services.queries.projects + (:require + [clojure.spec.alpha :as s] + [promesa.core :as p] + [uxbox.db :as db] + [uxbox.services.queries :as sq] + [uxbox.util.blob :as blob] + [uxbox.util.spec :as us])) + +;; --- Helpers & Specs + +(s/def ::id ::us/uuid) +(s/def ::name ::us/string) +(s/def ::token ::us/string) +(s/def ::user ::us/uuid) + +;; --- Query: Projects + +(def ^:private projects-sql + "select distinct on (p.id, p.created_at) + p.*, + array_agg(pg.id) over ( + partition by p.id + order by pg.created_at + range between unbounded preceding and unbounded following + ) as pages + from projects as p + left join pages as pg + on (pg.project_id = p.id) + where p.user_id = $1 + order by p.created_at asc") + +(s/def ::projects-query + (s/keys :req-un [::user])) + +(sq/defquery :projects + {:doc "Query all projects" + :spec ::projects-query} + [{:keys [user] :as params}] + (-> (db/query db/pool [projects-sql user]) + (p/then (fn [rows] + (mapv #(update % :pages vec) rows))))) diff --git a/backend/src/uxbox/services/queries/user_storage.clj b/backend/src/uxbox/services/queries/user_storage.clj new file mode 100644 index 0000000000..68eedb9f9e --- /dev/null +++ b/backend/src/uxbox/services/queries/user_storage.clj @@ -0,0 +1,34 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2019 Andrey Antukh + +(ns uxbox.services.queries.user-storage + (:require + [clojure.spec.alpha :as s] + [promesa.core :as p] + [uxbox.db :as db] + [uxbox.services.queries :as sq] + [uxbox.services.util :as su] + [uxbox.util.blob :as blob] + [uxbox.util.spec :as us])) + +(defn decode-row + [{:keys [val] :as row}] + (when row + (cond-> row + val (assoc :val (blob/decode val))))) + +(s/def ::user-storage-item + (s/keys :req-un [::key ::user])) + +(sq/defquery ::user-storage-entry + [{:keys [key user]}] + (let [sql "select kv.* + from user_storage as kv + where kv.user_id = $2 + and kv.key = $1"] + (-> (db/query-one db/pool [sql key user]) + (p/then' su/raise-not-found-if-nil) + (p/then' decode-row)))) diff --git a/backend/src/uxbox/services/util.clj b/backend/src/uxbox/services/util.clj new file mode 100644 index 0000000000..d87fd0007d --- /dev/null +++ b/backend/src/uxbox/services/util.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) 2019 Andrey Antukh + +(ns uxbox.services.util + (:require + [clojure.tools.logging :as log] + [vertx.core :as vc] + [uxbox.core :refer [system]] + [uxbox.util.uuid :as uuid] + [uxbox.util.dispatcher :as uds] + [uxbox.util.exceptions :as ex])) + +;; (def logging-interceptor +;; {:enter (fn [data] +;; (let [type (get-in data [:request ::type])] +;; (assoc data ::start-time (System/nanoTime)))) +;; :leave (fn [data] +;; (let [elapsed (- (System/nanoTime) (::start-time data)) +;; elapsed (str (quot elapsed 1000000) "ms") +;; type (get-in data [:request ::type])] +;; (log/info "service" type "processed in" elapsed) +;; data))}) + +(defn raise-not-found-if-nil + [v] + (if (nil? v) + (ex/raise :type :not-found + :hint "Object doest not exists.") + v)) + +(def constantly-nil (constantly nil)) + +(defn handle-on-context + [p] + (->> (vc/get-or-create-context system) + (vc/handle-on-context p))) diff --git a/backend/src/uxbox/util/migrations.clj b/backend/src/uxbox/util/migrations.clj index 379b7c4c89..ebaedd4cac 100644 --- a/backend/src/uxbox/util/migrations.clj +++ b/backend/src/uxbox/util/migrations.clj @@ -6,6 +6,7 @@ (ns uxbox.util.migrations (:require + [clojure.tools.logging :as log] [clojure.java.io :as io] [clojure.spec.alpha :as s] [cuerdas.core :as str] @@ -56,7 +57,7 @@ (-> (registered? pool modname (:name migration)) (p/then (fn [registered?] (when-not registered? - (println (str/format "applying migration - %s: %s" modname name)) + (log/info (str/format "applying migration %s/%s" modname name)) (execute))))))) (defn- impl-migrate diff --git a/backend/src/user.clj b/backend/test/user.clj similarity index 100% rename from backend/src/user.clj rename to backend/test/user.clj diff --git a/backend/test/uxbox/tests/helpers.clj b/backend/test/uxbox/tests/helpers.clj index 3abfcd7263..8e75991c4f 100644 --- a/backend/test/uxbox/tests/helpers.clj +++ b/backend/test/uxbox/tests/helpers.clj @@ -6,6 +6,9 @@ [cuerdas.core :as str] [mount.core :as mount] [datoteka.storages :as st] + [uxbox.services.mutations.profiles :as profiles] + [uxbox.services.mutations.projects :as projects] + [uxbox.services.mutations.pages :as pages] [uxbox.fixtures :as fixtures] [uxbox.migrations] [uxbox.media] @@ -59,52 +62,29 @@ ;; --- Users creation -(declare decode-user-row) -(declare decode-page-row) - (defn create-user [conn i] - (let [sql "insert into users (id, fullname, username, email, password, metadata, photo) - values ($1, $2, $3, $4, $5, $6, '') returning *"] - (-> (db/query-one conn [sql - (mk-uuid "user" i) - (str "User " i) - (str "user" i) - (str "user" i ".test@uxbox.io") - (hashers/encrypt "123123") - (blob/encode {})]) - (p/then' decode-user-row)))) + (profiles/create-profile conn {:id (mk-uuid "user" i) + :fullname (str "User " i) + :username (str "user" i) + :email (str "user" i ".test@uxbox.io") + :password "123123" + :metadata {}})) (defn create-project - [conn uid i] - (let [sql "insert into projects (id, user_id, name) - values ($1, $2, $3) returning *" - name (str "sample project " i)] - (db/query-one conn [sql (mk-uuid "project" i) uid name]))) + [conn user-id i] + (projects/create-project conn {:id (mk-uuid "project" i) + :user user-id + :name (str "sample project " i)})) (defn create-page [conn uid pid i] - (let [sql "insert into pages (id, user_id, project_id, name, data, metadata) - values ($1, $2, $3, $4, $5, $6) returning *" - data (blob/encode {:shapes []}) - mdata (blob/encode {}) - name (str "page" i) - id (mk-uuid "page" i)] - (-> (db/query-one conn [sql id uid pid name data mdata]) - (p/then' decode-page-row)))) - -(defn- decode-page-row - [{:keys [data metadata] :as row}] - (when row - (cond-> row - data (assoc :data (blob/decode data)) - metadata (assoc :metadata (blob/decode metadata))))) - -(defn- decode-user-row - [{:keys [metadata] :as row}] - (when row - (cond-> row - metadata (assoc :metadata (blob/decode metadata))))) + (pages/create-page conn {:id (mk-uuid "page" i) + :user uid + :project-id pid + :name (str "page" i) + :data {:shapes []} + :metadata {}})) (defn handle-error [err] @@ -152,10 +132,9 @@ (do (println "====> START ERROR") (if (= :spec-validation (:code error)) - (do - (s/explain-out (:data error)) - (println "====> END ERROR")) - (prn error))) + (s/explain-out (:data error)) + (prn error)) + (println "====> END ERROR")) (do (println "====> START RESPONSE") (prn result) diff --git a/backend/test/uxbox/tests/test_emails.clj b/backend/test/uxbox/tests/test_emails.clj index 2ffacd2531..9e853e9748 100644 --- a/backend/test/uxbox/tests/test_emails.clj +++ b/backend/test/uxbox/tests/test_emails.clj @@ -11,14 +11,13 @@ [mockery.core :refer [with-mock]] [uxbox.db :as db] [uxbox.emails :as emails] - [uxbox.services.core :as sv] [uxbox.tests.helpers :as th])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) (t/deftest register-email-rendering - (let [result (emails/render emails/register {:to "example@uxbox.io"})] + (let [result (emails/render emails/register {:to "example@uxbox.io" :name "foo"})] (t/is (map? result)) (t/is (contains? result :subject)) (t/is (contains? result :body)) @@ -27,7 +26,7 @@ (t/is (vector? (:body result))))) (t/deftest email-sending-and-sendmail-job - (let [res @(emails/send! emails/register {:to "example@uxbox.io"})] + (let [res @(emails/send! emails/register {:to "example@uxbox.io" :name "foo"})] (t/is (nil? res))) (with-mock mock {:target 'uxbox.jobs.sendmail/impl-sendmail diff --git a/backend/test/uxbox/tests/test_services_auth.clj b/backend/test/uxbox/tests/test_services_auth.clj index 50541b13bc..36ef172144 100644 --- a/backend/test/uxbox/tests/test_services_auth.clj +++ b/backend/test/uxbox/tests/test_services_auth.clj @@ -8,35 +8,34 @@ (:require [clojure.test :as t] [promesa.core :as p] - [buddy.hashers :as hashers] [uxbox.db :as db] - [uxbox.services.core :as sv] + [uxbox.services.mutations :as sm] [uxbox.tests.helpers :as th])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) -(t/deftest test-failed-auth +(t/deftest failed-auth (let [user @(th/create-user db/pool 1) event {:username "user1" - :type :login + ::sm/type :login :password "foobar" :metadata "1" :scope "foobar"} - [err res] (th/try-on - (sv/mutation event))] - (t/is (nil? res)) - (t/is (= (:type err) :validation)) - (t/is (= (:code err) :uxbox.services.auth/wrong-credentials)))) + out (th/try-on! (sm/handle event))] + ;; (th/print-result! out) + (t/is (map? (:error out))) + (t/is (= (get-in out [:error :type]) :validation)) + (t/is (= (get-in out [:error :code]) :uxbox.services.mutations.auth/wrong-credentials)))) -(t/deftest test-success-auth +(t/deftest success-auth (let [user @(th/create-user db/pool 1) event {:username "user1" - :type :login + ::sm/type :login :password "123123" :metadata "1" :scope "foobar"} - [err res] (th/try-on - (sv/mutation event))] - (t/is (= res (:id user))) - (t/is (nil? err)))) + out (th/try-on! (sm/handle event))] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (t/is (= (get-in out [:result :id]) (:id user))))) diff --git a/backend/test/uxbox/tests/test_services_kvstore.clj b/backend/test/uxbox/tests/test_services_kvstore.clj deleted file mode 100644 index f830effe22..0000000000 --- a/backend/test/uxbox/tests/test_services_kvstore.clj +++ /dev/null @@ -1,52 +0,0 @@ -(ns uxbox.tests.test-services-kvstore - (:require - [clojure.spec.alpha :as s] - [clojure.test :as t] - [promesa.core :as p] - [uxbox.db :as db] - [uxbox.http :as http] - [uxbox.services.core :as sv] - [uxbox.tests.helpers :as th])) - -(t/use-fixtures :once th/state-init) -(t/use-fixtures :each th/database-reset) - -(t/deftest test-mutation-upsert-kvstore - (let [{:keys [id] :as user} @(th/create-user db/pool 1)] - (let [out (th/try-on! (sv/query {::sv/type :kvstore-entry - :key "foobar" - :user id}))] - ;; (th/print-result! out) - (t/is (nil? (:error out))) - (t/is (nil? (:result out)))) - - (let [out (th/try-on! (sv/mutation {::sv/type :upsert-kvstore - :user id - :key "foobar" - :value {:some #{:value}}}))] - ;; (th/print-result! out) - (t/is (nil? (:error out))) - (t/is (nil? (:result out)))) - - (let [out (th/try-on! (sv/query {::sv/type :kvstore-entry - :key "foobar" - :user id}))] - ;; (th/print-result! out) - (t/is (nil? (:error out))) - (t/is (= {:some #{:value}} (get-in out [:result :value]))) - (t/is (= "foobar" (get-in out [:result :key])))) - - (let [out (th/try-on! (sv/mutation {::sv/type :delete-kvstore - :user id - :key "foobar"}))] - ;; (th/print-result! out) - (t/is (nil? (:error out))) - (t/is (nil? (:result out)))) - - (let [out (th/try-on! (sv/query {::sv/type :kvstore-entry - :key "foobar" - :user id}))] - ;; (th/print-result! out) - (t/is (nil? (:error out))) - (t/is (nil? (:result out)))))) - diff --git a/backend/test/uxbox/tests/test_services_pages.clj b/backend/test/uxbox/tests/test_services_pages.clj index 21c1ca887f..b1a5ba9202 100644 --- a/backend/test/uxbox/tests/test_services_pages.clj +++ b/backend/test/uxbox/tests/test_services_pages.clj @@ -5,7 +5,8 @@ [promesa.core :as p] [uxbox.db :as db] [uxbox.http :as http] - [uxbox.services.core :as sv] + [uxbox.services.mutations :as sm] + [uxbox.services.queries :as sq] [uxbox.tests.helpers :as th])) (t/use-fixtures :once th/state-init) @@ -14,13 +15,13 @@ (t/deftest mutation-create-page (let [user @(th/create-user db/pool 1) proj @(th/create-project db/pool (:id user) 1) - data {::sv/type :create-page + data {::sm/type :create-page :data {:shapes []} :metadata {} :project-id (:id proj) :name "test page" :user (:id user)} - res (th/try-on! (sv/mutation data))] + res (th/try-on! (sm/handle data))] (t/is (nil? (:error res))) (t/is (uuid? (get-in res [:result :id]))) (let [rsp (:result res)] @@ -33,35 +34,35 @@ (let [user @(th/create-user db/pool 1) proj @(th/create-project db/pool (:id user) 1) page @(th/create-page db/pool (:id user) (:id proj) 1) - data {::sv/type :update-page + data {::sm/type :update-page :id (:id page) :data {:shapes [1 2 3]} :metadata {:foo 2} :project-id (:id proj) :name "test page" :user (:id user)} - res (th/try-on! (sv/mutation data))] + res (th/try-on! (sm/handle data))] ;; (th/print-result! res) (t/is (nil? (:error res))) (t/is (= (:id data) (get-in res [:result :id]))) - (t/is (= (:user data) (get-in res [:result :user-id]))) - (t/is (= (:name data) (get-in res [:result :name]))) - (t/is (= (:data data) (get-in res [:result :data]))) - (t/is (= (:metadata data) (get-in res [:result :metadata]))))) + #_(t/is (= (:user data) (get-in res [:result :user-id]))) + #_(t/is (= (:name data) (get-in res [:result :name]))) + #_(t/is (= (:data data) (get-in res [:result :data]))) + #_(t/is (= (:metadata data) (get-in res [:result :metadata]))))) (t/deftest mutation-update-page-metadata (let [user @(th/create-user db/pool 1) proj @(th/create-project db/pool (:id user) 1) page @(th/create-page db/pool (:id user) (:id proj) 1) - data {::sv/type :update-page-metadata + data {::sm/type :update-page-metadata :id (:id page) :metadata {:foo 2} :project-id (:id proj) :name "test page" :user (:id user)} - res (th/try-on! (sv/mutation data))] + res (th/try-on! (sm/handle data))] ;; (th/print-result! res) (t/is (nil? (:error res))) @@ -74,10 +75,10 @@ (let [user @(th/create-user db/pool 1) proj @(th/create-project db/pool (:id user) 1) page @(th/create-page db/pool (:id user) (:id proj) 1) - data {::sv/type :delete-page + data {::sm/type :delete-page :id (:id page) :user (:id user)} - res (th/try-on! (sv/mutation data))] + res (th/try-on! (sm/handle data))] ;; (th/print-result! res) (t/is (nil? (:error res))) @@ -87,10 +88,10 @@ (let [user @(th/create-user db/pool 1) proj @(th/create-project db/pool (:id user) 1) page @(th/create-page db/pool (:id user) (:id proj) 1) - data {::sv/type :pages-by-project + data {::sq/type :pages-by-project :project-id (:id proj) :user (:id user)} - res (th/try-on! (sv/query data))] + res (th/try-on! (sq/handle data))] ;; (th/print-result! res) (t/is (nil? (:error res))) diff --git a/backend/test/uxbox/tests/test_services_projects.clj b/backend/test/uxbox/tests/test_services_projects.clj index b0dde3367b..eb5d4cc619 100644 --- a/backend/test/uxbox/tests/test_services_projects.clj +++ b/backend/test/uxbox/tests/test_services_projects.clj @@ -4,29 +4,35 @@ [promesa.core :as p] [uxbox.db :as db] [uxbox.http :as http] - [uxbox.services.core :as sv] + [uxbox.services.mutations :as sm] + [uxbox.services.queries :as sq] [uxbox.tests.helpers :as th])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) +;; TODO: migrate from try-on to try-on! + (t/deftest query-project-list (let [user @(th/create-user db/pool 1) proj @(th/create-project db/pool (:id user) 1) - data {::sv/type :projects + data {::sq/type :projects :user (:id user)} - [err rsp] (th/try-on (sv/query data))] - (t/is (nil? err)) - (t/is (= 1 (count rsp))) - (t/is (= (:id proj) (get-in rsp [0 :id]))) - (t/is (= (:name proj (get-in rsp [0 :name])))))) + out (th/try-on! (sq/handle data))] + + ;; (th/print-result! out) + + (t/is (nil? (:error out))) + (t/is (= 1 (count (:result out)))) + (t/is (= (:id proj) (get-in out [:result 0 :id]))) + (t/is (= (:name proj) (get-in out [:result 0 :name]))))) (t/deftest mutation-create-project (let [user @(th/create-user db/pool 1) - data {::sv/type :create-project + data {::sm/type :create-project :user (:id user) :name "test project"} - [err rsp] (th/try-on (sv/mutation data))] + [err rsp] (th/try-on (sm/handle data))] ;; (prn "RESPONSE:" err rsp) (t/is (nil? err)) (t/is (= (:user data) (:user-id rsp))) @@ -35,11 +41,11 @@ (t/deftest mutation-update-project (let [user @(th/create-user db/pool 1) proj @(th/create-project db/pool (:id user) 1) - data {::sv/type :update-project + data {::sm/type :update-project :id (:id proj) :name "test project mod" :user (:id user)} - [err rsp] (th/try-on (sv/mutation data))] + [err rsp] (th/try-on (sm/handle data))] ;; (prn "RESPONSE:" err rsp) (t/is (nil? err)) (t/is (= (:id data) (:id rsp))) @@ -49,10 +55,10 @@ (t/deftest mutation-delete-project (let [user @(th/create-user db/pool 1) proj @(th/create-project db/pool (:id user) 1) - data {::sv/type :delete-project + data {::sm/type :delete-project :id (:id proj) :user (:id user)} - [err rsp] (th/try-on (sv/mutation data))] + [err rsp] (th/try-on (sm/handle data))] ;; (prn "RESPONSE:" err rsp) (t/is (nil? err)) (t/is (nil? rsp)) diff --git a/backend/test/uxbox/tests/test_services_user_storage.clj b/backend/test/uxbox/tests/test_services_user_storage.clj new file mode 100644 index 0000000000..8585f5e967 --- /dev/null +++ b/backend/test/uxbox/tests/test_services_user_storage.clj @@ -0,0 +1,54 @@ +(ns uxbox.tests.test-services-user-storage + (:require + [clojure.spec.alpha :as s] + [clojure.test :as t] + [promesa.core :as p] + [uxbox.db :as db] + [uxbox.http :as http] + [uxbox.services.mutations :as sm] + [uxbox.services.queries :as sq] + [uxbox.tests.helpers :as th])) + +(t/use-fixtures :once th/state-init) +(t/use-fixtures :each th/database-reset) + +(t/deftest test-user-storage + (let [{:keys [id] :as user} @(th/create-user db/pool 1)] + (let [out (th/try-on! (sq/handle {::sq/type :user-storage-entry + :key "foobar" + :user id}))] + (t/is (nil? (:result out))) + (t/is (map? (:error out))) + (t/is (= :not-found (get-in out [:error :type])))) + + (let [out (th/try-on! (sm/handle {::sm/type :upsert-user-storage-entry + :user id + :key "foobar" + :val {:some #{:value}}}))] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))) + + (let [out (th/try-on! (sq/handle {::sq/type :user-storage-entry + :key "foobar" + :user id}))] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (t/is (= {:some #{:value}} (get-in out [:result :val]))) + (t/is (= "foobar" (get-in out [:result :key])))) + + (let [out (th/try-on! (sm/handle {::sm/type :delete-user-storage-entry + :user id + :key "foobar"}))] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (t/is (nil? (:result out)))) + + (let [out (th/try-on! (sq/handle {::sq/type :user-storage-entry + :key "foobar" + :user id}))] + ;; (th/print-result! out) + (t/is (nil? (:result out))) + (t/is (map? (:error out))) + (t/is (= :not-found (get-in out [:error :type])))))) + diff --git a/backend/test/uxbox/tests/test_users.clj b/backend/test/uxbox/tests/test_users.clj index c09900a8ab..f761b32ed7 100644 --- a/backend/test/uxbox/tests/test_users.clj +++ b/backend/test/uxbox/tests/test_users.clj @@ -5,10 +5,10 @@ [promesa.core :as p] [cuerdas.core :as str] [datoteka.core :as fs] - ;; [buddy.hashers :as hashers] [vertx.core :as vc] [uxbox.db :as db] - [uxbox.services.core :as sv] + [uxbox.services.mutations :as sm] + [uxbox.services.queries :as sq] [uxbox.tests.helpers :as th])) (t/use-fixtures :once th/state-init) @@ -16,9 +16,9 @@ (t/deftest test-query-profile (let [user @(th/create-user db/pool 1) - event {::sv/type :profile + event {::sq/type :profile :user (:id user)} - [err rsp] (th/try-on (sv/query event))] + [err rsp] (th/try-on (sq/handle event))] ;; (println "RESPONSE:" resp))) (t/is (nil? err)) (t/is (= (:fullname rsp) "User 1")) @@ -30,12 +30,12 @@ (t/deftest test-mutation-update-profile (let [user @(th/create-user db/pool 1) event (assoc user - ::sv/type :update-profile + ::sm/type :update-profile :fullname "Full Name" :username "user222" :metadata {:foo "bar"} :email "user222@uxbox.io") - [err data] (th/try-on (sv/mutation event))] + [err data] (th/try-on (sm/handle event))] ;; (println "RESPONSE:" err data) (t/is (nil? err)) (t/is (= (:fullname data) "Full Name")) @@ -46,13 +46,13 @@ (t/deftest test-mutation-update-profile-photo (let [user @(th/create-user db/pool 1) - event {::sv/type :update-profile-photo + event {::sm/type :update-profile-photo :user (:id user) :file {:name "sample.jpg" :path (fs/path "test/uxbox/tests/_files/sample.jpg") :size 123123 :mtype "image/jpeg"}} - [err rsp] (th/try-on (sv/mutation event))] + [err rsp] (th/try-on (sm/handle event))] ;; (prn "RESPONSE:" [err rsp]) (t/is (nil? err)) (t/is (= (:id user) (:id rsp))) @@ -64,7 +64,7 @@ ;; :email "user222@uxbox.io" ;; :password "user222" ;; ::sv/type :register-profile} -;; [err rsp] (th/try-on (sv/mutation data))] +;; [err rsp] (th/try-on (sm/handle data))] ;; (println "RESPONSE:" err rsp))) ;; ;; (t/deftest test-http-validate-recovery-token