diff --git a/backend/deps.edn b/backend/deps.edn index afc2773f5..422baadde 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -44,7 +44,7 @@ mount/mount {:mvn/version "0.1.16"} environ/environ {:mvn/version "1.1.0"}} - :paths ["src" "vendor" "resources"] + :paths ["src" "vendor" "resources" "../common"] :aliases {:dev {:extra-deps diff --git a/backend/src/uxbox/images.clj b/backend/src/uxbox/images.clj index 1b4137e35..6b2c7633a 100644 --- a/backend/src/uxbox/images.clj +++ b/backend/src/uxbox/images.clj @@ -43,8 +43,8 @@ width 200 height 200} :as opts}] - {:pre [(us/valid? ::thumbnail-opts opts) - (fs/path? input)]} + (s/assert ::thumbnail-opts opts) + (s/assert fs/path? input) (let [tmp (fs/create-tempfile :suffix (str "." format)) opr (doto (IMOperation.) (.addImage) @@ -60,9 +60,7 @@ (defn make-thumbnail [input {:keys [width height format quality] :as opts}] - {:pre [(us/valid? ::thumbnail-opts opts) - (or (string? input) - (fs/path input))]} + (s/assert ::thumbnail-opts opts) (let [[filename ext] (fs/split-ext (fs/name input)) suffix (->> [width height quality format] (interpose ".") diff --git a/backend/src/uxbox/media_loader.clj b/backend/src/uxbox/media_loader.clj index c49eef0e6..aea849529 100644 --- a/backend/src/uxbox/media_loader.clj +++ b/backend/src/uxbox/media_loader.clj @@ -40,7 +40,7 @@ (s/def ::name ::us/string) (s/def ::path ::us/string) -(s/def ::regex us/regex?) +(s/def ::regex #(instance? java.util.regex.Pattern %)) (s/def ::import-item (s/keys :req-un [::name ::path ::regex])) diff --git a/backend/src/uxbox/services/mutations/project_pages.clj b/backend/src/uxbox/services/mutations/project_pages.clj index 579b53855..820815ae0 100644 --- a/backend/src/uxbox/services/mutations/project_pages.clj +++ b/backend/src/uxbox/services/mutations/project_pages.clj @@ -13,22 +13,22 @@ [uxbox.services.mutations.project-files :as files] [uxbox.services.queries.project-pages :refer [decode-row]] [uxbox.services.util :as su] + [uxbox.common.pages :as cp] + [uxbox.common.spec :as cs] + [uxbox.util.exceptions :as ex] [uxbox.util.blob :as blob] - [uxbox.util.spec :as us] [uxbox.util.sql :as sql] [uxbox.util.uuid :as uuid])) ;; --- Helpers & Specs -;; TODO: validate `:data` and `:metadata` - -(s/def ::id ::us/uuid) -(s/def ::name ::us/string) -(s/def ::data any?) -(s/def ::user ::us/uuid) -(s/def ::project-id ::us/uuid) -(s/def ::metadata any?) -(s/def ::ordering ::us/number) +(s/def ::id ::cs/uuid) +(s/def ::name ::cs/string) +(s/def ::data ::cp/data) +(s/def ::user ::cs/uuid) +(s/def ::project-id ::cs/uuid) +(s/def ::metadata ::cp/metadata) +(s/def ::ordering ::cs/number) ;; --- Mutation: Create Page diff --git a/backend/src/uxbox/util/blob.clj b/backend/src/uxbox/util/blob.clj index f79e11cdb..6d467fdb9 100644 --- a/backend/src/uxbox/util/blob.clj +++ b/backend/src/uxbox/util/blob.clj @@ -26,9 +26,6 @@ Buffer (->bytes [data] (.getBytes ^Buffer data)) - ;; org.jooq.JSONB - ;; (->bytes [data] (->bytes (.toString data))) - String (->bytes [data] (.getBytes ^String data "UTF-8"))) diff --git a/backend/src/uxbox/util/data.clj b/backend/src/uxbox/util/data.clj index 88cf1785d..f4e6c1743 100644 --- a/backend/src/uxbox/util/data.clj +++ b/backend/src/uxbox/util/data.clj @@ -9,6 +9,8 @@ (:require [clojure.walk :as walk] [cuerdas.core :as str])) +;; TODO: move to uxbox.common.helpers + (defn dissoc-in [m [k & ks :as keys]] (if ks diff --git a/backend/src/uxbox/util/exceptions.clj b/backend/src/uxbox/util/exceptions.clj index b39a9e8ce..c32522f17 100644 --- a/backend/src/uxbox/util/exceptions.clj +++ b/backend/src/uxbox/util/exceptions.clj @@ -8,6 +8,8 @@ "A helpers for work with exceptions." (:require [clojure.spec.alpha :as s])) +;; TODO: moved to uxbox.common.exceptions + (s/def ::type keyword?) (s/def ::code keyword?) (s/def ::mesage string?) diff --git a/backend/src/uxbox/util/transit.clj b/backend/src/uxbox/util/transit.clj index a601d6e2c..f97bec799 100644 --- a/backend/src/uxbox/util/transit.clj +++ b/backend/src/uxbox/util/transit.clj @@ -65,10 +65,6 @@ (with-open [input (ByteArrayInputStream. data)] (read! (reader input opts))) - ;; ;; TODO: temporal - ;; (instance? org.jooq.JSONB data) - ;; (decode (.toString data) opts) - (string? data) (decode (.getBytes data "UTF-8") opts) diff --git a/backend/test/user.clj b/backend/test/user.clj index 7c98b0bb8..b9700ed1f 100644 --- a/backend/test/user.clj +++ b/backend/test/user.clj @@ -12,19 +12,17 @@ [clojure.pprint :refer [pprint]] [clojure.test :as test] [clojure.java.io :as io] + [clojure.repl :refer :all] [criterium.core :refer [quick-bench bench with-progress-reporting]] [expound.alpha :as expound] [promesa.core :as p] - [sieppari.core :as sp] - [sieppari.context :as spx] - [buddy.core.codecs :as codecs] - [buddy.core.codecs.base64 :as b64] - [buddy.core.nonce :as nonce] + ;; [sieppari.core :as sp] + ;; [sieppari.context :as spx] + ;; [buddy.core.codecs :as codecs] + ;; [buddy.core.codecs.base64 :as b64] + ;; [buddy.core.nonce :as nonce] [mount.core :as mount] - [uxbox.main] - [uxbox.util.sql :as sql] - [uxbox.util.blob :as blob]) - (:gen-class)) + [uxbox.main])) (defmacro run-quick-bench [& exprs] @@ -51,11 +49,11 @@ ;; --- Development Stuff -(defn- make-secret - [] - (-> (nonce/random-bytes 64) - (b64/encode true) - (codecs/bytes->str))) +;; (defn- make-secret +;; [] +;; (-> (nonce/random-bytes 64) +;; (b64/encode true) +;; (codecs/bytes->str))) (defn- start [] diff --git a/common/uxbox/common/exceptions.cljc b/common/uxbox/common/exceptions.cljc new file mode 100644 index 000000000..17c7cf3b8 --- /dev/null +++ b/common/uxbox/common/exceptions.cljc @@ -0,0 +1,32 @@ +;; 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-2019 Andrey Antukh + +(ns uxbox.util.exceptions + "A helpers for work with exceptions." + (:require [clojure.spec.alpha :as s])) + +(s/def ::type keyword?) +(s/def ::code keyword?) +(s/def ::mesage string?) +(s/def ::hint string?) + +(s/def ::error + (s/keys :req-un [::type] + :opt-un [::code + ::hint + ::mesage])) + +(defn error + [& {:keys [type code message hint cause] :as params}] + (s/assert ::error params) + (let [message (or message hint "") + payload (dissoc params :cause :message)] + (ex-info message payload cause))) + +#?(:clj + (defmacro raise + [& args] + `(throw (error ~@args)))) diff --git a/common/uxbox/common/pages.cljc b/common/uxbox/common/pages.cljc new file mode 100644 index 000000000..3eee32b88 --- /dev/null +++ b/common/uxbox/common/pages.cljc @@ -0,0 +1,96 @@ +(ns uxbox.common.pages + "A common (clj/cljs) functions and specs for pages." + (:require + [clojure.spec.alpha :as s] + [uxbox.common.spec :as cs])) + +;; --- Specs + +(s/def ::id ::cs/uuid) +(s/def ::name string?) +(s/def ::type keyword?) + +;; Metadata related +(s/def ::grid-x-axis ::cs/number) +(s/def ::grid-y-axis ::cs/number) +(s/def ::grid-color string?) +(s/def ::background string?) +(s/def ::background-opacity ::cs/number) + +;; Page related +(s/def ::file-id ::cs/uuid) +(s/def ::user ::cs/uuid) +(s/def ::created-at ::cs/inst) +(s/def ::modified-at ::cs/inst) +(s/def ::version ::cs/number) +(s/def ::ordering ::cs/number) + +;; Page Data related +(s/def ::shape + (s/keys :req-un [::type ::name] + :opt-un [::id])) + +(s/def ::shapes (s/coll-of ::cs/uuid :kind vector?)) +(s/def ::canvas (s/coll-of ::cs/uuid :kind vector?)) + +(s/def ::shapes-by-id + (s/map-of uuid? ::shape)) + +;; Main + +(s/def ::data + (s/keys :req-un [::shapes ::canvas ::shapes-by-id])) + + +(s/def ::metadata + (s/keys :opt-un [::grid-y-axis + ::grid-x-axis + ::grid-color + ::background + ::background-opacity])) + +(s/def ::opeation + (s/or :mod-shape (s/cat :name #(= % :mod-shape) + :id uuid? + :attr keyword? + :value any?) + :add-shape (s/cat :name #(= % :add-shape) + :id uuid? + :data any?) + :del-shape (s/cat :name #(= % :del-shape) + :id uuid?))) + +;; --- Operations Processing Impl + +(declare process-operation) +(declare process-add-shape) +(declare process-mod-shape) +(declare process-del-shape) + +(defn process-ops + [data operations] + (reduce process-operation data operations)) + +(defn- process-operation + [data operation] + (case (first operation) + :add-shape (process-add-shape data operation) + :mod-shape (process-mod-shape data operation) + :del-shape (process-del-shape data operation))) + +(defn- process-add-shape + [data {:keys [id data]}] + (-> data + (update :shapes conj id) + (update :shapes-by-id assoc id data))) + +(defn- process-mod-shape + [data {:keys [id attr value]}] + (update-in data [:shapes-by-id id] assoc attr value)) + +(defn- process-del-shape + [data {:keys [id attr value]}] + (-> data + (update :shapes (fn [s] (filterv #(not= % id) s))) + (update :shapes-by-id dissoc id))) + diff --git a/common/uxbox/common/spec.cljc b/common/uxbox/common/spec.cljc new file mode 100644 index 000000000..84dab7391 --- /dev/null +++ b/common/uxbox/common/spec.cljc @@ -0,0 +1,120 @@ +;; 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-2019 Andrey Antukh + +(ns uxbox.common.spec + (:require + [clojure.spec.alpha :as s] + [cuerdas.core :as str] + #?(:clj [datoteka.core :as fs]))) + +(s/check-asserts true) + +;; --- Constants + +(def email-rx + #"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$") + +(def uuid-rx + #"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$") + +(def number-rx + #"^[+-]?([0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)([eE][+-]?[0-9]+)?$") + +;; --- Predicates + +(defn email? + [v] + (and (string? v) + (re-matches email-rx v))) + +;; --- Conformers + +(defn- uuid-conformer + [v] + (cond + (uuid? v) v + (string? v) + (cond + (re-matches uuid-rx v) + #?(:clj (java.util.UUID/fromString v) + :cljs (uuid v)) + + (str/empty? v) + nil + + :else + ::s/invalid) + :else ::s/invalid)) + +(defn- integer-conformer + [v] + (cond + (integer? v) v + (string? v) + (if (re-matches #"^[-+]?\d+$" v) + (Long/parseLong v) + ::s/invalid) + :else ::s/invalid)) + +(defn boolean-conformer + [v] + (cond + (boolean? v) v + (string? v) + (if (re-matches #"^(?:t|true|false|f|0|1)$" v) + (contains? #{"t" "true" "1"} v) + ::s/invalid) + :else ::s/invalid)) + +(defn boolean-unformer + [v] + (if v "true" "false")) + +#?(:clj + (defn path-conformer + [v] + (cond + (string? v) (fs/path v) + (fs/path? v) v + :else ::s/invalid))) + +(defn- number-conformer + [v] + (cond + (number? v) v + + (str/numeric? v) + #?(:clj (Double/parseDouble v) + :cljs (js/parseFloat v)) + + :else ::s/invalid)) + +;; --- Default Specs + +(s/def ::string string?) +(s/def ::integer (s/conformer integer-conformer str)) +(s/def ::uuid (s/conformer uuid-conformer str)) +(s/def ::boolean (s/conformer boolean-conformer boolean-unformer)) +(s/def ::number (s/conformer number-conformer str)) + +(s/def ::inst inst?) +(s/def ::positive pos?) +(s/def ::negative neg?) +(s/def ::uploaded-file any?) +(s/def ::email email?) +(s/def ::file any?) + +;; Clojure Specific +#?(:clj + (do + (s/def ::bytes bytes?) + (s/def ::name ::string) + (s/def ::size ::integer) + (s/def ::mtype ::string) + (s/def ::path (s/conformer path-conformer str)) + (s/def ::upload + (s/keys :req-un [::name ::path ::size ::mtype])))) + diff --git a/frontend/src/uxbox/main/data/pages.cljs b/frontend/src/uxbox/main/data/pages.cljs index 179724fdb..ab7a7de7e 100644 --- a/frontend/src/uxbox/main/data/pages.cljs +++ b/frontend/src/uxbox/main/data/pages.cljs @@ -30,12 +30,14 @@ (s/def ::version ::us/number) (s/def ::width (s/and ::us/number ::us/positive)) (s/def ::height (s/and ::us/number ::us/positive)) + (s/def ::grid-x-axis ::us/number) (s/def ::grid-y-axis ::us/number) (s/def ::grid-color ::us/string) -(s/def ::ordering ::us/number) (s/def ::background ::us/string) (s/def ::background-opacity ::us/number) + +(s/def ::ordering ::us/number) (s/def ::user ::us/uuid) (s/def ::metadata @@ -45,12 +47,14 @@ ::background ::background-opacity])) +;; TODO: start using uxbox.common.pagedata/data spec ... + (s/def ::minimal-shape (s/keys :req-un [::type ::name] :opt-un [::id])) -(s/def ::shapes (s/every ::us/uuid :kind vector? :into [])) -(s/def ::canvas (s/every ::us/uuid :kind vector? :into [])) +(s/def ::shapes (s/coll-of ::us/uuid :kind vector?)) +(s/def ::canvas (s/coll-of ::us/uuid :kind vector?)) (s/def ::shapes-by-id (s/map-of ::us/uuid ::minimal-shape)) diff --git a/frontend/src/uxbox/util/data.cljs b/frontend/src/uxbox/util/data.cljs index 1fd43f8da..f26dcf61e 100644 --- a/frontend/src/uxbox/util/data.cljs +++ b/frontend/src/uxbox/util/data.cljs @@ -9,6 +9,8 @@ (:require [cljs.reader :as r] [cuerdas.core :as str])) +;; TODO: partially move to uxbox.common.helpers + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data structure manipulation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;