♻️ Restructure the services directory.

This commit is contained in:
Andrey Antukh 2019-12-01 16:48:41 +01:00
parent eeb5482d36
commit b66bc02098
45 changed files with 951 additions and 960 deletions

View file

@ -5,6 +5,7 @@
</Console> </Console>
</Appenders> </Appenders>
<Loggers> <Loggers>
<Logger name="io.vertx.sqlclient.impl.SocketConnectionBase" level="INFO"/>
<Root level="info"> <Root level="info">
<AppenderRef ref="console"/> <AppenderRef ref="console"/>
</Root> </Root>

View file

@ -13,6 +13,28 @@ CREATE TABLE users (
metadata bytea NOT NULL 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 ( CREATE TABLE sessions (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
@ -24,6 +46,7 @@ CREATE TABLE sessions (
); );
-- Insert a placeholder system user. -- Insert a placeholder system user.
INSERT INTO users (id, fullname, username, email, photo, password, metadata) INSERT INTO users (id, fullname, username, email, photo, password, metadata)
VALUES ('00000000-0000-0000-0000-000000000000'::uuid, VALUES ('00000000-0000-0000-0000-000000000000'::uuid,
'System User', 'System User',
@ -44,18 +67,6 @@ CREATE UNIQUE INDEX users_email_idx
CREATE TRIGGER users_modified_at_tgr BEFORE UPDATE ON users CREATE TRIGGER users_modified_at_tgr BEFORE UPDATE ON users
FOR EACH ROW EXECUTE PROCEDURE update_modified_at(); FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
CREATE TABLE user_pswd_recovery ( CREATE TRIGGER user_storage_modified_at_tgr BEFORE UPDATE ON user_storage
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), FOR EACH ROW EXECUTE PROCEDURE update_modified_at();
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);

View file

@ -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();

View file

@ -1 +0,0 @@
select version();

View file

@ -16,7 +16,6 @@
[uxbox.http.session :as session] [uxbox.http.session :as session]
[uxbox.http.handlers :as handlers] [uxbox.http.handlers :as handlers]
[uxbox.http.debug :as debug] [uxbox.http.debug :as debug]
[uxbox.services.core :as sv]
[vertx.core :as vc] [vertx.core :as vc]
[vertx.http :as vh] [vertx.http :as vh]
[vertx.web :as vw] [vertx.web :as vw]
@ -62,6 +61,7 @@
(vw/assets "/static/*" {:root "resources/public/static"}) (vw/assets "/static/*" {:root "resources/public/static"})
(vw/router routes))] (vw/router routes))]
(log/info "Starting http server on" (:http-server-port cfg/config) "port.")
(vh/server ctx {:handler handler (vh/server ctx {:handler handler
:port (:http-server-port cfg/config)}))) :port (:http-server-port cfg/config)})))

View file

@ -11,7 +11,6 @@
[promesa.core :as p] [promesa.core :as p]
[uxbox.http.errors :as errors] [uxbox.http.errors :as errors]
[uxbox.http.session :as session] [uxbox.http.session :as session]
[uxbox.services.core :as sv]
[uxbox.util.uuid :as uuid])) [uxbox.util.uuid :as uuid]))
(defn emails-list (defn emails-list

View file

@ -9,16 +9,18 @@
[promesa.core :as p] [promesa.core :as p]
[uxbox.emails :as emails] [uxbox.emails :as emails]
[uxbox.http.session :as session] [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])) [uxbox.util.uuid :as uuid]))
(defn query-handler (defn query-handler
[req] [req]
(let [type (get-in req [:path-params :type]) (let [type (get-in req [:path-params :type])
data (merge (:params req) data (merge (:params req)
{::sv/type (keyword type) {::sq/type (keyword type)
:user (:user req)})] :user (:user req)})]
(-> (sv/query (with-meta data {:req req})) (-> (sq/handle (with-meta data {:req req}))
(p/then' (fn [result] (p/then' (fn [result]
{:status 200 {:status 200
:body result}))))) :body result})))))
@ -29,9 +31,9 @@
data (merge (:params req) data (merge (:params req)
(:body-params req) (:body-params req)
(:uploads req) (:uploads req)
{::sv/type (keyword type) {::sm/type (keyword type)
:user (:user req)})] :user (:user req)})]
(-> (sv/mutation (with-meta data {:req req})) (-> (sm/handle (with-meta data {:req req}))
(p/then' (fn [result] (p/then' (fn [result]
{:status 200 :body result}))))) {:status 200 :body result})))))
@ -39,7 +41,7 @@
[req] [req]
(let [data (:body-params req) (let [data (:body-params req)
user-agent (get-in req [:headers "user-agent"])] 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 #(session/create % user-agent))
(p/then' (fn [token] (p/then' (fn [token]
{:status 204 {:status 204
@ -59,9 +61,9 @@
(defn register-handler (defn register-handler
[req] [req]
(let [data (merge (:body-params req) (let [data (merge (:body-params req)
{::sv/type :register-profile}) {::sm/type :register-profile})
user-agent (get-in req [:headers "user-agent"])] 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}] (p/then (fn [{:keys [id] :as user}]
(session/create id user-agent))) (session/create id user-agent)))
(p/then' (fn [token] (p/then' (fn [token]

View file

@ -19,28 +19,25 @@
:steps :steps
[{:desc "Initial triggers and utils." [{:desc "Initial triggers and utils."
:name "0001-main" :name "0001-main"
:fn (mg/resource "migrations/0001.main.up.sql")} :fn (mg/resource "migrations/0001.main.sql")}
{:desc "Initial auth related tables" {:desc "Initial auth related tables"
:name "0002-auth" :name "0002-users"
:fn (mg/resource "migrations/0002.auth.up.sql")} :fn (mg/resource "migrations/0002.users.sql")}
{:desc "Initial projects tables" {:desc "Initial projects tables"
:name "0003-projects" :name "0003-projects"
:fn (mg/resource "migrations/0003.projects.up.sql")} :fn (mg/resource "migrations/0003.projects.sql")}
{:desc "Initial pages tables" {:desc "Initial pages tables"
:name "0004-pages" :name "0004-pages"
:fn (mg/resource "migrations/0004.pages.up.sql")} :fn (mg/resource "migrations/0004.pages.sql")}
{:desc "Initial kvstore tables"
:name "0005-kvstore"
:fn (mg/resource "migrations/0005.kvstore.up.sql")}
{:desc "Initial emails related tables" {:desc "Initial emails related tables"
:name "0006-emails" :name "0005-emails"
:fn (mg/resource "migrations/0006.emails.up.sql")} :fn (mg/resource "migrations/0005.emails.sql")}
{:desc "Initial images tables" {:desc "Initial images tables"
:name "0007-images" :name "0006-images"
:fn (mg/resource "migrations/0007.images.up.sql")} :fn (mg/resource "migrations/0006.images.sql")}
{:desc "Initial icons tables" {:desc "Initial icons tables"
:name "0008-icons" :name "0007-icons"
:fn (mg/resource "migrations/0008.icons.up.sql")} :fn (mg/resource "migrations/0007.icons.sql")}
]}) ]})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -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 <niwi@niwi.nz>
(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))))

View file

@ -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 <niwi@niwi.nz>
(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)))

View file

@ -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 <niwi@niwi.nz>
(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))

View file

@ -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 <niwi@niwi.nz>
(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))))

View file

@ -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 <niwi@niwi.nz>
(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))

View file

@ -4,33 +4,32 @@
;; ;;
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.services.auth (ns uxbox.services.mutations.auth
(:require (:require
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[buddy.hashers :as hashers] [buddy.hashers :as hashers]
[promesa.core :as p] [promesa.core :as p]
[uxbox.config :as cfg] [uxbox.config :as cfg]
[uxbox.db :as db] [uxbox.db :as db]
[uxbox.services.core :as sc] [uxbox.services.mutations :as sm]
[uxbox.services.users :as users]
[uxbox.util.spec :as us] [uxbox.util.spec :as us]
[uxbox.util.exceptions :as ex])) [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 (def ^:private user-by-username-sql
"select id, password "select id, password
from users from users
where username=$1 or email=$1 where username=$1 or email=$1
and deleted_at is null") 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" {:doc "User login"
:spec ::login-params} :spec ::login-params}
[{:keys [username password scope] :as params}] [{:keys [username password scope] :as params}]
@ -44,7 +43,7 @@
(when-not (check-password user password) (when-not (check-password user password)
(ex/raise :type :validation (ex/raise :type :validation
:code ::wrong-credentials)) :code ::wrong-credentials))
(:id user))]
{:id (:id user)})]
(-> (db/query-one db/pool [user-by-username-sql username]) (-> (db/query-one db/pool [user-by-username-sql username])
(p/then' check-user)))) (p/then' check-user))))

View file

@ -4,15 +4,15 @@
;; ;;
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.services.icons (ns uxbox.services.mutations.icons
"Icons library related services."
(:require (:require
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[promesa.core :as p] [promesa.core :as p]
[uxbox.db :as db] [uxbox.db :as db]
[uxbox.services.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.blob :as blob]
[uxbox.util.exceptions :as ex]
[uxbox.util.spec :as us] [uxbox.util.spec :as us]
[uxbox.util.uuid :as uuid])) [uxbox.util.uuid :as uuid]))
@ -36,75 +36,13 @@
(s/def ::metadata (s/def ::metadata
(s/keys :opt-un [::width ::height ::view-box ::mimetype])) (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 ;; --- Mutation: Create Collection
(s/def ::create-icons-collection (s/def ::create-icons-collection
(s/keys :req-un [::user ::name] (s/keys :req-un [::user ::name]
:opt-un [::id])) :opt-un [::id]))
(sv/defmutation :create-icons-collection (sm/defmutation ::create-icons-collection
{:doc "Create a new collection of icons."
:spec ::create-icons-collection}
[{:keys [id user name] :as params}] [{:keys [id user name] :as params}]
(let [id (or id (uuid/next)) (let [id (or id (uuid/next))
sql "insert into icons_collections (id, user_id, name) sql "insert into icons_collections (id, user_id, name)
@ -116,9 +54,7 @@
(s/def ::update-icons-collection (s/def ::update-icons-collection
(s/keys :req-un [::user ::name ::id])) (s/keys :req-un [::user ::name ::id]))
(sv/defmutation :update-icons-collection (sm/defmutation ::update-icons-collection
{:doc "Update a collection of icons."
:spec ::update-icons-collection}
[{:keys [id user name] :as params}] [{:keys [id user name] :as params}]
(let [sql "update icons_collections (let [sql "update icons_collections
set name = $3 set name = $3
@ -126,7 +62,7 @@
and user_id = $2 and user_id = $2
returning *"] returning *"]
(-> (db/query-one db/pool [sql id user name]) (-> (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 ;; --- Copy Icon
@ -140,14 +76,12 @@
and (user_id = $2 or and (user_id = $2 or
user_id = '00000000-0000-0000-0000-000000000000'::uuid)"] user_id = '00000000-0000-0000-0000-000000000000'::uuid)"]
(-> (db/query-one conn [sql id user]) (-> (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/def ::copy-icon
(s/keys :req-un [:us/id ::collection-id ::user])) (s/keys :req-un [:us/id ::collection-id ::user]))
(sv/defmutation :copy-icon (sm/defmutation ::copy-icon
{:doc "Copy an icon from one collection to other."
:spec ::copy-icon}
[{:keys [user id collection-id] :as params}] [{:keys [user id collection-id] :as params}]
(db/with-atomic [conn db/pool] (db/with-atomic [conn db/pool]
(-> (retrieve-icon conn {:user user :id id}) (-> (retrieve-icon conn {:user user :id id})
@ -161,9 +95,7 @@
(s/def ::delete-icons-collection (s/def ::delete-icons-collection
(s/keys :req-un [::user ::id])) (s/keys :req-un [::user ::id]))
(sv/defmutation :delete-icons-collection (sm/defmutation ::delete-icons-collection
{:doc "Delete a collection of icons."
:spec ::delete-icons-collection}
[{:keys [user id] :as params}] [{:keys [user id] :as params}]
(let [sql "update icons_collections (let [sql "update icons_collections
set deleted_at = clock_timestamp() set deleted_at = clock_timestamp()
@ -171,8 +103,8 @@
and user_id = $2 and user_id = $2
returning id"] returning id"]
(-> (db/query-one db/pool [sql id user]) (-> (db/query-one db/pool [sql id user])
(p/then' sv/raise-not-found-if-nil) (p/then' su/raise-not-found-if-nil)
(p/then' sv/constantly-nil)))) (p/then' su/constantly-nil))))
;; --- Mutation: Create Icon (Upload) ;; --- Mutation: Create Icon (Upload)
@ -194,9 +126,7 @@
(s/keys :req-un [::user ::name ::metadata ::content] (s/keys :req-un [::user ::name ::metadata ::content]
:opt-un [::id ::collection-id])) :opt-un [::id ::collection-id]))
(sv/defmutation :create-icon (sm/defmutation ::create-icon
{:doc "Create (upload) a new icon."
:spec ::create-icon}
[params] [params]
(create-icon db/pool params)) (create-icon db/pool params))
@ -205,9 +135,7 @@
(s/def ::update-icon (s/def ::update-icon
(s/keys :req-un [::id ::user ::name ::collection-id])) (s/keys :req-un [::id ::user ::name ::collection-id]))
(sv/defmutation :update-icon (sm/defmutation ::update-icon
{:doc "Update an icon entry."
:spec ::update-icon}
[{:keys [id name user collection-id] :as params}] [{:keys [id name user collection-id] :as params}]
(let [sql "update icons (let [sql "update icons
set name = $1, set name = $1,
@ -216,16 +144,14 @@
and user_id = $4 and user_id = $4
returning *"] returning *"]
(-> (db/query-one db/pool [sql name collection-id id user]) (-> (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 ;; --- Mutation: Delete Icon
(s/def ::delete-icon (s/def ::delete-icon
(s/keys :req-un [::user ::id])) (s/keys :req-un [::user ::id]))
(sv/defmutation :delete-icon (sm/defmutation ::delete-icon
{:doc "Delete an icon entry."
:spec ::delete-icon}
[{:keys [id user] :as params}] [{:keys [id user] :as params}]
(let [sql "update icons (let [sql "update icons
set deleted_at = clock_timestamp() set deleted_at = clock_timestamp()
@ -233,5 +159,5 @@
and user_id = $2 and user_id = $2
returning id"] returning id"]
(-> (db/query-one db/pool [sql id user]) (-> (db/query-one db/pool [sql id user])
(p/then' sv/raise-not-found-if-nil) (p/then' su/raise-not-found-if-nil)
(p/then' sv/constantly-nil)))) (p/then' su/constantly-nil))))

View file

@ -4,8 +4,7 @@
;; ;;
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.services.images (ns uxbox.services.mutations.images
"Images library related services."
(:require (:require
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[datoteka.core :as fs] [datoteka.core :as fs]
@ -15,7 +14,8 @@
[uxbox.db :as db] [uxbox.db :as db]
[uxbox.media :as media] [uxbox.media :as media]
[uxbox.images :as images] [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.blob :as blob]
[uxbox.util.data :as data] [uxbox.util.data :as data]
[uxbox.util.exceptions :as ex] [uxbox.util.exceptions :as ex]
@ -35,7 +35,7 @@
[row] [row]
(let [opts +thumbnail-options+] (let [opts +thumbnail-options+]
(-> (px/submit! #(images/populate-thumbnails row opts)) (-> (px/submit! #(images/populate-thumbnails row opts))
(sc/handle-on-context)))) (su/handle-on-context))))
(defn- populate-thumbnails (defn- populate-thumbnails
[rows] [rows]
@ -58,9 +58,7 @@
(s/keys :req-un [::user ::us/name] (s/keys :req-un [::user ::us/name]
:opt-un [::id])) :opt-un [::id]))
(sc/defmutation :create-image-collection (sm/defmutation ::create-image-collection
{:doc "Create image collection"
:spec ::create-image-collection}
[{:keys [id user name] :as params}] [{:keys [id user name] :as params}]
(let [sql "insert into images_collections (id, user_id, name) (let [sql "insert into images_collections (id, user_id, name)
values ($1, $2, $3) returning *;"] values ($1, $2, $3) returning *;"]
@ -71,9 +69,7 @@
(s/def ::update-images-collection (s/def ::update-images-collection
(s/keys :req-un [::id ::user ::us/name])) (s/keys :req-un [::id ::user ::us/name]))
(sc/defmutation :update-images-collection (sm/defmutation ::update-images-collection
{:doc "Update image collection."
:spec ::update-images-collection}
[{:keys [id user name] :as params}] [{:keys [id user name] :as params}]
(let [sql "update images_collections (let [sql "update images_collections
set name = $3 set name = $3
@ -82,30 +78,12 @@
returning *;"] returning *;"]
(db/query-one db/pool [sql id user name]))) (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 ;; --- Delete Collection
(s/def ::delete-images-collection (s/def ::delete-images-collection
(s/keys :req-un [::user ::id])) (s/keys :req-un [::user ::id]))
(sc/defmutation :delete-images-collection (sm/defmutation ::delete-images-collection
{:doc "Delete an image collection"
:spec ::delete-images-collection}
[{:keys [id user] :as params}] [{:keys [id user] :as params}]
(let [sql "update images_collections (let [sql "update images_collections
set deleted_at = clock_timestamp() set deleted_at = clock_timestamp()
@ -113,35 +91,16 @@
and user_id = $2 and user_id = $2
returning id"] returning id"]
(-> (db/query-one db/pool [sql id user]) (-> (db/query-one db/pool [sql id user])
(p/then' sc/raise-not-found-if-nil)))) (p/then' su/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)))
;; --- Create Image (Upload) ;; --- Create Image (Upload)
(defn- store-image-in-fs (defn- store-image-in-fs
[{:keys [name path] :as upload}] [{:keys [name path] :as upload}]
(prn "store-image-in-fs" upload)
(let [filename (fs/name name) (let [filename (fs/name name)
storage media/images-storage] storage media/images-storage]
(-> (ds/save storage filename path) (-> (ds/save storage filename path)
(vc/handle-on-context)))) (su/handle-on-context))))
(def ^:private create-image-sql (def ^:private create-image-sql
"insert into images (user_id, name, collection_id, path, width, height, mimetype) "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] (s/keys :req-un [::user ::name ::file ::width ::height ::mimetype]
:opt-un [::id ::collection-id])) :opt-un [::id ::collection-id]))
(sc/defmutation :create-image (sm/defmutation ::create-image
{:doc "Create (upload) new image."
:spec ::create-image}
[{:keys [file] :as params}] [{:keys [file] :as params}]
(when-not (valid-image-types? (:mtype file)) (when-not (valid-image-types? (:mtype file))
(ex/raise :type :validation (ex/raise :type :validation
@ -192,9 +149,7 @@
and user_id = $4 and user_id = $4
returning *;") returning *;")
(sc/defmutation :update-image (sm/defmutation ::update-image
{:doc "Update a image entry."
:spec ::update-image}
[{:keys [id name user collection-id] :as params}] [{:keys [id name user collection-id] :as params}]
(let [sql update-image-sql] (let [sql update-image-sql]
(db/query-one db/pool [sql id collection-id name user]))) (db/query-one db/pool [sql id collection-id name user])))
@ -206,9 +161,7 @@
(s/def ::copy-image (s/def ::copy-image
(s/keys :req-un [::id ::collection-id ::user])) (s/keys :req-un [::id ::collection-id ::user]))
(sc/defmutation :copy-image (sm/defmutation ::copy-image
{:doc "Copy image from one collection to an other."
:spec ::copy-image}
[{:keys [user id collection-id] :as params}] [{:keys [user id collection-id] :as params}]
(letfn [(copy-image [conn {:keys [path] :as image}] (letfn [(copy-image [conn {:keys [path] :as image}]
(-> (ds/lookup media/images-storage (:path image)) (-> (ds/lookup media/images-storage (:path image))
@ -221,7 +174,7 @@
(db/with-atomic [conn db/pool] (db/with-atomic [conn db/pool]
(-> (retrieve-image conn {:id id :user user}) (-> (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)))))) (p/then (partial copy-image conn))))))
;; --- Delete Image ;; --- Delete Image
@ -237,9 +190,7 @@
(s/def ::delete-image (s/def ::delete-image
(s/keys :req-un [::id ::user])) (s/keys :req-un [::id ::user]))
(sc/defmutation :delete-image (sm/defmutation ::delete-image
{:doc "Delete image entry."
:spec ::delete-image}
[{:keys [user id] :as params}] [{:keys [user id] :as params}]
(let [sql "update images (let [sql "update images
set deleted_at = clock_timestamp() set deleted_at = clock_timestamp()
@ -247,29 +198,3 @@
and user_id = $2 and user_id = $2
returning *"] returning *"]
(db/query-one db/pool [sql id user]))) (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 %)))))

View file

@ -4,22 +4,21 @@
;; ;;
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.services.pages (ns uxbox.services.mutations.pages
(:require (:require
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[promesa.core :as p] [promesa.core :as p]
[uxbox.db :as db] [uxbox.db :as db]
[uxbox.util.spec :as us] [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.sql :as sql]
[uxbox.util.time :as dt]
[uxbox.util.blob :as blob] [uxbox.util.blob :as blob]
[uxbox.util.uuid :as uuid])) [uxbox.util.uuid :as uuid]))
;; --- Helpers & Specs ;; --- Helpers & Specs
(declare decode-row)
;; TODO: validate `:data` and `:metadata` ;; TODO: validate `:data` and `:metadata`
(s/def ::id ::us/uuid) (s/def ::id ::us/uuid)
@ -29,84 +28,23 @@
(s/def ::project-id ::us/uuid) (s/def ::project-id ::us/uuid)
(s/def ::metadata any?) (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 ;; --- Mutation: Create Page
(declare create-page)
(s/def ::create-page (s/def ::create-page
(s/keys :req-un [::data ::user ::project-id ::name ::metadata] (s/keys :req-un [::data ::user ::project-id ::name ::metadata]
:opt-un [::id])) :opt-un [::id]))
(sv/defmutation ::create-page (sm/defmutation ::create-page
[{:keys [id user project-id name data metadata]}] [params]
(let [sql "insert into pages (id, user_id, project_id, name, data, metadata) (create-page db/pool params))
values ($1, $2, $3, $4, $5, $6) returning *"
(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)) id (or id (uuid/next))
data (blob/encode data) data (blob/encode data)
mdata (blob/encode metadata)] mdata (blob/encode metadata)]
@ -125,7 +63,7 @@
and deleted_at is null and deleted_at is null
for update;"] for update;"]
(-> (db/query-one conn [sql id]) (-> (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]}] (update-page [conn {:keys [id name version data metadata user]}]
(let [sql "update pages (let [sql "update pages
@ -136,15 +74,15 @@
where id = $5 where id = $5
and user_id = $6"] and user_id = $6"]
(-> (db/query-one conn [sql name version data metadata id user]) (-> (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]}] (update-history [conn {:keys [user id version data metadata]}]
(let [sql "insert into pages_history (user_id, page_id, version, data, metadata) (let [sql "insert into pages_history (user_id, page_id, version, data, metadata)
values ($1, $2, $3, $4, $5)"] values ($1, $2, $3, $4, $5)"]
(-> (db/query-one conn [sql user id version data metadata]) (-> (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}] [{:keys [id data metadata] :as params}]
(db/with-atomic [conn db/pool] (db/with-atomic [conn db/pool]
(-> (select-for-update conn id) (-> (select-for-update conn id)
@ -166,7 +104,7 @@
(s/def ::update-page-metadata (s/def ::update-page-metadata
(s/keys :req-un [::user ::project-id ::name ::metadata ::id])) (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]}] [{:keys [id user project-id name metadata]}]
(let [sql "update pages (let [sql "update pages
set name = $3, set name = $3,
@ -184,7 +122,7 @@
(s/def ::delete-page (s/def ::delete-page
(s/keys :req-un [::user ::id])) (s/keys :req-un [::user ::id]))
(sv/defmutation ::delete-page (sm/defmutation ::delete-page
[{:keys [id user]}] [{:keys [id user]}]
(let [sql "update pages (let [sql "update pages
set deleted_at = clock_timestamp() set deleted_at = clock_timestamp()
@ -193,8 +131,8 @@
and deleted_at is null and deleted_at is null
returning id"] returning id"]
(-> (db/query-one db/pool [sql id user]) (-> (db/query-one db/pool [sql id user])
(p/then sv/raise-not-found-if-nil) (p/then su/raise-not-found-if-nil)
(p/then sv/constantly-nil)))) (p/then su/constantly-nil))))
;; ;; --- Update Page History ;; ;; --- Update Page History
@ -211,29 +149,9 @@
;; (s/def ::update-page-history ;; (s/def ::update-page-history
;; (s/keys :req-un [::user ::id ::pinned ::label])) ;; (s/keys :req-un [::user ::id ::pinned ::label]))
;; (sv/defmutation :update-page-history ;; (sm/defmutation :update-page-history
;; {:doc "Update page history" ;; {:doc "Update page history"
;; :spec ::update-page-history} ;; :spec ::update-page-history}
;; [params] ;; [params]
;; (with-open [conn (db/connection)] ;; (with-open [conn (db/connection)]
;; (update-page-history conn params))) ;; (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))))

View file

@ -4,7 +4,7 @@
;; ;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.services.users (ns uxbox.services.mutations.profiles
(:require (:require
[buddy.hashers :as hashers] [buddy.hashers :as hashers]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
@ -17,7 +17,12 @@
[uxbox.emails :as emails] [uxbox.emails :as emails]
[uxbox.images :as images] [uxbox.images :as images]
[uxbox.media :as media] [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.blob :as blob]
[uxbox.util.exceptions :as ex] [uxbox.util.exceptions :as ex]
[uxbox.util.spec :as us] [uxbox.util.spec :as us]
@ -27,9 +32,6 @@
;; --- Helpers & Specs ;; --- Helpers & Specs
(declare decode-profile-row)
(declare strip-private-attrs)
(s/def ::email ::us/email) (s/def ::email ::us/email)
(s/def ::fullname ::us/string) (s/def ::fullname ::us/string)
(s/def ::metadata any?) (s/def ::metadata any?)
@ -39,42 +41,6 @@
(s/def ::user ::us/uuid) (s/def ::user ::us/uuid)
(s/def ::username ::us/string) (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) ;; --- Mutation: Update Profile (own)
(defn- check-username-and-email! (defn- check-username-and-email!
@ -110,14 +76,14 @@
and deleted_at is null and deleted_at is null
returning *"] returning *"]
(-> (db/query-one conn [sql id username email fullname (blob/encode metadata)]) (-> (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' decode-profile-row)
(p/then' strip-private-attrs)))) (p/then' strip-private-attrs))))
(s/def ::update-profile (s/def ::update-profile
(s/keys :req-un [::id ::username ::email ::fullname ::metadata])) (s/keys :req-un [::id ::username ::email ::fullname ::metadata]))
(sv/defmutation :update-profile (sm/defmutation :update-profile
{:doc "Update self profile." {:doc "Update self profile."
:spec ::update-profile} :spec ::update-profile}
[params] [params]
@ -144,13 +110,13 @@
and deleted_at is null and deleted_at is null
returning id"] returning id"]
(-> (db/query-one conn [sql user password]) (-> (db/query-one conn [sql user password])
(p/then' sv/raise-not-found-if-nil) (p/then' su/raise-not-found-if-nil)
(p/then' sv/constantly-nil)))) (p/then' su/constantly-nil))))
(s/def ::update-password (s/def ::update-password
(s/keys :req-un [::user ::us/password ::old-password])) (s/keys :req-un [::user ::us/password ::old-password]))
(sv/defmutation :update-password (sm/defmutation :update-password
{:doc "Update self password." {:doc "Update self password."
:spec ::update-password} :spec ::update-password}
[params] [params]
@ -168,7 +134,7 @@
(def valid-image-types? (def valid-image-types?
#{"image/jpeg", "image/png", "image/webp"}) #{"image/jpeg", "image/png", "image/webp"})
(sv/defmutation :update-profile-photo (sm/defmutation :update-profile-photo
{:doc "Update profile photo." {:doc "Update profile photo."
:spec ::update-profile-photo} :spec ::update-profile-photo}
[{:keys [user file] :as params}] [{:keys [user file] :as params}]
@ -176,7 +142,7 @@
(let [filename (fs/name name) (let [filename (fs/name name)
storage media/images-storage] storage media/images-storage]
(-> (ds/save storage filename path) (-> (ds/save storage filename path)
#_(sv/handle-on-context)))) #_(su/handle-on-context))))
(update-user-photo [path] (update-user-photo [path]
(let [sql "update users (let [sql "update users
@ -185,7 +151,7 @@
and deleted_at is null and deleted_at is null
returning *"] returning *"]
(-> (db/query-one db/pool [sql (str path) user]) (-> (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' strip-private-attrs)
(p/then resolve-thumbnail))))] (p/then resolve-thumbnail))))]
@ -216,33 +182,38 @@
:code ::username-or-email-already-exists)) :code ::username-or-email-already-exists))
params))))) params)))))
(defn- register-profile (defn create-profile
"Create the user entry on the database with limited input "Create the user entry on the database with limited input
filling all the other fields with defaults." filling all the other fields with defaults."
[conn {:keys [username fullname email password] :as params}] [conn {:keys [id username fullname email password metadata] :as params}]
(let [metadata (blob/encode {}) (let [id (or id (uuid/next))
metadata (blob/encode metadata)
password (hashers/encrypt password) password (hashers/encrypt password)
sqlv [create-user-sql sqlv [create-user-sql
(uuid/next) id
fullname fullname
username username
email email
password password
metadata]] metadata]]
(-> (db/query-one conn sqlv) (-> (db/query-one conn sqlv)
(p/then' decode-profile-row) (p/then' decode-profile-row))))
(p/then' strip-private-attrs)
#_(p/then (fn [profile] (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/send! {::emails/id :users/register
::emails/to (:email params) ::emails/to (:email params)
::emails/priority :high ::emails/priority :high
:name (:fullname params)}) :name (:fullname params)})
(p/then' (constantly profile)))))))) (p/then' (constantly profile)))))))
(s/def ::register-profile (s/def ::register-profile
(s/keys :req-un [::username ::email ::password ::fullname])) (s/keys :req-un [::username ::email ::password ::fullname]))
(sv/defmutation :register-profile (sm/defmutation :register-profile
{:doc "Register new user." {:doc "Register new user."
:spec ::register-profile} :spec ::register-profile}
[params] [params]
@ -366,16 +337,3 @@
;; (some-> (db/fetch-one conn sqlv) ;; (some-> (db/fetch-one conn sqlv)
;; (trim-user-attrs)))) ;; (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]))

View file

@ -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 <niwi@niwi.nz>
(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))))

View file

@ -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 <niwi@niwi.nz>
(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))))

View file

@ -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 <niwi@niwi.nz>
(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)))

View file

@ -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 <niwi@niwi.nz>
(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))

View file

@ -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 <niwi@niwi.nz>
(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 %)))))

View file

@ -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 <niwi@niwi.nz>
(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 %)))))

View file

@ -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 <niwi@niwi.nz>
(ns uxbox.services.queries.pages
(:require
[clojure.spec.alpha :as s]
[promesa.core :as p]
[uxbox.db :as db]
[uxbox.services.queries :as sq]
[uxbox.util.blob :as blob]
[uxbox.util.spec :as us]
[uxbox.util.sql :as sql]))
;; --- Helpers & Specs
(declare decode-row)
(s/def ::id ::us/uuid)
(s/def ::user ::us/uuid)
(s/def ::project-id ::us/uuid)
;; --- Query: Pages by Project
(s/def ::pages-by-project
(s/keys :req-un [::user ::project-id]))
(sq/defquery ::pages-by-project
[{:keys [user project-id] :as params}]
(let [sql "select pg.*,
pg.data,
pg.metadata
from pages as pg
where pg.user_id = $2
and pg.project_id = $1
and pg.deleted_at is null
order by pg.created_at asc;"]
(-> (db/query db/pool [sql project-id user])
(p/then #(mapv decode-row %)))))
;; --- Query: Page by Id
(s/def ::page
(s/keys :req-un [::user ::id]))
(sq/defquery ::page
[{:keys [user id] :as params}]
(let [sql "select pg.*,
pg.data,
pg.metadata
from pages as pg
where pg.user_id = $2
and pg.id = $1
and pg.deleted_at is null"]
(-> (db/query-one db/pool [sql id user])
(p/then' decode-row))))
;; --- Query: Page History
(s/def ::page-id ::us/uuid)
(s/def ::max ::us/integer)
(s/def ::pinned ::us/boolean)
(s/def ::since ::us/integer)
(s/def ::page-history
(s/keys :req-un [::page-id ::user]
:opt-un [::max ::pinned ::since]))
(sq/defquery ::page-history
[{:keys [page-id user since max pinned] :or {since Long/MAX_VALUE max 10}}]
(let [sql (-> (sql/from ["pages_history" "ph"])
(sql/select "ph.*")
(sql/where ["ph.user_id = ?" user]
["ph.page_id = ?" page-id]
["ph.version < ?" since]
(when pinned
["ph.pinned = ?" true]))
(sql/order "ph.version desc")
(sql/limit max))]
(-> (db/query db/pool (sql/fmt sql))
(p/then (partial mapv decode-row)))))
;; --- Helpers
(defn decode-row
[{:keys [data metadata] :as row}]
(when row
(cond-> row
data (assoc :data (blob/decode data))
metadata (assoc :metadata (blob/decode metadata)))))

View file

@ -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 <niwi@niwi.nz>
(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]))

View file

@ -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 <niwi@niwi.nz>
(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)))))

View file

@ -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 <niwi@niwi.nz>
(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))))

View file

@ -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 <niwi@niwi.nz>
(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)))

View file

@ -6,6 +6,7 @@
(ns uxbox.util.migrations (ns uxbox.util.migrations
(:require (:require
[clojure.tools.logging :as log]
[clojure.java.io :as io] [clojure.java.io :as io]
[clojure.spec.alpha :as s] [clojure.spec.alpha :as s]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -56,7 +57,7 @@
(-> (registered? pool modname (:name migration)) (-> (registered? pool modname (:name migration))
(p/then (fn [registered?] (p/then (fn [registered?]
(when-not registered? (when-not registered?
(println (str/format "applying migration - %s: %s" modname name)) (log/info (str/format "applying migration %s/%s" modname name))
(execute))))))) (execute)))))))
(defn- impl-migrate (defn- impl-migrate

View file

@ -6,6 +6,9 @@
[cuerdas.core :as str] [cuerdas.core :as str]
[mount.core :as mount] [mount.core :as mount]
[datoteka.storages :as st] [datoteka.storages :as st]
[uxbox.services.mutations.profiles :as profiles]
[uxbox.services.mutations.projects :as projects]
[uxbox.services.mutations.pages :as pages]
[uxbox.fixtures :as fixtures] [uxbox.fixtures :as fixtures]
[uxbox.migrations] [uxbox.migrations]
[uxbox.media] [uxbox.media]
@ -59,52 +62,29 @@
;; --- Users creation ;; --- Users creation
(declare decode-user-row)
(declare decode-page-row)
(defn create-user (defn create-user
[conn i] [conn i]
(let [sql "insert into users (id, fullname, username, email, password, metadata, photo) (profiles/create-profile conn {:id (mk-uuid "user" i)
values ($1, $2, $3, $4, $5, $6, '') returning *"] :fullname (str "User " i)
(-> (db/query-one conn [sql :username (str "user" i)
(mk-uuid "user" i) :email (str "user" i ".test@uxbox.io")
(str "User " i) :password "123123"
(str "user" i) :metadata {}}))
(str "user" i ".test@uxbox.io")
(hashers/encrypt "123123")
(blob/encode {})])
(p/then' decode-user-row))))
(defn create-project (defn create-project
[conn uid i] [conn user-id i]
(let [sql "insert into projects (id, user_id, name) (projects/create-project conn {:id (mk-uuid "project" i)
values ($1, $2, $3) returning *" :user user-id
name (str "sample project " i)] :name (str "sample project " i)}))
(db/query-one conn [sql (mk-uuid "project" i) uid name])))
(defn create-page (defn create-page
[conn uid pid i] [conn uid pid i]
(let [sql "insert into pages (id, user_id, project_id, name, data, metadata) (pages/create-page conn {:id (mk-uuid "page" i)
values ($1, $2, $3, $4, $5, $6) returning *" :user uid
data (blob/encode {:shapes []}) :project-id pid
mdata (blob/encode {}) :name (str "page" i)
name (str "page" i) :data {:shapes []}
id (mk-uuid "page" i)] :metadata {}}))
(-> (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)))))
(defn handle-error (defn handle-error
[err] [err]
@ -152,10 +132,9 @@
(do (do
(println "====> START ERROR") (println "====> START ERROR")
(if (= :spec-validation (:code error)) (if (= :spec-validation (:code error))
(do (s/explain-out (:data error))
(s/explain-out (:data error)) (prn error))
(println "====> END ERROR")) (println "====> END ERROR"))
(prn error)))
(do (do
(println "====> START RESPONSE") (println "====> START RESPONSE")
(prn result) (prn result)

View file

@ -11,14 +11,13 @@
[mockery.core :refer [with-mock]] [mockery.core :refer [with-mock]]
[uxbox.db :as db] [uxbox.db :as db]
[uxbox.emails :as emails] [uxbox.emails :as emails]
[uxbox.services.core :as sv]
[uxbox.tests.helpers :as th])) [uxbox.tests.helpers :as th]))
(t/use-fixtures :once th/state-init) (t/use-fixtures :once th/state-init)
(t/use-fixtures :each th/database-reset) (t/use-fixtures :each th/database-reset)
(t/deftest register-email-rendering (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 (map? result))
(t/is (contains? result :subject)) (t/is (contains? result :subject))
(t/is (contains? result :body)) (t/is (contains? result :body))
@ -27,7 +26,7 @@
(t/is (vector? (:body result))))) (t/is (vector? (:body result)))))
(t/deftest email-sending-and-sendmail-job (t/deftest email-sending-and-sendmail-job
(let [res @(emails/send! emails/register {:to "example@uxbox.io"})] (let [res @(emails/send! emails/register {:to "example@uxbox.io" :name "foo"})]
(t/is (nil? res))) (t/is (nil? res)))
(with-mock mock (with-mock mock
{:target 'uxbox.jobs.sendmail/impl-sendmail {:target 'uxbox.jobs.sendmail/impl-sendmail

View file

@ -8,35 +8,34 @@
(:require (:require
[clojure.test :as t] [clojure.test :as t]
[promesa.core :as p] [promesa.core :as p]
[buddy.hashers :as hashers]
[uxbox.db :as db] [uxbox.db :as db]
[uxbox.services.core :as sv] [uxbox.services.mutations :as sm]
[uxbox.tests.helpers :as th])) [uxbox.tests.helpers :as th]))
(t/use-fixtures :once th/state-init) (t/use-fixtures :once th/state-init)
(t/use-fixtures :each th/database-reset) (t/use-fixtures :each th/database-reset)
(t/deftest test-failed-auth (t/deftest failed-auth
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
event {:username "user1" event {:username "user1"
:type :login ::sm/type :login
:password "foobar" :password "foobar"
:metadata "1" :metadata "1"
:scope "foobar"} :scope "foobar"}
[err res] (th/try-on out (th/try-on! (sm/handle event))]
(sv/mutation event))] ;; (th/print-result! out)
(t/is (nil? res)) (t/is (map? (:error out)))
(t/is (= (:type err) :validation)) (t/is (= (get-in out [:error :type]) :validation))
(t/is (= (:code err) :uxbox.services.auth/wrong-credentials)))) (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) (let [user @(th/create-user db/pool 1)
event {:username "user1" event {:username "user1"
:type :login ::sm/type :login
:password "123123" :password "123123"
:metadata "1" :metadata "1"
:scope "foobar"} :scope "foobar"}
[err res] (th/try-on out (th/try-on! (sm/handle event))]
(sv/mutation event))] ;; (th/print-result! out)
(t/is (= res (:id user))) (t/is (nil? (:error out)))
(t/is (nil? err)))) (t/is (= (get-in out [:result :id]) (:id user)))))

View file

@ -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))))))

View file

@ -5,7 +5,8 @@
[promesa.core :as p] [promesa.core :as p]
[uxbox.db :as db] [uxbox.db :as db]
[uxbox.http :as http] [uxbox.http :as http]
[uxbox.services.core :as sv] [uxbox.services.mutations :as sm]
[uxbox.services.queries :as sq]
[uxbox.tests.helpers :as th])) [uxbox.tests.helpers :as th]))
(t/use-fixtures :once th/state-init) (t/use-fixtures :once th/state-init)
@ -14,13 +15,13 @@
(t/deftest mutation-create-page (t/deftest mutation-create-page
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
proj @(th/create-project db/pool (:id user) 1) proj @(th/create-project db/pool (:id user) 1)
data {::sv/type :create-page data {::sm/type :create-page
:data {:shapes []} :data {:shapes []}
:metadata {} :metadata {}
:project-id (:id proj) :project-id (:id proj)
:name "test page" :name "test page"
:user (:id user)} :user (:id user)}
res (th/try-on! (sv/mutation data))] res (th/try-on! (sm/handle data))]
(t/is (nil? (:error res))) (t/is (nil? (:error res)))
(t/is (uuid? (get-in res [:result :id]))) (t/is (uuid? (get-in res [:result :id])))
(let [rsp (:result res)] (let [rsp (:result res)]
@ -33,35 +34,35 @@
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
proj @(th/create-project db/pool (:id user) 1) proj @(th/create-project db/pool (:id user) 1)
page @(th/create-page db/pool (:id user) (:id proj) 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) :id (:id page)
:data {:shapes [1 2 3]} :data {:shapes [1 2 3]}
:metadata {:foo 2} :metadata {:foo 2}
:project-id (:id proj) :project-id (:id proj)
:name "test page" :name "test page"
:user (:id user)} :user (:id user)}
res (th/try-on! (sv/mutation data))] res (th/try-on! (sm/handle data))]
;; (th/print-result! res) ;; (th/print-result! res)
(t/is (nil? (:error res))) (t/is (nil? (:error res)))
(t/is (= (:id data) (get-in res [:result :id]))) (t/is (= (:id data) (get-in res [:result :id])))
(t/is (= (:user data) (get-in res [:result :user-id]))) #_(t/is (= (:user data) (get-in res [:result :user-id])))
(t/is (= (:name data) (get-in res [:result :name]))) #_(t/is (= (:name data) (get-in res [:result :name])))
(t/is (= (:data data) (get-in res [:result :data]))) #_(t/is (= (:data data) (get-in res [:result :data])))
(t/is (= (:metadata data) (get-in res [:result :metadata]))))) #_(t/is (= (:metadata data) (get-in res [:result :metadata])))))
(t/deftest mutation-update-page-metadata (t/deftest mutation-update-page-metadata
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
proj @(th/create-project db/pool (:id user) 1) proj @(th/create-project db/pool (:id user) 1)
page @(th/create-page db/pool (:id user) (:id proj) 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) :id (:id page)
:metadata {:foo 2} :metadata {:foo 2}
:project-id (:id proj) :project-id (:id proj)
:name "test page" :name "test page"
:user (:id user)} :user (:id user)}
res (th/try-on! (sv/mutation data))] res (th/try-on! (sm/handle data))]
;; (th/print-result! res) ;; (th/print-result! res)
(t/is (nil? (:error res))) (t/is (nil? (:error res)))
@ -74,10 +75,10 @@
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
proj @(th/create-project db/pool (:id user) 1) proj @(th/create-project db/pool (:id user) 1)
page @(th/create-page db/pool (:id user) (:id proj) 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) :id (:id page)
:user (:id user)} :user (:id user)}
res (th/try-on! (sv/mutation data))] res (th/try-on! (sm/handle data))]
;; (th/print-result! res) ;; (th/print-result! res)
(t/is (nil? (:error res))) (t/is (nil? (:error res)))
@ -87,10 +88,10 @@
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
proj @(th/create-project db/pool (:id user) 1) proj @(th/create-project db/pool (:id user) 1)
page @(th/create-page db/pool (:id user) (:id proj) 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) :project-id (:id proj)
:user (:id user)} :user (:id user)}
res (th/try-on! (sv/query data))] res (th/try-on! (sq/handle data))]
;; (th/print-result! res) ;; (th/print-result! res)
(t/is (nil? (:error res))) (t/is (nil? (:error res)))

View file

@ -4,29 +4,35 @@
[promesa.core :as p] [promesa.core :as p]
[uxbox.db :as db] [uxbox.db :as db]
[uxbox.http :as http] [uxbox.http :as http]
[uxbox.services.core :as sv] [uxbox.services.mutations :as sm]
[uxbox.services.queries :as sq]
[uxbox.tests.helpers :as th])) [uxbox.tests.helpers :as th]))
(t/use-fixtures :once th/state-init) (t/use-fixtures :once th/state-init)
(t/use-fixtures :each th/database-reset) (t/use-fixtures :each th/database-reset)
;; TODO: migrate from try-on to try-on!
(t/deftest query-project-list (t/deftest query-project-list
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
proj @(th/create-project db/pool (:id user) 1) proj @(th/create-project db/pool (:id user) 1)
data {::sv/type :projects data {::sq/type :projects
:user (:id user)} :user (:id user)}
[err rsp] (th/try-on (sv/query data))] out (th/try-on! (sq/handle data))]
(t/is (nil? err))
(t/is (= 1 (count rsp))) ;; (th/print-result! out)
(t/is (= (:id proj) (get-in rsp [0 :id])))
(t/is (= (:name proj (get-in rsp [0 :name])))))) (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 (t/deftest mutation-create-project
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
data {::sv/type :create-project data {::sm/type :create-project
:user (:id user) :user (:id user)
:name "test project"} :name "test project"}
[err rsp] (th/try-on (sv/mutation data))] [err rsp] (th/try-on (sm/handle data))]
;; (prn "RESPONSE:" err rsp) ;; (prn "RESPONSE:" err rsp)
(t/is (nil? err)) (t/is (nil? err))
(t/is (= (:user data) (:user-id rsp))) (t/is (= (:user data) (:user-id rsp)))
@ -35,11 +41,11 @@
(t/deftest mutation-update-project (t/deftest mutation-update-project
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
proj @(th/create-project db/pool (:id user) 1) proj @(th/create-project db/pool (:id user) 1)
data {::sv/type :update-project data {::sm/type :update-project
:id (:id proj) :id (:id proj)
:name "test project mod" :name "test project mod"
:user (:id user)} :user (:id user)}
[err rsp] (th/try-on (sv/mutation data))] [err rsp] (th/try-on (sm/handle data))]
;; (prn "RESPONSE:" err rsp) ;; (prn "RESPONSE:" err rsp)
(t/is (nil? err)) (t/is (nil? err))
(t/is (= (:id data) (:id rsp))) (t/is (= (:id data) (:id rsp)))
@ -49,10 +55,10 @@
(t/deftest mutation-delete-project (t/deftest mutation-delete-project
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
proj @(th/create-project db/pool (:id user) 1) proj @(th/create-project db/pool (:id user) 1)
data {::sv/type :delete-project data {::sm/type :delete-project
:id (:id proj) :id (:id proj)
:user (:id user)} :user (:id user)}
[err rsp] (th/try-on (sv/mutation data))] [err rsp] (th/try-on (sm/handle data))]
;; (prn "RESPONSE:" err rsp) ;; (prn "RESPONSE:" err rsp)
(t/is (nil? err)) (t/is (nil? err))
(t/is (nil? rsp)) (t/is (nil? rsp))

View file

@ -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]))))))

View file

@ -5,10 +5,10 @@
[promesa.core :as p] [promesa.core :as p]
[cuerdas.core :as str] [cuerdas.core :as str]
[datoteka.core :as fs] [datoteka.core :as fs]
;; [buddy.hashers :as hashers]
[vertx.core :as vc] [vertx.core :as vc]
[uxbox.db :as db] [uxbox.db :as db]
[uxbox.services.core :as sv] [uxbox.services.mutations :as sm]
[uxbox.services.queries :as sq]
[uxbox.tests.helpers :as th])) [uxbox.tests.helpers :as th]))
(t/use-fixtures :once th/state-init) (t/use-fixtures :once th/state-init)
@ -16,9 +16,9 @@
(t/deftest test-query-profile (t/deftest test-query-profile
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
event {::sv/type :profile event {::sq/type :profile
:user (:id user)} :user (:id user)}
[err rsp] (th/try-on (sv/query event))] [err rsp] (th/try-on (sq/handle event))]
;; (println "RESPONSE:" resp))) ;; (println "RESPONSE:" resp)))
(t/is (nil? err)) (t/is (nil? err))
(t/is (= (:fullname rsp) "User 1")) (t/is (= (:fullname rsp) "User 1"))
@ -30,12 +30,12 @@
(t/deftest test-mutation-update-profile (t/deftest test-mutation-update-profile
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
event (assoc user event (assoc user
::sv/type :update-profile ::sm/type :update-profile
:fullname "Full Name" :fullname "Full Name"
:username "user222" :username "user222"
:metadata {:foo "bar"} :metadata {:foo "bar"}
:email "user222@uxbox.io") :email "user222@uxbox.io")
[err data] (th/try-on (sv/mutation event))] [err data] (th/try-on (sm/handle event))]
;; (println "RESPONSE:" err data) ;; (println "RESPONSE:" err data)
(t/is (nil? err)) (t/is (nil? err))
(t/is (= (:fullname data) "Full Name")) (t/is (= (:fullname data) "Full Name"))
@ -46,13 +46,13 @@
(t/deftest test-mutation-update-profile-photo (t/deftest test-mutation-update-profile-photo
(let [user @(th/create-user db/pool 1) (let [user @(th/create-user db/pool 1)
event {::sv/type :update-profile-photo event {::sm/type :update-profile-photo
:user (:id user) :user (:id user)
:file {:name "sample.jpg" :file {:name "sample.jpg"
:path (fs/path "test/uxbox/tests/_files/sample.jpg") :path (fs/path "test/uxbox/tests/_files/sample.jpg")
:size 123123 :size 123123
:mtype "image/jpeg"}} :mtype "image/jpeg"}}
[err rsp] (th/try-on (sv/mutation event))] [err rsp] (th/try-on (sm/handle event))]
;; (prn "RESPONSE:" [err rsp]) ;; (prn "RESPONSE:" [err rsp])
(t/is (nil? err)) (t/is (nil? err))
(t/is (= (:id user) (:id rsp))) (t/is (= (:id user) (:id rsp)))
@ -64,7 +64,7 @@
;; :email "user222@uxbox.io" ;; :email "user222@uxbox.io"
;; :password "user222" ;; :password "user222"
;; ::sv/type :register-profile} ;; ::sv/type :register-profile}
;; [err rsp] (th/try-on (sv/mutation data))] ;; [err rsp] (th/try-on (sm/handle data))]
;; (println "RESPONSE:" err rsp))) ;; (println "RESPONSE:" err rsp)))
;; ;; (t/deftest test-http-validate-recovery-token ;; ;; (t/deftest test-http-validate-recovery-token