♻️ 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>
</Appenders>
<Loggers>
<Logger name="io.vertx.sqlclient.impl.SocketConnectionBase" level="INFO"/>
<Root level="info">
<AppenderRef ref="console"/>
</Root>

View file

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

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.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)})))

View file

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

View file

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

View file

@ -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")}
]})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

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

View file

@ -4,15 +4,15 @@
;;
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
(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))))

View file

@ -4,8 +4,7 @@
;;
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
(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 %)))))

View file

@ -4,22 +4,21 @@
;;
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
(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))))

View file

@ -4,7 +4,7 @@
;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(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' 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]))

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
(: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

View file

@ -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))
(prn error))
(println "====> END ERROR"))
(prn error)))
(do
(println "====> START RESPONSE")
(prn result)

View file

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

View file

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

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

View file

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

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]
[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