diff --git a/backend/deps.edn b/backend/deps.edn index fdf05436ac..ecb80c9087 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -5,11 +5,13 @@ :deps {org.clojure/clojure {:mvn/version "1.10.1"} org.clojure/data.json {:mvn/version "1.0.0"} + org.clojure/core.async {:mvn/version "1.2.603"} ;; Logging org.clojure/tools.logging {:mvn/version "1.1.0"} org.apache.logging.log4j/log4j-api {:mvn/version "2.13.2"} org.apache.logging.log4j/log4j-core {:mvn/version "2.13.2"} + org.apache.logging.log4j/log4j-web {:mvn/version "2.13.2"} org.apache.logging.log4j/log4j-jul {:mvn/version "2.13.2"} org.apache.logging.log4j/log4j-slf4j-impl {:mvn/version "2.13.2"} @@ -17,14 +19,14 @@ instaparse/instaparse {:mvn/version "1.4.10"} com.cognitect/transit-clj {:mvn/version "1.0.324"} - ;; TODO: vendorize pgclient under `vertx-clojure/vertx-pgclient` - io.vertx/vertx-pg-client {:mvn/version "4.0.0-milestone4"} io.lettuce/lettuce-core {:mvn/version "5.2.2.RELEASE"} java-http-clj/java-http-clj {:mvn/version "0.4.1"} - vertx-clojure/vertx - {:local/root "vendor/vertx" - :deps/manifest :pom} + info.sunng/ring-jetty9-adapter {:mvn/version "0.12.8"} + seancorfield/next.jdbc {:mvn/version "1.0.424"} + metosin/reitit-ring {:mvn/version "0.4.2"} + org.postgresql/postgresql {:mvn/version "42.2.12"} + com.zaxxer/HikariCP {:mvn/version "3.4.3"} funcool/datoteka {:mvn/version "1.2.0"} funcool/promesa {:mvn/version "5.1.0"} diff --git a/backend/src/uxbox/core.clj b/backend/src/uxbox/core.clj deleted file mode 100644 index 44290af85b..0000000000 --- a/backend/src/uxbox/core.clj +++ /dev/null @@ -1,18 +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/. -;; -;; This Source Code Form is "Incompatible With Secondary Licenses", as -;; defined by the Mozilla Public License, v. 2.0. -;; -;; Copyright (c) 2019-2020 Andrey Antukh - -(ns uxbox.core - (:require - [vertx.core :as vc] - [vertx.timers :as vt] - [mount.core :as mount :refer [defstate]])) - -(defstate system - :start (vc/system) - :stop (vc/stop system)) diff --git a/backend/src/uxbox/db.clj b/backend/src/uxbox/db.clj index a32fd107ed..538356d7fd 100644 --- a/backend/src/uxbox/db.clj +++ b/backend/src/uxbox/db.clj @@ -6,49 +6,109 @@ (ns uxbox.db (:require + [clojure.string :as str] [clojure.tools.logging :as log] [lambdaisland.uri :refer [uri]] [mount.core :as mount :refer [defstate]] - [promesa.core :as p] + [next.jdbc :as jdbc] + [next.jdbc.date-time :as jdbc-dt] + [next.jdbc.optional :as jdbc-opt] + [next.jdbc.result-set :as jdbc-rs] + [next.jdbc.sql :as jdbc-sql] + [next.jdbc.sql.builder :as jdbc-bld] [uxbox.common.exceptions :as ex] [uxbox.config :as cfg] - [uxbox.core :refer [system]] - [uxbox.util.data :as data] - [uxbox.util.pgsql :as pg] - [vertx.core :as vx])) + [uxbox.util.data :as data]) + (:import + com.zaxxer.hikari.HikariConfig + com.zaxxer.hikari.HikariDataSource)) + +(defn- create-datasource-config + [cfg] + (let [dburi (:database-uri cfg) + username (:database-username cfg) + password (:database-password cfg) + config (HikariConfig.)] + (doto config + (.setJdbcUrl (str "jdbc:" dburi)) + (.setAutoCommit true) + (.setReadOnly false) + (.setConnectionTimeout 30000) + (.setValidationTimeout 5000) + (.setIdleTimeout 600000) + (.setMaxLifetime 1800000) + (.setMinimumIdle 10) + (.setMaximumPoolSize 20)) + (when username (.setUsername config username)) + (when password (.setPassword config password)) + config)) (defn- create-pool - [config system] - (let [dburi (:database-uri config) - username (:database-username config) - password (:database-password config) - dburi (-> (uri dburi) - (assoc :user username) - (assoc :password password) - (str))] - (log/info "creating connection pool with" dburi) - (pg/pool dburi {:system system :max-size 8}))) + [cfg] + (let [dsc (create-datasource-config cfg)] + (jdbc-dt/read-as-instant) + (HikariDataSource. dsc))) (defstate pool - :start (create-pool cfg/config system)) + :start (create-pool cfg/config) + :stop (.close pool)) (defmacro with-atomic - [bindings & args] - `(pg/with-atomic ~bindings (p/do! ~@args))) + [& args] + `(jdbc/with-transaction ~@args)) -(def row-xfm - (comp (map pg/row->map) - (map data/normalize-attrs))) +(defn- kebab-case [s] (str/replace s #"_" "-")) +(defn- snake-case [s] (str/replace s #"-" "_")) +(defn- as-kebab-maps + [rs opts] + (jdbc-opt/as-unqualified-modified-maps rs (assoc opts :label-fn kebab-case))) -(defmacro query - [conn sql] - `(-> (pg/query ~conn ~sql {:xfm row-xfm}) - (p/catch' (fn [err#] - (ex/raise :type :database-error - :cause err#))))) -(defmacro query-one - [conn sql] - `(-> (pg/query-one ~conn ~sql {:xfm row-xfm}) - (p/catch' (fn [err#] - (ex/raise :type :database-error - :cause err#))))) +(defn open + [] + (jdbc/get-connection pool)) + +(defn exec! + [ds sv] + (jdbc/execute! ds sv {:builder-fn as-kebab-maps})) + +(defn exec-one! + ([ds sv] (exec-one! ds sv {})) + ([ds sv opts] + (jdbc/execute-one! ds sv (assoc opts :builder-fn as-kebab-maps)))) + +(def ^:private default-options + {:table-fn snake-case + :column-fn snake-case + :builder-fn as-kebab-maps}) + +(defn insert! + [ds table params] + (jdbc-sql/insert! ds table params default-options)) + +(defn update! + [ds table params where] + (let [opts (assoc default-options :return-keys true)] + (jdbc-sql/update! ds table params where opts))) + +(defn delete! + [ds table params] + (let [opts (assoc default-options :return-keys true)] + (jdbc-sql/delete! ds table params opts))) + +(defn get-by-params + ([ds table params] + (get-by-params ds table params nil)) + ([ds table params opts] + (let [opts (cond-> (merge default-options opts) + (:for-update opts) + (assoc :suffix "for update")) + res (exec-one! ds (jdbc-bld/for-query table params opts) opts)] + (when (:deleted-at res) + (ex/raise :type :not-found)) + res))) + +(defn get-by-id + ([ds table id] + (get-by-params ds table {:id id} nil)) + ([ds table id opts] + (get-by-params ds table {:id id} opts))) diff --git a/backend/src/uxbox/emails.clj b/backend/src/uxbox/emails.clj index 2efac2f7ed..7926ebb427 100644 --- a/backend/src/uxbox/emails.clj +++ b/backend/src/uxbox/emails.clj @@ -27,8 +27,8 @@ (defn render [email context] - (let [defaults {:from (:email-from cfg/config) - :reply-to (:email-reply-to cfg/config)}] + (let [defaults {:from (:sendmail-from cfg/config) + :reply-to (:sendmail-reply-to cfg/config)}] (email (merge defaults context)))) (defn send! diff --git a/backend/src/uxbox/fixtures.clj b/backend/src/uxbox/fixtures.clj index a0196f6324..2de33e0f4b 100644 --- a/backend/src/uxbox/fixtures.clj +++ b/backend/src/uxbox/fixtures.clj @@ -8,20 +8,17 @@ "A initial fixtures." (:require [clojure.tools.logging :as log] - [sodi.pwhash :as pwhash] [mount.core :as mount] - [promesa.core :as p] - [uxbox.config :as cfg] - [uxbox.common.pages :as cp] + [sodi.pwhash :as pwhash] [uxbox.common.data :as d] - [uxbox.core] + [uxbox.common.pages :as cp] + [uxbox.common.uuid :as uuid] + [uxbox.config :as cfg] [uxbox.db :as db] [uxbox.media :as media] [uxbox.migrations] [uxbox.services.mutations.profile :as mt.profile] - [uxbox.util.blob :as blob] - [uxbox.common.uuid :as uuid] - [vertx.util :as vu])) + [uxbox.util.blob :as blob])) (defn- mk-uuid [prefix & args] @@ -31,52 +28,6 @@ (def password (pwhash/derive "123123")) -(def sql:create-team - "insert into team (id, name, photo) - values ($1, $2, $3) - returning *;") - -(def sql:create-team-profile - "insert into team_profile_rel (team_id, profile_id, is_owner, is_admin, can_edit) - values ($1, $2, $3, $4, $5) - returning *;") - -(def sql:create-project - "insert into project (id, team_id, name) - values ($1, $2, $3) - returning *;") - -(def sql:create-project-profile - "insert into project_profile_rel (project_id, profile_id, is_owner, is_admin, can_edit) - values ($1, $2, $3, $4, $5) - returning *") - -(def sql:create-file-profile - "insert into file_profile_rel (file_id, profile_id, is_owner, is_admin, can_edit) - values ($1, $2, $3, $4, $5) - returning *") - -(def sql:create-file - "insert into file (id, project_id, name) - values ($1, $2, $3 ) returning *") - -(def sql:create-page - "insert into page (id, file_id, name, - version, ordering, data) - values ($1, $2, $3, $4, $5, $6) - returning id;") - -(def sql:create-icon-library - "insert into icon_library (team_id, name) - values ($1, $2) - returning id;") - -(def sql:create-icon - "insert into icon_library (library_id, name, content, metadata) - values ($1, $2, $3, $4) - returning id;") - - (def preset-small {:num-teams 5 :num-profiles 5 @@ -113,13 +64,7 @@ (defn- collect [f items] - (reduce (fn [acc n] - (p/then acc (fn [acc] - (p/then (f n) - (fn [res] - (conj acc res)))))) - (p/promise []) - items)) + (reduce #(conj %1 (f %2)) [] items)) (defn impl-run [opts] @@ -144,13 +89,13 @@ create-team (fn [conn index] - (let [sql sql:create-team - id (mk-uuid "team" index) + (let [id (mk-uuid "team" index) name (str "Team" index)] (log/info "create team" id) - - (-> (db/query-one conn [sql id name ""]) - (p/then (constantly id))))) + (db/insert! conn :team {:id id + :name name + :photo ""}) + id)) create-teams (fn [conn] @@ -160,114 +105,143 @@ create-page (fn [conn owner-id project-id file-id index] - (p/let [id (mk-uuid "page" project-id file-id index) - data cp/default-page-data - name (str "page " index) - version 0 - ordering index - data (blob/encode data)] + (let [id (mk-uuid "page" project-id file-id index) + data cp/default-page-data + name (str "page " index) + version 0 + ordering index + data (blob/encode data)] (log/info "create page" id) - (db/query-one conn [sql:create-page - id file-id name version ordering data]))) + (db/insert! conn :page + {:id id + :file-id file-id + :name name + :version version + :ordering ordering + :data data}))) create-pages (fn [conn owner-id project-id file-id] (log/info "create pages") - (p/run! (partial create-page conn owner-id project-id file-id) - (range (:num-pages-per-file opts)))) + (run! (partial create-page conn owner-id project-id file-id) + (range (:num-pages-per-file opts)))) create-file (fn [conn owner-id project-id index] - (p/let [id (mk-uuid "file" project-id index) - name (str "file" index)] + (let [id (mk-uuid "file" project-id index) + name (str "file" index)] (log/info "create file" id) - (db/query-one conn [sql:create-file id project-id name]) - (db/query-one conn [sql:create-file-profile - id owner-id true true true]) + (db/insert! conn :file + {:id id + :project-id project-id + :name name}) + (db/insert! conn :file-profile-rel + {:file-id id + :profile-id owner-id + :is-owner true + :is-admin true + :can-edit true}) id)) create-files (fn [conn owner-id project-id] (log/info "create files") - (p/let [file-ids (collect (partial create-file conn owner-id project-id) - (range (:num-files-per-project opts)))] - (p/run! (partial create-pages conn owner-id project-id) file-ids))) + (let [file-ids (collect (partial create-file conn owner-id project-id) + (range (:num-files-per-project opts)))] + (run! (partial create-pages conn owner-id project-id) file-ids))) create-project (fn [conn team-id owner-id index] - (p/let [id (mk-uuid "project" team-id index) - name (str "project " index)] + (let [id (mk-uuid "project" team-id index) + name (str "project " index)] (log/info "create project" id) - (db/query-one conn [sql:create-project id team-id name]) - (db/query-one conn [sql:create-project-profile - id owner-id true true true]) + (db/insert! conn :project + {:id id + :team-id team-id + :name name}) + (db/insert! conn :project-profile-rel + {:project-id id + :profile-id owner-id + :is-owner true + :is-admin true + :can-edit true}) id)) create-projects (fn [conn team-id profile-ids] (log/info "create projects") - (p/let [owner-id (rng-nth rng profile-ids) - project-ids (collect (partial create-project conn team-id owner-id) - (range (:num-projects-per-team opts)))] - (p/run! (partial create-files conn owner-id) project-ids))) + (let [owner-id (rng-nth rng profile-ids) + project-ids (collect (partial create-project conn team-id owner-id) + (range (:num-projects-per-team opts)))] + (run! (partial create-files conn owner-id) project-ids))) assign-profile-to-team (fn [conn team-id owner? profile-id] - (let [sql sql:create-team-profile] - (db/query-one conn [sql team-id profile-id owner? true true]))) + (db/insert! conn :team-profile-rel + {:team-id team-id + :profile-id profile-id + :is-owner owner? + :is-admin true + :can-edit true})) setup-team (fn [conn team-id profile-ids] (log/info "setup team" team-id profile-ids) - (p/do! - (assign-profile-to-team conn team-id true (first profile-ids)) - (p/run! (partial assign-profile-to-team conn team-id false) - (rest profile-ids)) - (create-projects conn team-id profile-ids))) + (assign-profile-to-team conn team-id true (first profile-ids)) + (run! (partial assign-profile-to-team conn team-id false) + (rest profile-ids)) + (create-projects conn team-id profile-ids)) assign-teams-and-profiles (fn [conn teams profiles] (log/info "assign teams and profiles") - (vu/loop [team-id (first teams) - teams (rest teams)] + (loop [team-id (first teams) + teams (rest teams)] (when-not (nil? team-id) - (p/let [n-profiles-team (:num-profiles-per-team opts) - selected-profiles (rng-vec rng profiles n-profiles-team)] + (let [n-profiles-team (:num-profiles-per-team opts) + selected-profiles (rng-vec rng profiles n-profiles-team)] (setup-team conn team-id selected-profiles) - (p/recur (first teams) - (rest teams)))))) + (recur (first teams) + (rest teams)))))) create-draft-pages (fn [conn owner-id file-id] (log/info "create draft pages") - (p/run! (partial create-page conn owner-id nil file-id) - (range (:num-draft-pages-per-file opts)))) + (run! (partial create-page conn owner-id nil file-id) + (range (:num-draft-pages-per-file opts)))) create-draft-file (fn [conn owner index] - (p/let [owner-id (:id owner) - id (mk-uuid "file" "draft" owner-id index) - name (str "file" index) - project-id (:id (:default-project owner))] + (let [owner-id (:id owner) + id (mk-uuid "file" "draft" owner-id index) + name (str "file" index) + project-id (:default-project owner)] (log/info "create draft file" id) - (db/query-one conn [sql:create-file id project-id name]) - (db/query-one conn [sql:create-file-profile - id owner-id true true true]) + (db/insert! conn :file + {:id id + :project-id project-id + :name name}) + (db/insert! conn :file-profile-rel + {:file-id id + :profile-id owner-id + :is-owner true + :is-admin true + :can-edit true}) id)) create-draft-files (fn [conn profile] - (p/let [file-ids (collect (partial create-draft-file conn profile) + (let [file-ids (collect (partial create-draft-file conn profile) (range (:num-draft-files-per-profile opts)))] - (p/run! (partial create-draft-pages conn (:id profile)) file-ids))) + (run! (partial create-draft-pages conn (:id profile)) file-ids))) ] (db/with-atomic [conn db/pool] - (p/let [profiles (create-profiles conn) - teams (create-teams conn)] + (let [profiles (create-profiles conn) + teams (create-teams conn)] (assign-teams-and-profiles conn teams (map :id profiles)) - (p/run! (partial create-draft-files conn) profiles))))) + (run! (partial create-draft-files conn) profiles))))) (defn run [preset] @@ -278,17 +252,15 @@ ;; "medium" preset-medium ;; "big" preset-big preset-small))] - (deref (impl-run preset)))) + (impl-run preset))) (defn -main [& args] (try (-> (mount/only #{#'uxbox.config/config - #'uxbox.core/system #'uxbox.db/pool #'uxbox.migrations/migrations}) (mount/start)) - (run (first args)) (finally (mount/stop)))) diff --git a/backend/src/uxbox/http.clj b/backend/src/uxbox/http.clj index a273090371..1b5d6a551c 100644 --- a/backend/src/uxbox/http.clj +++ b/backend/src/uxbox/http.clj @@ -8,70 +8,57 @@ (:require [clojure.tools.logging :as log] [mount.core :as mount :refer [defstate]] - [promesa.core :as p] - [uxbox.core :refer [system]] + [reitit.ring :as rring] + [ring.adapter.jetty9 :as jetty] [uxbox.config :as cfg] + [uxbox.http.debug :as debug] [uxbox.http.errors :as errors] + [uxbox.http.handlers :as handlers] [uxbox.http.middleware :as middleware] [uxbox.http.session :as session] - [uxbox.http.handlers :as handlers] - [uxbox.http.debug :as debug] [uxbox.http.ws :as ws] - [vertx.core :as vc] - [vertx.http :as vh] - [vertx.web :as vw] - [vertx.web.middleware :as vwm])) + [uxbox.services.notifications :as usn])) -(defn- on-start - [ctx] - (let [cors-opts {:origin (:http-server-cors cfg/config "http://localhost:3449") - :max-age 3600 - :allow-credentials true - :allow-methods #{:post :get :patch :head :options :put} - :allow-headers #{:x-requested-with :content-type :cookie}} +(defn- create-router + [] + (rring/router + [["/api" {:middleware [[middleware/format-response-body] + [middleware/errors errors/handle] + [middleware/parse-request-body] + [middleware/params] + [middleware/multipart-params] + [middleware/keyword-params] + [middleware/cookies]]} + ["/echo" {:get handlers/echo-handler + :post handlers/echo-handler}] - routes [["/notifications/:file-id/:session-id" - {:middleware [[vwm/cookies] - [vwm/cors cors-opts] - [middleware/format-response-body] - [session/auth]] - :handler ws/handler - :method :get}] + ["/login" {:handler handlers/login-handler + :method :post}] + ["/logout" {:handler handlers/logout-handler + :method :post}] - ["/api" {:middleware [[vwm/cookies] - [vwm/params] - [vwm/cors cors-opts] - [middleware/parse-request-body] - [middleware/format-response-body] - [middleware/method-match] - [vwm/errors errors/handle]]} - ["/echo" {:handler handlers/echo-handler}] - ["/login" {:handler handlers/login-handler - :method :post}] - ["/logout" {:handler handlers/logout-handler - :method :post}] - ["/w" {:middleware [session/auth]} - ["/mutation/:type" {:middleware [vwm/uploads] - :handler handlers/mutation-handler - :method :post}] - ["/query/:type" {:handler handlers/query-handler - :method :get}]]]] + ["/w" {:middleware [session/auth]} + ["/query/:type" {:get handlers/query-handler}] + ["/mutation/:type" {:post handlers/mutation-handler}]]]])) - handler (vw/handler ctx - (vw/assets "/media/*" {:root "resources/public/media"}) - (vw/assets "/static/*" {:root "resources/public/static"}) - (vw/router routes))] +(defstate app + :start (rring/ring-handler + (create-router) + (constantly {:status 404, :body ""}) + {:middleware [middleware/development-resources + middleware/development-cors]})) - (log/info "Starting http server on" (:http-server-port cfg/config) "port.") - (vh/server ctx {:handler handler - :port (:http-server-port cfg/config)}))) - -(def num-cpus - (delay (.availableProcessors (Runtime/getRuntime)))) +(defn start-server + [cfg app] + (let [wsockets {"/ws/notifications" ws/handler} + options {:port (:http-server-port cfg) + :h2c? true + :join? false + :allow-null-path-info true + :websockets wsockets}] + (jetty/run-jetty app options))) (defstate server - :start (let [vf (vc/verticle {:on-start on-start})] - @(vc/deploy! system vf {:instances @num-cpus}))) - - + :start (start-server cfg/config app) + :stop (.stop server)) diff --git a/backend/src/uxbox/http/handlers.clj b/backend/src/uxbox/http/handlers.clj index d620c8fbb1..225eb46d49 100644 --- a/backend/src/uxbox/http/handlers.clj +++ b/backend/src/uxbox/http/handlers.clj @@ -6,16 +6,13 @@ (ns uxbox.http.handlers (:require - [promesa.core :as p] [uxbox.common.exceptions :as ex] + [uxbox.common.uuid :as uuid] [uxbox.emails :as emails] [uxbox.http.session :as session] [uxbox.services.init] [uxbox.services.mutations :as sm] - [uxbox.services.queries :as sq] - [uxbox.common.uuid :as uuid] - [vertx.web :as vw] - [vertx.eventbus :as ve])) + [uxbox.services.queries :as sq])) (def unauthorized-services #{:create-demo-profile @@ -36,10 +33,8 @@ (:profile-id req) (assoc :profile-id (:profile-id req)))] (if (or (:profile-id req) (contains? unauthorized-services type)) - (-> (sq/handle (with-meta data {:req req})) - (p/then' (fn [result] - {:status 200 - :body result}))) + {:status 200 + :body (sq/handle (with-meta data {:req req}))} {:status 403 :body {:type :authentication :code :unauthorized}}))) @@ -55,9 +50,8 @@ (:profile-id req) (assoc :profile-id (:profile-id req)))] (if (or (:profile-id req) (contains? unauthorized-services type)) - (-> (sm/handle (with-meta data {:req req})) - (p/then' (fn [result] - {:status 200 :body result}))) + {:status 200 + :body (sm/handle (with-meta data {:req req}))} {:status 403 :body {:type :authentication :code :unauthorized}}))) @@ -66,8 +60,8 @@ [req] (let [data (:body-params req) user-agent (get-in req [:headers "user-agent"])] - (p/let [profile (sm/handle (assoc data ::sm/type :login)) - token (session/create (:id profile) user-agent)] + (let [profile (sm/handle (assoc data ::sm/type :login)) + token (session/create (:id profile) user-agent)] {:status 200 :cookies {"auth-token" {:value token :path "/"}} :body profile}))) @@ -76,16 +70,15 @@ [req] (some-> (get-in req [:cookies "auth-token"]) (uuid/uuid) - (session/delete) - (p/then' (fn [token] - {:status 204 - :cookies {"auth-token" nil} - :body ""})))) + (session/delete)) + {:status 204 + :cookies {"auth-token" nil} + :body ""}) (defn echo-handler [req] - (p/promise {:status 200 - :body {:params (:params req) - :cookies (:cookies req) - :headers (:headers req)}})) + {:status 200 + :body {:params (:params req) + :cookies (:cookies req) + :headers (:headers req)}}) diff --git a/backend/src/uxbox/http/middleware.clj b/backend/src/uxbox/http/middleware.clj index 9e57283fde..e16a6da143 100644 --- a/backend/src/uxbox/http/middleware.clj +++ b/backend/src/uxbox/http/middleware.clj @@ -9,30 +9,32 @@ (ns uxbox.http.middleware (:require - [promesa.core :as p] - [vertx.web :as vw] - [uxbox.config :as cfg] + [clojure.tools.logging :as log] + [ring.middleware.cookies :refer [wrap-cookies]] + [ring.middleware.keyword-params :refer [wrap-keyword-params]] + [ring.middleware.multipart-params :refer [wrap-multipart-params]] + [ring.middleware.params :refer [wrap-params]] + [ring.middleware.resource :refer [wrap-resource]] [uxbox.common.exceptions :as ex] - [uxbox.util.transit :as t]) - (:import - io.vertx.ext.web.RoutingContext - io.vertx.ext.web.FileUpload - io.vertx.core.buffer.Buffer)) + [uxbox.config :as cfg] + [uxbox.util.transit :as t])) (defn- wrap-parse-request-body [handler] - (fn [{:keys [headers body method] :as request}] - (let [mtype (get headers "content-type")] - (if (and (= "application/transit+json" mtype) - (not= method :get)) - (try - (let [params (t/decode (t/buffer->bytes body))] - (handler (assoc request :body-params params))) - (catch Exception e - (ex/raise :type :parse - :message "Unable to parse transit from request body." - :cause e))) - (handler request))))) + (letfn [(parse-body [body] + (try + (let [reader (t/reader body)] + (t/read! reader)) + (catch Exception e + (ex/raise :type :parse + :message "Unable to parse transit from request body." + :cause e))))] + (fn [{:keys [headers body request-method] :as request}] + (handler + (cond-> request + (and (= "application/transit+json" (get headers "content-type")) + (not= request-method :get)) + (assoc :body-params (parse-body body))))))) (def parse-request-body {:name ::parse-request-body @@ -47,7 +49,7 @@ (cond (coll? body) (-> response - (assoc :body (t/bytes->buffer (t/encode body {:type type}))) + (assoc :body (t/encode body {:type type})) (update :headers assoc "content-type" "application/transit+json")) @@ -61,26 +63,69 @@ (defn- wrap-format-response-body [handler] (fn [request] - (-> (p/do! (handler request)) - (p/then' (fn [response] - (cond-> response - (map? response) (impl-format-response-body))))))) + (let [response (handler request)] + (cond-> response + (map? response) (impl-format-response-body))))) (def format-response-body {:name ::format-response-body :compile (constantly wrap-format-response-body)}) +(defn- wrap-errors + [handler on-error] + (fn [request] + (try + (handler request) + (catch Throwable e + (on-error e request))))) -(defn- wrap-method-match +(def errors + {:name ::errors + :compile (constantly wrap-errors)}) + +(def cookies + {:name ::cookies + :compile (constantly wrap-cookies)}) + +(def params + {:name ::params + :compile (constantly wrap-params)}) + +(def multipart-params + {:name ::multipart-params + :compile (constantly wrap-multipart-params)}) + +(def keyword-params + {:name ::keyword-params + :compile (constantly wrap-keyword-params)}) + +(defn- wrap-development-cors [handler] - (fn [request])) + (letfn [(add-cors-headers [response] + (update response :headers + (fn [headers] + (-> headers + (assoc "access-control-allow-origin" "http://localhost:3449") + (assoc "access-control-allow-methods" "GET,POST,DELETE,OPTIONS,PUT,HEAD,PATCH") + (assoc "access-control-allow-credentials" "true") + (assoc "access-control-expose-headers" "x-requested-with, content-type, cookie") + (assoc "access-control-allow-headers" "content-type")))))] + (fn [request] + (if (= (:request-method request) :options) + (-> {:status 200 :body ""} + (add-cors-headers)) + (let [response (handler request)] + (add-cors-headers response)))))) + +(def development-cors + {:name ::development-cors + :compile (fn [& args] + (when *assert* + wrap-development-cors))}) + +(def development-resources + {:name ::development-resources + :compile (fn [& args] + (when *assert* + #(wrap-resource % "public")))}) -(def method-match - {:name ::method-match - :compile (fn [data opts] - (when-let [method (:method data)] - (fn [handler] - (fn [request] - (if (= (:method request) method) - (handler request) - {:status 405 :body ""})))))}) diff --git a/backend/src/uxbox/http/session.clj b/backend/src/uxbox/http/session.clj index 0f48e8c347..35235b982b 100644 --- a/backend/src/uxbox/http/session.clj +++ b/backend/src/uxbox/http/session.clj @@ -6,8 +6,6 @@ (ns uxbox.http.session (:require - [promesa.core :as p] - [vertx.core :as vc] [uxbox.db :as db] [uxbox.common.uuid :as uuid])) @@ -17,22 +15,21 @@ "Retrieves a user id associated with the provided auth token." [token] (when token - (let [sql "select profile_id from session where id = $1"] - (-> (db/query-one db/pool [sql token]) - (p/then' (fn [row] (when row (:profile-id row)))))))) + (let [row (db/get-by-params db/pool :session {:id token})] + (:profile-id row)))) (defn create [user-id user-agent] - (let [id (uuid/random) - sql "insert into session (id, profile_id, user_agent) values ($1, $2, $3)"] - (-> (db/query-one db/pool [sql id user-id user-agent]) - (p/then (constantly (str id)))))) + (let [id (uuid/random)] + (db/insert! db/pool :session {:id id + :profile-id user-id + :user-agent user-agent}) + (str id))) (defn delete [token] - (let [sql "delete from session where id = $1"] - (-> (db/query-one db/pool [sql token]) - (p/then' (constantly nil))))) + (db/delete! db/pool :session {:id token}) + nil) ;; --- Interceptor @@ -40,19 +37,18 @@ [request] (try (when-let [token (get-in request [:cookies "auth-token"])] - (uuid/uuid token)) + (uuid/uuid (:value token))) (catch java.lang.IllegalArgumentException e nil))) -(defn- wrap-auth +(defn wrap-auth [handler] (fn [request] - (let [token (parse-token request)] - (-> (p/do! (retrieve token)) - (p/then (fn [profile-id] - (if profile-id - (handler (assoc request :profile-id profile-id)) - (handler request)))))))) + (let [token (parse-token request) + profile-id (retrieve token)] + (if profile-id + (handler (assoc request :profile-id profile-id)) + (handler request))))) (def auth {:nane ::auth diff --git a/backend/src/uxbox/http/ws.clj b/backend/src/uxbox/http/ws.clj index 080565a22a..aec9c1b955 100644 --- a/backend/src/uxbox/http/ws.clj +++ b/backend/src/uxbox/http/ws.clj @@ -7,12 +7,37 @@ (ns uxbox.http.ws "Web Socket handlers" (:require - [uxbox.services.notifications :as nf] - [vertx.web.websockets :as ws])) + [clojure.core.async :as a] + [ring.middleware.cookies :refer [wrap-cookies]] + [ring.middleware.keyword-params :refer [wrap-keyword-params]] + [ring.middleware.params :refer [wrap-params]] + [uxbox.http.session :refer [wrap-auth]] + [clojure.tools.logging :as log] + [clojure.spec.alpha :as s] + [promesa.core :as p] + [ring.adapter.jetty9 :as jetty] + [uxbox.common.exceptions :as ex] + [uxbox.common.uuid :as uuid] + [uxbox.common.spec :as us] + [uxbox.redis :as redis] + [ring.util.codec :as codec] + [uxbox.util.transit :as t] + [uxbox.services.notifications :as nf])) -(defn handler - [{:keys [user] :as req}] - (ws/websocket - {:handler #(nf/websocket req %) - :input-buffer-size 64 - :output-buffer-size 64})) +(s/def ::file-id ::us/uuid) +(s/def ::session-id ::us/uuid) +(s/def ::websocket-params + (s/keys :req-un [::file-id ::session-id])) + +(defn websocket + [req] + (let [params (us/conform ::websocket-params (:params req)) + params (assoc params :profile-id (:profile-id req))] + (nf/websocket params))) + +(def handler + (-> websocket + (wrap-auth) + (wrap-keyword-params) + (wrap-cookies) + (wrap-params))) diff --git a/backend/src/uxbox/media_loader.clj b/backend/src/uxbox/media_loader.clj index feb9d06c2c..7c8b522fdb 100644 --- a/backend/src/uxbox/media_loader.clj +++ b/backend/src/uxbox/media_loader.clj @@ -15,7 +15,6 @@ [clojure.pprint :refer [pprint]] [clojure.java.io :as io] [clojure.edn :as edn] - [promesa.core :as p] [mount.core :as mount] [datoteka.core :as fs] [cuerdas.core :as str] @@ -63,7 +62,6 @@ ([code] (System/exit code))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Icons Libraries Importer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -71,10 +69,8 @@ (defn- icon-library-exists? [conn id] (s/assert ::us/uuid id) - (let [sql "select id from icon_library where id = $1"] - (-> (db/query-one conn [sql id]) - (p/then (fn [row] (if row true false)))))) - + (let [row (db/get-by-id conn :icon-library id)] + (if row true false))) (defn- create-icons-library [conn {:keys [name] :as item}] @@ -87,11 +83,9 @@ (defn- create-icons-library-if-not-exists [conn {:keys [name] :as item}] (let [id (uuid/namespaced +icons-uuid-ns+ name)] - (-> (icon-library-exists? conn id) - (p/then (fn [exists?] - (when-not exists? - (create-icons-library conn item)))) - (p/then (constantly id))))) + (when-not (icon-library-exists? conn id) + (create-icons-library conn item)) + id)) (defn- create-icon [conn library-id icon-id localpath] @@ -113,36 +107,32 @@ (defn- icon-exists? [conn id] (s/assert ::us/uuid id) - (let [sql "select id from icon where id = $1"] - (-> (db/query-one conn [sql id]) - (p/then (fn [row] (if row true false)))))) + (let [row (db/get-by-id conn :icon id)] + (if row true false))) (defn- import-icon-if-not-exists [conn library-id fpath] (s/assert ::us/uuid library-id) (s/assert fs/path? fpath) (let [icon-id (uuid/namespaced +icons-uuid-ns+ (str library-id (fs/name fpath)))] - (-> (icon-exists? conn icon-id) - (p/then (fn [exists?] - (when-not exists? - (create-icon conn library-id icon-id fpath)))) - (p/then (constantly icon-id))))) + (when-not (icon-exists? conn icon-id) + (create-icon conn library-id icon-id fpath)) + icon-id)) (defn- import-icons [conn library-id {:keys [path regex] :as item}] - (p/run! (fn [fpath] - (when (re-matches regex (str fpath)) - (import-icon-if-not-exists conn library-id fpath))) - (->> (fs/list-dir path) - (filter fs/regular-file?)))) + (run! (fn [fpath] + (when (re-matches regex (str fpath)) + (import-icon-if-not-exists conn library-id fpath))) + (->> (fs/list-dir path) + (filter fs/regular-file?)))) (defn- process-icons-library [conn basedir {:keys [path regex] :as item}] (s/assert ::import-item-media item) - (-> (create-icons-library-if-not-exists conn item) - (p/then (fn [library-id] - (->> (assoc item :path (fs/join basedir path)) - (import-icons conn library-id)))))) + (let [library-id (create-icons-library-if-not-exists conn item)] + (->> (assoc item :path (fs/join basedir path)) + (import-icons conn library-id)))) ;; --- Images Libraries Importer @@ -150,9 +140,8 @@ (defn- image-library-exists? [conn id] (s/assert ::us/uuid id) - (let [sql "select id from image_library where id = $1"] - (-> (db/query-one conn [sql id]) - (p/then (fn [row] (if row true false)))))) + (let [row (db/get-by-id conn :image-library id)] + (if row true false))) (defn- create-images-library [conn {:keys [name] :as item}] @@ -162,16 +151,12 @@ :team-id uuid/zero :name name}))) - (defn- create-images-library-if-not-exists [conn {:keys [name] :as item}] (let [id (uuid/namespaced +images-uuid-ns+ name)] - (-> (image-library-exists? conn id) - (p/then (fn [exists?] - (when-not exists? - (create-images-library conn item)))) - (p/then (constantly id))))) - + (when-not (image-library-exists? conn id) + (create-images-library conn item) + id))) (defn- create-image [conn library-id image-id localpath] @@ -186,9 +171,9 @@ ".png" "image/png" ".webp" "image/webp")] (log/info "Creating image" filename image-id) - (images/create-image conn {:content {:path localpath - :name filename - :mtype mtype + (images/create-image conn {:content {:tempfile localpath + :filename filename + :content-type mtype :size (.length file)} :id image-id :library-id library-id @@ -198,36 +183,32 @@ (defn- image-exists? [conn id] (s/assert ::us/uuid id) - (let [sql "select id from image where id = $1"] - (-> (db/query-one conn [sql id]) - (p/then (fn [row] (if row true false)))))) + (let [row (db/get-by-id conn :image id)] + (if row true false))) (defn- import-image-if-not-exists [conn library-id fpath] (s/assert ::us/uuid library-id) (s/assert fs/path? fpath) (let [image-id (uuid/namespaced +images-uuid-ns+ (str library-id (fs/name fpath)))] - (-> (image-exists? conn image-id) - (p/then (fn [exists?] - (when-not exists? - (create-image conn library-id image-id fpath)))) - (p/then (constantly image-id))))) + (when-not (image-exists? conn image-id) + (create-image conn library-id image-id fpath)) + image-id)) (defn- import-images [conn library-id {:keys [path regex] :as item}] - (p/run! (fn [fpath] - (when (re-matches regex (str fpath)) - (import-image-if-not-exists conn library-id fpath))) - (->> (fs/list-dir path) - (filter fs/regular-file?)))) + (run! (fn [fpath] + (when (re-matches regex (str fpath)) + (import-image-if-not-exists conn library-id fpath))) + (->> (fs/list-dir path) + (filter fs/regular-file?)))) (defn- process-images-library [conn basedir {:keys [path regex] :as item}] (s/assert ::import-item-media item) - (-> (create-images-library-if-not-exists conn item) - (p/then (fn [library-id] - (->> (assoc item :path (fs/join basedir path)) - (import-images conn library-id)))))) + (let [library-id (create-images-library-if-not-exists conn item)] + (->> (assoc item :path (fs/join basedir path)) + (import-images conn library-id)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -237,9 +218,8 @@ (defn- color-library-exists? [conn id] (s/assert ::us/uuid id) - (let [sql "select id from color_library where id = $1"] - (-> (db/query-one conn [sql id]) - (p/then (fn [row] (if row true false)))))) + (let [row (db/get-by-id conn :color-library id)] + (if row true false))) (defn- create-colors-library [conn {:keys [name] :as item}] @@ -253,43 +233,33 @@ (defn- create-colors-library-if-not-exists [conn {:keys [name] :as item}] (let [id (uuid/namespaced +colors-uuid-ns+ name)] - (-> (color-library-exists? conn id) - (p/then (fn [exists?] - (when-not exists? - (create-colors-library conn item)))) - (p/then (constantly id))))) + (when-not (color-library-exists? conn id) + (create-colors-library conn item)) + id)) (defn- create-color [conn library-id content] (s/assert ::us/uuid library-id) (s/assert ::us/color content) - (let [color-id (uuid/namespaced +colors-uuid-ns+ (str library-id content))] (log/info "Creating color" content color-id) - (-> (colors/create-color conn {:id color-id - :library-id library-id - :name content - :content content}) - (p/then' (constantly color-id))))) - -(defn- prune-colors - [conn library-id] - (-> (db/query-one conn ["delete from color where library_id=$1" library-id]) - (p/then (constantly nil)))) + (colors/create-color conn {:id color-id + :library-id library-id + :name content + :content content}) + color-id)) (defn- import-colors [conn library-id {:keys [colors] :as item}] (us/verify ::import-item-color item) - (p/do! - (prune-colors conn library-id) - (p/run! #(create-color conn library-id %) colors))) + (db/delete! conn :color {:library-id library-id}) + (run! #(create-color conn library-id %) colors)) (defn- process-colors-library [conn {:keys [name id colors] :as item}] (us/verify ::import-item-color item) - (-> (create-colors-library-if-not-exists conn item) - (p/then #(import-colors conn % item)))) - + (let [library-id (create-colors-library-if-not-exists conn item)] + (import-colors conn library-id item))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Entry Point @@ -328,22 +298,22 @@ (let [images (:images data) icons (:icons data) colors (:colors data)] - (p/do! - (p/run! #(process-images-library conn basedir %) images) - (p/run! #(process-icons-library conn basedir %) icons) - (p/run! #(process-colors-library conn %) colors) - nil))) + (run! #(process-images-library conn basedir %) images) + (run! #(process-icons-library conn basedir %) icons) + (run! #(process-colors-library conn %) colors))) (defn run [path] - (p/let [[basedir data] (read-file path)] + (let [[basedir data] (read-file path)] (db/with-atomic [conn db/pool] (importer conn basedir data)))) (defn -main [& [path]] (let [path (validate-path path)] - (start-system) - (-> (run path) - (p/finally (fn [_ _] (stop-system)))))) + (try + (start-system) + (run path) + (finally + (stop-system))))) diff --git a/backend/src/uxbox/migrations.clj b/backend/src/uxbox/migrations.clj index d4f4d5bdb5..d5ad700497 100644 --- a/backend/src/uxbox/migrations.clj +++ b/backend/src/uxbox/migrations.clj @@ -37,8 +37,9 @@ (defn migrate [] - (with-open [ctx (mg/context db/pool)] - @(mg/migrate ctx +migrations+))) + (with-open [conn (db/open)] + (mg/setup! conn) + (mg/migrate! conn +migrations+))) (defstate migrations :start (migrate)) diff --git a/backend/src/uxbox/redis.clj b/backend/src/uxbox/redis.clj index 0a7d160df9..0d18b7b3b5 100644 --- a/backend/src/uxbox/redis.clj +++ b/backend/src/uxbox/redis.clj @@ -10,13 +10,10 @@ [clojure.tools.logging :as log] [lambdaisland.uri :refer [uri]] [mount.core :as mount :refer [defstate]] - [promesa.core :as p] [uxbox.common.exceptions :as ex] [uxbox.config :as cfg] - [uxbox.core :refer [system]] - [uxbox.util.redis :as redis] [uxbox.util.data :as data] - [vertx.util :as vu]) + [uxbox.util.redis :as redis]) (:import java.lang.AutoCloseable)) @@ -33,20 +30,20 @@ :stop (.close ^AutoCloseable client)) (defstate conn - :start @(redis/connect client) + :start (redis/connect client) :stop (.close ^AutoCloseable conn)) ;; --- API FORWARD (defn subscribe - [topic] - (redis/subscribe client topic)) + ([topic] + (redis/subscribe client topic)) + ([topic xf] + (redis/subscribe client topic xf))) (defn run! [cmd params] - (let [ctx (vu/get-or-create-context system)] - (-> (redis/run! conn cmd params) - (vu/handle-on-context ctx)))) + (redis/run! conn cmd params)) (defn run [cmd params] diff --git a/backend/src/uxbox/services/mutations/colors.clj b/backend/src/uxbox/services/mutations/colors.clj index 1da36b1e25..394ecd5251 100644 --- a/backend/src/uxbox/services/mutations/colors.clj +++ b/backend/src/uxbox/services/mutations/colors.clj @@ -10,15 +10,15 @@ (ns uxbox.services.mutations.colors (:require [clojure.spec.alpha :as s] - [promesa.core :as p] + [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] + [uxbox.common.uuid :as uuid] [uxbox.config :as cfg] [uxbox.db :as db] - [uxbox.tasks :as tasks] - [uxbox.services.queries.teams :as teams] [uxbox.services.mutations :as sm] - [uxbox.services.util :as su] - [uxbox.common.uuid :as uuid])) + [uxbox.services.queries.teams :as teams] + [uxbox.tasks :as tasks] + [uxbox.util.time :as dt])) ;; --- Helpers & Specs @@ -44,15 +44,13 @@ (teams/check-edition-permissions! conn profile-id team-id) (create-library conn params))) -(def ^:private sql:create-library - "insert into color_library (id, team_id, name) - values ($1, $2, $3) - returning *;") - (defn create-library [conn {:keys [id team-id name]}] (let [id (or id (uuid/next))] - (db/query-one conn [sql:create-library id team-id name]))) + (db/insert! conn :color-library + {:id id + :team-id team-id + :name name}))) ;; --- Mutation: Rename Library @@ -66,7 +64,7 @@ (sm/defmutation ::rename-color-library [{:keys [id profile-id name] :as params}] (db/with-atomic [conn db/pool] - (p/let [lib (select-library-for-update conn id)] + (let [lib (select-library-for-update conn id)] (teams/check-edition-permissions! conn profile-id (:team-id lib)) (rename-library conn id name)))) @@ -83,42 +81,13 @@ (defn- select-library-for-update [conn id] - (-> (db/query-one conn [sql:select-library-for-update id]) - (p/then' su/raise-not-found-if-nil))) + (db/get-by-id conn :color-library id {:for-update true})) (defn- rename-library [conn id name] - (-> (db/query-one conn [sql:rename-library id name]) - (p/then' su/constantly-nil))) - - - -;; --- Copy Color - -;; (declare create-color) - -;; (defn- retrieve-color -;; [conn {:keys [profile-id id]}] -;; (let [sql "select * from color -;; where id = $1 -;; and deleted_at is null -;; and (profile_id = $2 or -;; profile_id = '00000000-0000-0000-0000-000000000000'::uuid)"] -;; (-> (db/query-one conn [sql id profile-id]) -;; (p/then' su/raise-not-found-if-nil)))) - -;; (s/def ::copy-color -;; (s/keys :req-un [:us/id ::library-id ::profile-id])) - -;; (sm/defmutation ::copy-color -;; [{:keys [profile-id id library-id] :as params}] -;; (db/with-atomic [conn db/pool] -;; (-> (retrieve-color conn {:profile-id profile-id :id id}) -;; (p/then (fn [color] -;; (let [color (-> (dissoc color :id) -;; (assoc :library-id library-id))] -;; (create-color conn color))))))) - + (db/update! conn :color-library + {:name name} + {:id id})) ;; --- Delete Library @@ -131,7 +100,7 @@ (sm/defmutation ::delete-color-library [{:keys [id profile-id] :as params}] (db/with-atomic [conn db/pool] - (p/let [lib (select-library-for-update conn id)] + (let [lib (select-library-for-update conn id)] (teams/check-edition-permissions! conn profile-id (:team-id lib)) ;; Schedule object deletion @@ -139,18 +108,10 @@ :delay cfg/default-deletion-delay :props {:id id :type :color-library}}) - (delete-library conn id)))) - -(def ^:private sql:mark-library-deleted - "update color_library - set deleted_at = clock_timestamp() - where id = $1") - -(defn- delete-library - [conn id] - (-> (db/query-one conn [sql:mark-library-deleted id]) - (p/then' su/constantly-nil))) - + (db/update! conn :color-library + {:deleted-at (dt/now)} + {:id id}) + nil))) ;; --- Mutation: Create Color (Upload) @@ -164,7 +125,7 @@ (sm/defmutation ::create-color [{:keys [profile-id library-id] :as params}] (db/with-atomic [conn db/pool] - (p/let [lib (select-library-for-update conn library-id)] + (let [lib (select-library-for-update conn library-id)] (teams/check-edition-permissions! conn profile-id (:team-id lib)) (create-color conn params)))) @@ -175,14 +136,15 @@ (defn create-color [conn {:keys [id name library-id content]}] (let [id (or id (uuid/next))] - (db/query-one conn [sql:create-color id name library-id content]))) - + (db/insert! conn :color {:id id + :name name + :library-id library-id + :content content}))) ;; --- Mutation: Rename Color (declare select-color-for-update) -(declare rename-color) (s/def ::rename-color (s/keys :req-un [::id ::profile-id ::name])) @@ -190,33 +152,26 @@ (sm/defmutation ::rename-color [{:keys [id profile-id name] :as params}] (db/with-atomic [conn db/pool] - (p/let [clr (select-color-for-update conn id)] + (let [clr (select-color-for-update conn id)] (teams/check-edition-permissions! conn profile-id (:team-id clr)) - (rename-color conn id name)))) + (db/update! conn :color + {:name name} + {:id id})))) (def ^:private sql:select-color-for-update "select c.*, lib.team_id as team_id from color as c inner join color_library as lib on (lib.id = c.library_id) - where c.id = $1 + where c.id = ? for update of c") -(def ^:private sql:rename-color - "update color - set name = $2 - where id = $1") - (defn- select-color-for-update [conn id] - (-> (db/query-one conn [sql:select-color-for-update id]) - (p/then' su/raise-not-found-if-nil))) - -(defn- rename-color - [conn id name] - (-> (db/query-one conn [sql:rename-color id name]) - (p/then' su/constantly-nil))) - + (let [row (db/exec-one! conn [sql:select-color-for-update id])] + (when-not row + (ex/raise :type :not-found)) + row)) ;; --- Delete Color @@ -229,7 +184,7 @@ (sm/defmutation ::delete-color [{:keys [profile-id id] :as params}] (db/with-atomic [conn db/pool] - (p/let [clr (select-color-for-update conn id)] + (let [clr (select-color-for-update conn id)] (teams/check-edition-permissions! conn profile-id (:team-id clr)) ;; Schedule object deletion @@ -237,14 +192,7 @@ :delay cfg/default-deletion-delay :props {:id id :type :color}}) - (delete-color conn id)))) - -(def ^:private sql:mark-color-deleted - "update color - set deleted_at = clock_timestamp() - where id = $1") - -(defn- delete-color - [conn id] - (-> (db/query-one conn [sql:mark-color-deleted id]) - (p/then' su/constantly-nil))) + (db/update! conn :color + {:deleted-at (dt/now)} + {:id id}) + nil))) diff --git a/backend/src/uxbox/services/mutations/files.clj b/backend/src/uxbox/services/mutations/files.clj index 21c162badb..4dffcb94dd 100644 --- a/backend/src/uxbox/services/mutations/files.clj +++ b/backend/src/uxbox/services/mutations/files.clj @@ -10,25 +10,24 @@ (ns uxbox.services.mutations.files (:require [clojure.spec.alpha :as s] - [promesa.core :as p] [datoteka.core :as fs] + [promesa.core :as p] + [uxbox.common.exceptions :as ex] + [uxbox.common.pages :as cp] + [uxbox.common.spec :as us] + [uxbox.common.uuid :as uuid] [uxbox.config :as cfg] [uxbox.db :as db] - [uxbox.media :as media] [uxbox.images :as images] - [uxbox.common.exceptions :as ex] - [uxbox.common.spec :as us] - [uxbox.common.pages :as cp] - [uxbox.tasks :as tasks] - [uxbox.services.queries.files :as files] + [uxbox.media :as media] [uxbox.services.mutations :as sm] - [uxbox.services.mutations.projects :as proj] [uxbox.services.mutations.images :as imgs] - [uxbox.services.util :as su] + [uxbox.services.mutations.projects :as proj] + [uxbox.services.queries.files :as files] + [uxbox.tasks :as tasks] [uxbox.util.blob :as blob] - [uxbox.common.uuid :as uuid] [uxbox.util.storage :as ust] - [vertx.util :as vu])) + [uxbox.util.time :as dt])) ;; --- Helpers & Specs @@ -49,41 +48,36 @@ (sm/defmutation ::create-file [{:keys [profile-id project-id] :as params}] (db/with-atomic [conn db/pool] - (p/let [file (create-file conn params) - page (create-page conn (assoc params :file-id (:id file)))] + (let [file (create-file conn params) + page (create-page conn (assoc params :file-id (:id file)))] (assoc file :pages [(:id page)])))) -(def ^:private sql:create-file - "insert into file (id, project_id, name) - values ($1, $2, $3) returning *") - -(def ^:private sql:create-file-profile - "insert into file_profile_rel (profile_id, file_id, is_owner, is_admin, can_edit) - values ($1, $2, true, true, true) returning *") - -(def ^:private sql:create-page - "insert into page (id, file_id, name, ordering, data) - values ($1, $2, $3, $4, $5) returning id") - (defn- create-file-profile [conn {:keys [profile-id file-id] :as params}] - (db/query-one conn [sql:create-file-profile profile-id file-id])) + (db/insert! conn :file-profile-rel + {:profile-id profile-id + :file-id file-id + :is-owner true + :is-admin true + :can-edit true})) (defn- create-file [conn {:keys [id profile-id name project-id] :as params}] - (p/let [id (or id (uuid/next)) - file (db/query-one conn [sql:create-file id project-id name])] + (let [id (or id (uuid/next)) + file (db/insert! conn :file {:id id :project-id project-id :name name})] (->> (assoc params :file-id id) (create-file-profile conn)) file)) (defn- create-page [conn {:keys [file-id] :as params}] - (let [id (uuid/next) - name "Page 1" - data (blob/encode cp/default-page-data)] - (db/query-one conn [sql:create-page id file-id name 1 data]))) - + (let [id (uuid/next)] + (db/insert! conn :page + {:id id + :file-id file-id + :name "Page 1" + :ordering 1 + :data (blob/encode cp/default-page-data)}))) ;; --- Mutation: Rename File @@ -99,16 +93,11 @@ (files/check-edition-permissions! conn profile-id id) (rename-file conn params))) -(def ^:private sql:rename-file - "update file - set name = $2 - where id = $1 - and deleted_at is null - returning *") - (defn- rename-file [conn {:keys [id name] :as params}] - (db/query-one conn [sql:rename-file id name])) + (db/update! conn :file + {:name name} + {:id id})) ;; --- Mutation: Delete Project File @@ -133,13 +122,15 @@ (def ^:private sql:mark-file-deleted "update file set deleted_at = clock_timestamp() - where id = $1 + where id = ? and deleted_at is null") (defn mark-file-deleted [conn {:keys [id] :as params}] - (-> (db/query-one conn [sql:mark-file-deleted id]) - (p/then' su/constantly-nil))) + (db/update! conn :file + {:deleted-at (dt/now)} + {:id id}) + nil) ;; --- Mutation: Upload File Image @@ -169,31 +160,29 @@ (defn- create-file-image [conn {:keys [content file-id name] :as params}] - (when-not (imgs/valid-image-types? (:mtype content)) + (when-not (imgs/valid-image-types? (:content-type content)) (ex/raise :type :validation :code :image-type-not-allowed :hint "Seems like you are uploading an invalid image.")) - (p/let [image-opts (vu/blocking (images/info (:path content))) - image-path (imgs/persist-image-on-fs content) - thumb-opts imgs/thumbnail-options - thumb-path (imgs/persist-image-thumbnail-on-fs thumb-opts image-path) - - sqlv [sql:insert-file-image - file-id - name - (str image-path) - (:width image-opts) - (:height image-opts) - (:mtype content) - (str thumb-path) - (:width thumb-opts) - (:height thumb-opts) - (:quality thumb-opts) - (images/format->mtype (:format thumb-opts))]] - (-> (db/query-one db/pool sqlv) - (p/then' #(images/resolve-urls % :path :uri)) - (p/then' #(images/resolve-urls % :thumb-path :thumb-uri))))) + (let [image-opts (images/info (:tempfile content)) + image-path (imgs/persist-image-on-fs content) + thumb-opts imgs/thumbnail-options + thumb-path (imgs/persist-image-thumbnail-on-fs thumb-opts image-path)] + (-> (db/insert! conn :file-image + {:file-id file-id + :name name + :path (str image-path) + :width (:width image-opts) + :height (:height image-opts) + :mtype (:content-type content) + :thumb-path (str thumb-path) + :thumb-width (:width thumb-opts) + :thumb-height (:height thumb-opts) + :thumb-quality (:quality thumb-opts) + :thumb-mtype (images/format->mtype (:format thumb-opts))}) + (images/resolve-urls :path :uri) + (images/resolve-urls :thumb-path :thumb-uri)))) ;; --- Mutation: Import from collection @@ -215,28 +204,26 @@ (defn- import-image-to-file [conn {:keys [image-id file-id] :as params}] - (p/let [image (-> (db/query-one conn [sql:select-image-by-id image-id]) - (p/then' su/raise-not-found-if-nil)) - image-path (copy-image (:path image)) - thumb-path (copy-image (:thumb-path image)) - sqlv [sql:insert-file-image - file-id - (:name image) - (str image-path) - (:width image) - (:height image) - (:mtype image) - (str thumb-path) - (:thumb-width image) - (:thumb-height image) - (:thumb-quality image) - (:thumb-mtype image)]] - (-> (db/query-one db/pool sqlv) - (p/then' #(images/resolve-urls % :path :uri)) - (p/then' #(images/resolve-urls % :thumb-path :thumb-uri))))) + (let [image (db/get-by-id conn :image image-id) + image-path (copy-image (:path image)) + thumb-path (copy-image (:thumb-path image))] + + (-> (db/insert! conn :file-image + {:file-id file-id + :name (:name image) + :path (str image-path) + :width (:width image) + :height (:height image) + :mtype (:mtype image) + :thumb-path (str thumb-path) + :thumb-width (:thumb-width image) + :thumb-height (:thumb-height image) + :thumb-quality (:thumb-quality image) + :thumb-mtype (:thumb-mtype image)}) + (images/resolve-urls :path :uri) + (images/resolve-urls :thumb-path :thumb-uri)))) (defn- copy-image [path] - (vu/blocking - (let [image-path (ust/lookup media/media-storage path)] - (ust/save! media/media-storage (fs/name image-path) image-path)))) + (let [image-path (ust/lookup media/media-storage path)] + (ust/save! media/media-storage (fs/name image-path) image-path))) diff --git a/backend/src/uxbox/services/mutations/icons.clj b/backend/src/uxbox/services/mutations/icons.clj index a6b16e2004..d8bad54a77 100644 --- a/backend/src/uxbox/services/mutations/icons.clj +++ b/backend/src/uxbox/services/mutations/icons.clj @@ -10,17 +10,17 @@ (ns uxbox.services.mutations.icons (:require [clojure.spec.alpha :as s] - [promesa.core :as p] + [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] + [uxbox.common.uuid :as uuid] [uxbox.config :as cfg] [uxbox.db :as db] [uxbox.services.mutations :as sm] [uxbox.services.queries.icons :refer [decode-row]] [uxbox.services.queries.teams :as teams] - [uxbox.services.util :as su] [uxbox.tasks :as tasks] [uxbox.util.blob :as blob] - [uxbox.common.uuid :as uuid])) + [uxbox.util.time :as dt])) ;; --- Helpers & Specs @@ -44,7 +44,6 @@ (s/keys :opt-un [::width ::height ::view-box ::mimetype])) - ;; --- Mutation: Create Library (declare create-library) @@ -67,8 +66,10 @@ (defn create-library [conn {:keys [team-id id name] :as params}] (let [id (or id (uuid/next))] - (db/query-one conn [sql:create-library id team-id name]))) - + (db/insert! conn :icon-library + {:id id + :team-id team-id + :name name}))) ;; --- Mutation: Rename Library @@ -82,59 +83,19 @@ (sm/defmutation ::rename-icon-library [{:keys [id profile-id name] :as params}] (db/with-atomic [conn db/pool] - (p/let [lib (select-library-for-update conn id)] + (let [lib (select-library-for-update conn id)] (teams/check-edition-permissions! conn profile-id (:team-id lib)) (rename-library conn id name)))) -(def ^:private sql:select-library-for-update - "select l.* - from icon_library as l - where l.id = $1 - for update") - -(def ^:private sql:rename-library - "update icon_library - set name = $2 - where id = $1") - (defn- select-library-for-update [conn id] - (-> (db/query-one conn [sql:select-library-for-update id]) - (p/then' su/raise-not-found-if-nil))) + (db/get-by-id conn :icon-library id {:for-update true})) (defn- rename-library [conn id name] - (-> (db/query-one conn [sql:rename-library id name]) - (p/then' su/constantly-nil))) - - - -;; ;; --- Copy Icon - -;; (declare create-icon) - -;; (defn- retrieve-icon -;; [conn {:keys [profile-id id]}] -;; (let [sql "select * from icon -;; where id = $1 -;; and deleted_at is null -;; and (profile_id = $2 or -;; profile_id = '00000000-0000-0000-0000-000000000000'::uuid)"] -;; (-> (db/query-one conn [sql id profile-id]) -;; (p/then' su/raise-not-found-if-nil)))) - -;; (s/def ::copy-icon -;; (s/keys :req-un [:us/id ::library-id ::profile-id])) - -;; (sm/defmutation ::copy-icon -;; [{:keys [profile-id id library-id] :as params}] -;; (db/with-atomic [conn db/pool] -;; (-> (retrieve-icon conn {:profile-id profile-id :id id}) -;; (p/then (fn [icon] -;; (let [icon (-> (dissoc icon :id) -;; (assoc :library-id library-id))] -;; (create-icon conn icon))))))) - + (db/update! conn :icon-library + {:name name} + {:id id})) ;; --- Mutation: Delete Library @@ -146,7 +107,7 @@ (sm/defmutation ::delete-icon-library [{:keys [profile-id id] :as params}] (db/with-atomic [conn db/pool] - (p/let [lib (select-library-for-update conn id)] + (let [lib (select-library-for-update conn id)] (teams/check-edition-permissions! conn profile-id (:team-id lib)) ;; Schedule object deletion @@ -154,19 +115,10 @@ :delay cfg/default-deletion-delay :props {:id id :type :icon-library}}) - (delete-library conn id)))) - -(def ^:private sql:mark-library-deleted - "update icon_library - set deleted_at = clock_timestamp() - where id = $1 - returning id") - -(defn- delete-library - [conn id] - (-> (db/query-one conn [sql:mark-library-deleted id]) - (p/then' su/constantly-nil))) - + (db/update! conn :icon-library + {:deleted-at (dt/now)} + {:id id}) + nil))) ;; --- Mutation: Create Icon (Upload) @@ -180,21 +132,20 @@ (sm/defmutation ::create-icon [{:keys [profile-id library-id] :as params}] (db/with-atomic [conn db/pool] - (p/let [lib (select-library-for-update conn library-id)] + (let [lib (select-library-for-update conn library-id)] (teams/check-edition-permissions! conn profile-id (:team-id lib)) (create-icon conn params)))) -(def ^:private sql:create-icon - "insert into icon (id, name, library_id, content, metadata) - values ($1, $2, $3, $4, $5) returning *") - (defn create-icon [conn {:keys [id name library-id metadata content]}] (let [id (or id (uuid/next))] - (-> (db/query-one conn [sql:create-icon id name library-id - content (blob/encode metadata)]) - (p/then' decode-row)))) - + (-> (db/insert! conn :icon + {:id id + :name name + :library-id library-id + :content content + :metadata (blob/encode metadata)}) + (decode-row)))) ;; --- Mutation: Rename Icon @@ -208,33 +159,27 @@ (sm/defmutation ::rename-icon [{:keys [id profile-id name] :as params}] (db/with-atomic [conn db/pool] - (p/let [clr (select-icon-for-update conn id)] - (teams/check-edition-permissions! conn profile-id (:team-id clr)) - (rename-icon conn id name)))) + (let [icon (select-icon-for-update conn id)] + (teams/check-edition-permissions! conn profile-id (:team-id icon)) + (db/update! conn :icon + {:name name} + {:id id})))) -(def ^:private sql:select-icon-for-update +(def ^:private + sql:select-icon-for-update "select i.*, lib.team_id as team_id from icon as i inner join icon_library as lib on (lib.id = i.library_id) - where i.id = $1 + where i.id = ? for update") -(def ^:private sql:rename-icon - "update icon - set name = $2 - where id = $1") - (defn- select-icon-for-update [conn id] - (-> (db/query-one conn [sql:select-icon-for-update id]) - (p/then' su/raise-not-found-if-nil))) - -(defn- rename-icon - [conn id name] - (-> (db/query-one conn [sql:rename-icon id name]) - (p/then' su/constantly-nil))) - + (let [row (db/exec-one! conn [sql:select-icon-for-update id])] + (when-not row + (ex/raise :type :not-found)) + row)) ;; --- Mutation: Delete Icon @@ -247,7 +192,7 @@ (sm/defmutation ::delete-icon [{:keys [id profile-id] :as params}] (db/with-atomic [conn db/pool] - (p/let [icn (select-icon-for-update conn id)] + (let [icn (select-icon-for-update conn id)] (teams/check-edition-permissions! conn profile-id (:team-id icn)) ;; Schedule object deletion @@ -255,14 +200,7 @@ :delay cfg/default-deletion-delay :props {:id id :type :icon}}) - (delete-icon conn id)))) - -(def ^:private sql:mark-icon-deleted - "update icon - set deleted_at = clock_timestamp() - where id = $1") - -(defn- delete-icon - [conn id] - (-> (db/query-one conn [sql:mark-icon-deleted id]) - (p/then' su/constantly-nil))) + (db/update! conn :icon + {:deleted-at (dt/now)} + {:id id}) + nil))) diff --git a/backend/src/uxbox/services/mutations/images.clj b/backend/src/uxbox/services/mutations/images.clj index a0242d6b6f..905fc8a23d 100644 --- a/backend/src/uxbox/services/mutations/images.clj +++ b/backend/src/uxbox/services/mutations/images.clj @@ -21,10 +21,9 @@ [uxbox.tasks :as tasks] [uxbox.services.queries.teams :as teams] [uxbox.services.mutations :as sm] - [uxbox.services.util :as su] [uxbox.common.uuid :as uuid] [uxbox.util.storage :as ust] - [vertx.util :as vu])) + [uxbox.util.time :as dt])) (def thumbnail-options {:width 800 @@ -53,21 +52,18 @@ (teams/check-edition-permissions! conn profile-id team-id) (create-library conn params))) -(def ^:private sql:create-library - "insert into image_library (id, team_id, name) - values ($1, $2, $3) - returning *;") - (defn create-library [conn {:keys [id team-id name]}] (let [id (or id (uuid/next))] - (db/query-one conn [sql:create-library id team-id name]))) + (db/insert! conn :image-library + {:id id + :team-id team-id + :name name}))) ;; --- Rename Library (declare select-library-for-update) -(declare rename-library) (s/def ::rename-image-library (s/keys :req-un [::id ::profile-id ::name])) @@ -75,31 +71,15 @@ (sm/defmutation ::rename-image-library [{:keys [profile-id id name] :as params}] (db/with-atomic [conn db/pool] - (p/let [lib (select-library-for-update conn id)] + (let [lib (select-library-for-update conn id)] (teams/check-edition-permissions! conn profile-id (:team-id lib)) - (rename-library conn id name)))) - -(def ^:private sql:select-library-for-update - "select l.* - from image_library as l - where l.id = $1 - for update") - -(def ^:private sql:rename-library - "update image_library - set name = $2 - where id = $1") + (db/update! conn :image-library + {:name name} + {:id id})))) (defn- select-library-for-update [conn id] - (-> (db/query-one conn [sql:select-library-for-update id]) - (p/then' su/raise-not-found-if-nil))) - -(defn- rename-library - [conn id name] - (-> (db/query-one conn [sql:rename-library id name]) - (p/then' su/constantly-nil))) - + (db/get-by-id conn :image-library id {:for-update true})) ;; --- Delete Library @@ -112,7 +92,7 @@ (sm/defmutation ::delete-image-library [{:keys [id profile-id] :as params}] (db/with-atomic [conn db/pool] - (p/let [lib (select-library-for-update conn id)] + (let [lib (select-library-for-update conn id)] (teams/check-edition-permissions! conn profile-id (:team-id lib)) ;; Schedule object deletion @@ -120,17 +100,10 @@ :delay cfg/default-deletion-delay :props {:id id :type :image-library}}) - (delete-library conn id)))) - -(def ^:private sql:mark-library-deleted - "update image_library - set deleted_at = clock_timestamp() - where id = $1") - -(defn- delete-library - [conn id] - (-> (db/query-one conn [sql:mark-library-deleted id]) - (p/then' su/constantly-nil))) + (db/update! conn :image-library + {:deleted-at (dt/now)} + {:id id}) + nil))) @@ -143,16 +116,16 @@ (def valid-image-types? #{"image/jpeg", "image/png", "image/webp"}) -(s/def :uxbox$upload/name ::us/string) +(s/def :uxbox$upload/filename ::us/string) (s/def :uxbox$upload/size ::us/integer) -(s/def :uxbox$upload/mtype valid-image-types?) -(s/def :uxbox$upload/path ::us/string) +(s/def :uxbox$upload/content-type valid-image-types?) +(s/def :uxbox$upload/tempfile any?) (s/def ::upload - (s/keys :req-un [:uxbox$upload/name + (s/keys :req-un [:uxbox$upload/filename :uxbox$upload/size - :uxbox$upload/path - :uxbox$upload/mtype])) + :uxbox$upload/tempfile + :uxbox$upload/content-type])) (s/def ::content ::upload) @@ -163,69 +136,54 @@ (sm/defmutation ::upload-image [{:keys [library-id profile-id] :as params}] (db/with-atomic [conn db/pool] - (p/let [lib (select-library-for-update conn library-id)] + (let [lib (select-library-for-update conn library-id)] (teams/check-edition-permissions! conn profile-id (:team-id lib)) (create-image conn params)))) -(def ^:private sql:insert-image - "insert into image - (id, library_id, name, path, width, height, mtype, - thumb_path, thumb_width, thumb_height, thumb_quality, thumb_mtype) - values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) - returning *") - (defn create-image [conn {:keys [id content library-id name]}] - (when-not (valid-image-types? (:mtype content)) + (when-not (valid-image-types? (:content-type content)) (ex/raise :type :validation :code :image-type-not-allowed :hint "Seems like you are uploading an invalid image.")) - (p/let [image-opts (vu/blocking (images/info (:path content))) - image-path (persist-image-on-fs content) - thumb-opts thumbnail-options - thumb-path (persist-image-thumbnail-on-fs thumb-opts image-path) - id (or id (uuid/next)) - - sqlv [sql:insert-image - id - library-id - name - (str image-path) - (:width image-opts) - (:height image-opts) - (:mtype content) - (str thumb-path) - (:width thumb-opts) - (:height thumb-opts) - (:quality thumb-opts) - (images/format->mtype (:format thumb-opts))]] - - (-> (db/query-one conn sqlv) - (p/then' #(images/resolve-urls % :path :uri)) - (p/then' #(images/resolve-urls % :thumb-path :thumb-uri))))) + (let [image-opts (images/info (:tempfile content)) + image-path (persist-image-on-fs content) + thumb-opts thumbnail-options + thumb-path (persist-image-thumbnail-on-fs thumb-opts image-path)] + (-> (db/insert! conn :image + {:id (or id (uuid/next)) + :library-id library-id + :name name + :path (str image-path) + :width (:width image-opts) + :height (:height image-opts) + :mtype (:content-type content) + :thumb-path (str thumb-path) + :thumb-width (:width thumb-opts) + :thumb-height (:height thumb-opts) + :thumb-quality (:quality thumb-opts) + :thumb-mtype (images/format->mtype (:format thumb-opts))}) + (images/resolve-urls :path :uri) + (images/resolve-urls :thumb-path :thumb-uri)))) (defn persist-image-on-fs - [{:keys [name path]}] - (vu/blocking - (let [filename (fs/name name)] - (ust/save! media/media-storage filename path)))) + [{:keys [filename tempfile]}] + (let [filename (fs/name filename)] + (ust/save! media/media-storage filename tempfile))) (defn persist-image-thumbnail-on-fs [thumb-opts input-path] - (vu/blocking - (let [input-path (ust/lookup media/media-storage input-path) - thumb-data (images/generate-thumbnail input-path thumb-opts) - [filename _] (fs/split-ext (fs/name input-path)) - thumb-name (->> (images/format->extension (:format thumb-opts)) + (let [input-path (ust/lookup media/media-storage input-path) + thumb-data (images/generate-thumbnail input-path thumb-opts) + [filename _] (fs/split-ext (fs/name input-path)) + thumb-name (->> (images/format->extension (:format thumb-opts)) (str "thumbnail-" filename))] - (ust/save! media/media-storage thumb-name thumb-data)))) - + (ust/save! media/media-storage thumb-name thumb-data))) ;; --- Mutation: Rename Image (declare select-image-for-update) -(declare rename-image) (s/def ::rename-image (s/keys :req-un [::id ::profile-id ::name])) @@ -233,70 +191,38 @@ (sm/defmutation ::rename-image [{:keys [id profile-id name] :as params}] (db/with-atomic [conn db/pool] - (p/let [img (select-image-for-update conn id)] + (let [img (select-image-for-update conn id)] (teams/check-edition-permissions! conn profile-id (:team-id img)) - (rename-image conn id name)))) + (db/update! conn :image + {:name name} + {:id id})))) (def ^:private sql:select-image-for-update "select img.*, lib.team_id as team_id from image as img inner join image_library as lib on (lib.id = img.library_id) - where img.id = $1 + where img.id = ? for update of img") -(def ^:private sql:rename-image - "update image - set name = $2 - where id = $1") - (defn- select-image-for-update [conn id] - (-> (db/query-one conn [sql:select-image-for-update id]) - (p/then' su/raise-not-found-if-nil))) + (let [row (db/exec-one! conn [sql:select-image-for-update id])] + (when-not row + (ex/raise :type :not-found)) + row)) -(defn- rename-image - [conn id name] - (-> (db/query-one conn [sql:rename-image id name]) - (p/then' su/constantly-nil))) - - - -;; --- Copy Image - -;; (declare retrieve-image) - -;; (s/def ::copy-image -;; (s/keys :req-un [::id ::library-id ::profile-id])) - -;; (sm/defmutation ::copy-image -;; [{:keys [profile-id id library-id] :as params}] -;; (letfn [(copy-image [conn {:keys [path] :as image}] -;; (-> (ds/lookup media/images-storage (:path image)) -;; (p/then (fn [path] (ds/save media/images-storage (fs/name path) path))) -;; (p/then (fn [path] -;; (-> image -;; (assoc :path (str path) :library-id library-id) -;; (dissoc :id)))) -;; (p/then (partial store-image-in-db conn))))] - -;; (db/with-atomic [conn db/pool] -;; (-> (retrieve-image conn {:id id :profile-id profile-id}) -;; (p/then su/raise-not-found-if-nil) -;; (p/then (partial copy-image conn)))))) ;; --- Delete Image -(declare delete-image) - (s/def ::delete-image (s/keys :req-un [::id ::profile-id])) (sm/defmutation ::delete-image [{:keys [profile-id id] :as params}] (db/with-atomic [conn db/pool] - (p/let [img (select-image-for-update conn id)] + (let [img (select-image-for-update conn id)] (teams/check-edition-permissions! conn profile-id (:team-id img)) ;; Schedule object deletion @@ -304,14 +230,7 @@ :delay cfg/default-deletion-delay :props {:id id :type :image}}) - (delete-image conn id)))) - -(def ^:private sql:mark-image-deleted - "update image - set deleted_at = clock_timestamp() - where id = $1") - -(defn- delete-image - [conn id] - (-> (db/query-one conn [sql:mark-image-deleted id]) - (p/then' su/constantly-nil))) + (db/update! conn :image + {:deleted-at (dt/now)} + {:id id}) + nil))) diff --git a/backend/src/uxbox/services/mutations/pages.clj b/backend/src/uxbox/services/mutations/pages.clj index a1bca24e93..8c570938e4 100644 --- a/backend/src/uxbox/services/mutations/pages.clj +++ b/backend/src/uxbox/services/mutations/pages.clj @@ -10,22 +10,19 @@ (ns uxbox.services.mutations.pages (:require [clojure.spec.alpha :as s] - [promesa.core :as p] [uxbox.common.data :as d] - [uxbox.common.pages :as cp] [uxbox.common.exceptions :as ex] + [uxbox.common.pages :as cp] [uxbox.common.spec :as us] + [uxbox.common.uuid :as uuid] [uxbox.config :as cfg] [uxbox.db :as db] - [uxbox.services.queries.files :as files] [uxbox.services.mutations :as sm] + [uxbox.services.queries.files :as files] [uxbox.services.queries.pages :refer [decode-row]] - [uxbox.services.util :as su] [uxbox.tasks :as tasks] [uxbox.util.blob :as blob] - [uxbox.util.sql :as sql] - [uxbox.common.uuid :as uuid] - [vertx.eventbus :as ve])) + [uxbox.util.time :as dt])) ;; --- Helpers & Specs @@ -51,20 +48,17 @@ (files/check-edition-permissions! conn profile-id file-id) (create-page conn params))) -(def ^:private sql:create-page - "insert into page (id, file_id, name, ordering, data) - values ($1, $2, $3, $4, $5) - returning *") - (defn- create-page [conn {:keys [id file-id name ordering data] :as params}] (let [id (or id (uuid/next)) data (blob/encode data)] - (-> (db/query-one conn [sql:create-page - id file-id name - ordering data]) - (p/then' decode-row)))) - + (-> (db/insert! conn :page + {:id id + :file-id file-id + :name name + :ordering ordering + :data data}) + (decode-row)))) ;; --- Mutation: Rename Page @@ -78,33 +72,19 @@ (sm/defmutation ::rename-page [{:keys [id name profile-id]}] (db/with-atomic [conn db/pool] - (p/let [page (select-page-for-update conn id)] + (let [page (select-page-for-update conn id)] (files/check-edition-permissions! conn profile-id (:file-id page)) (rename-page conn (assoc page :name name))))) -(def ^:private sql:select-page-for-update - "select p.id, p.revn, p.file_id, p.data - from page as p - where p.id = $1 - and deleted_at is null - for update;") - (defn- select-page-for-update [conn id] - (-> (db/query-one conn [sql:select-page-for-update id]) - (p/then' su/raise-not-found-if-nil))) - -(def ^:private sql:rename-page - "update page - set name = $2 - where id = $1 - and deleted_at is null") + (db/get-by-id conn :page id {:for-update true})) (defn- rename-page [conn {:keys [id name] :as params}] - (-> (db/query-one conn [sql:rename-page id name]) - (p/then su/constantly-nil))) - + (db/update! conn :page + {:name name} + {:id id})) ;; --- Mutation: Sort Pages @@ -118,20 +98,16 @@ (sm/defmutation ::reorder-pages [{:keys [profile-id file-id page-ids]}] (db/with-atomic [conn db/pool] - (p/run! #(update-page-ordering conn file-id %) - (d/enumerate page-ids)) + (run! #(update-page-ordering conn file-id %) + (d/enumerate page-ids)) nil)) -(def ^:private sql:update-page-ordering - "update page - set ordering = $1 - where id = $2 and file_id = $3") - (defn- update-page-ordering [conn file-id [ordering page-id]] - (-> (db/query-one conn [sql:update-page-ordering ordering page-id file-id]) - (p/then su/constantly-nil))) - + (db/update! conn :page + {:ordering ordering} + {:file-id file-id + :id page-id})) ;; --- Mutation: Generate Share Token @@ -146,16 +122,9 @@ (let [token (-> (sodi.prng/random-bytes 16) (sodi.util/bytes->b64s))] (db/with-atomic [conn db/pool] - (assign-page-share-token conn id token)))) - -(def ^:private sql:update-page-share-token - "update page set share_token = $2 where id = $1") - -(defn- assign-page-share-token - [conn id token] - (-> (db/query-one conn [sql:update-page-share-token id token]) - (p/then (fn [_] {:id id :share-token token})))) - + (db/update! conn :page + {:share-token token} + {:id id})))) ;; --- Mutation: Clear Share Token @@ -166,7 +135,9 @@ (sm/defmutation ::clear-page-share-token [{:keys [id] :as params}] (db/with-atomic [conn db/pool] - (assign-page-share-token conn id nil))) + (db/update! conn :page + {:share-token nil} + {:id id}))) @@ -183,13 +154,12 @@ (declare update-page) (declare retrieve-lagged-changes) -(declare update-page-data) -(declare insert-page-change) +(declare insert-page-change!) (sm/defmutation ::update-page [{:keys [id profile-id] :as params}] (db/with-atomic [conn db/pool] - (p/let [{:keys [file-id] :as page} (select-page-for-update conn id)] + (let [{:keys [file-id] :as page} (select-page-for-update conn id)] (files/check-edition-permissions! conn profile-id file-id) (update-page conn page params)))) @@ -211,61 +181,52 @@ page (assoc page :data data :revn (inc (:revn page)) - :changes (blob/encode changes))] + :changes (blob/encode changes)) - (-> (update-page-data conn page) - (p/then (fn [_] (insert-page-change conn page))) - (p/then (fn [s] - (let [topic (str "internal.uxbox.file." (:file-id page))] - (p/do! (ve/publish! uxbox.core/system topic - {:type :page-change - :profile-id (:profile-id params) - :page-id (:page-id s) - :revn (:revn s) - :changes changes}) - (retrieve-lagged-changes conn s params)))))))) + chng (insert-page-change! conn page)] -(def ^:private sql:update-page-data - "update page - set revn = $1, - data = $2 - where id = $3") + (db/update! conn :page + {:revn (:revn page) + :data data} + {:id (:id page)}) -(defn- update-page-data - [conn {:keys [id name revn data]}] - (-> (db/query-one conn [sql:update-page-data revn data id]) - (p/then' su/constantly-nil))) + (retrieve-lagged-changes conn chng params))) -(def ^:private sql:insert-page-change - "insert into page_change (id, page_id, revn, data, changes) - values ($1, $2, $3, $4, $5) - returning id, page_id, revn, changes") +;; (p/do! (ve/publish! uxbox.core/system topic +;; {:type :page-change +;; :profile-id (:profile-id params) +;; :page-id (:page-id s) +;; :revn (:revn s) +;; :changes changes}) -(defn- insert-page-change +(defn- insert-page-change! [conn {:keys [revn data changes] :as page}] (let [id (uuid/next) page-id (:id page)] - (db/query-one conn [sql:insert-page-change id - page-id revn data changes]))) + (db/insert! conn :page-change + {:id id + :page-id page-id + :revn revn + :data data + :changes changes}))) -(def ^:private sql:lagged-changes +(def ^:private + sql:lagged-changes "select s.id, s.changes from page_change as s - where s.page_id = $1 - and s.revn > $2 + where s.page_id = ? + and s.revn > ? order by s.created_at asc") (defn- retrieve-lagged-changes [conn snapshot params] - (-> (db/query conn [sql:lagged-changes (:id params) (:revn params)]) - (p/then (fn [rows] - {:page-id (:id params) - :revn (:revn snapshot) - :changes (into [] (comp (map decode-row) - (map :changes) - (mapcat identity)) - rows)})))) - + (let [rows (db/exec! conn [sql:lagged-changes (:id params) (:revn params)])] + {:page-id (:id params) + :revn (:revn snapshot) + :changes (into [] (comp (map decode-row) + (map :changes) + (mapcat identity)) + rows)})) ;; --- Mutation: Delete Page @@ -277,7 +238,7 @@ (sm/defmutation ::delete-page [{:keys [id profile-id]}] (db/with-atomic [conn db/pool] - (p/let [page (select-page-for-update conn id)] + (let [page (select-page-for-update conn id)] (files/check-edition-permissions! conn profile-id (:file-id page)) ;; Schedule object deletion @@ -285,15 +246,7 @@ :delay cfg/default-deletion-delay :props {:id id :type :page}}) - (mark-page-deleted conn id)))) - -(def ^:private sql:mark-page-deleted - "update page - set deleted_at = clock_timestamp() - where id = $1 - and deleted_at is null") - -(defn- mark-page-deleted - [conn id] - (-> (db/query-one conn [sql:mark-page-deleted id]) - (p/then su/constantly-nil))) + (db/update! conn :page + {:deleted-at (dt/now)} + {:id id}) + nil))) diff --git a/backend/src/uxbox/services/mutations/profile.clj b/backend/src/uxbox/services/mutations/profile.clj index 91d2327df7..e3e39cc3f4 100644 --- a/backend/src/uxbox/services/mutations/profile.clj +++ b/backend/src/uxbox/services/mutations/profile.clj @@ -19,23 +19,21 @@ [sodi.util] [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] + [uxbox.common.uuid :as uuid] [uxbox.config :as cfg] [uxbox.db :as db] [uxbox.emails :as emails] [uxbox.images :as images] - [uxbox.tasks :as tasks] [uxbox.media :as media] [uxbox.services.mutations :as sm] [uxbox.services.mutations.images :as imgs] - [uxbox.services.mutations.teams :as mt.teams] [uxbox.services.mutations.projects :as mt.projects] + [uxbox.services.mutations.teams :as mt.teams] [uxbox.services.queries.profile :as profile] - [uxbox.services.util :as su] + [uxbox.tasks :as tasks] [uxbox.util.blob :as blob] [uxbox.util.storage :as ust] - [uxbox.common.uuid :as uuid] - [uxbox.util.time :as tm] - [vertx.util :as vu])) + [uxbox.util.time :as dt])) ;; --- Helpers & Specs @@ -75,22 +73,15 @@ :code ::wrong-credentials)) profile)] (db/with-atomic [conn db/pool] - (p/let [prof (-> (retrieve-profile-by-email conn email) - (p/then' check-profile) - (p/then' profile/strip-private-attrs)) - addt (profile/retrieve-additional-data conn (:id prof))] + (let [prof (-> (retrieve-profile-by-email conn email) + (check-profile) + (profile/strip-private-attrs)) + addt (profile/retrieve-additional-data conn (:id prof))] (merge prof addt))))) -(def sql:profile-by-email - "select u.* - from profile as u - where u.email=$1 - and u.deleted_at is null") - (defn- retrieve-profile-by-email [conn email] - (-> (db/query-one conn [sql:profile-by-email email]) - (p/then #(images/resolve-media-uris % [:photo :photo-uri])))) + (db/get-by-params conn :profile {:email email} {:for-update true})) ;; --- Mutation: Update Profile (own) @@ -106,10 +97,11 @@ (defn- update-profile [conn {:keys [id fullname lang theme] :as params}] - (let [sqlv [sql:update-profile id fullname lang theme]] - (-> (db/query-one conn sqlv) - (p/then' su/raise-not-found-if-nil) - (p/then' profile/strip-private-attrs)))) + (db/update! conn :profile + {:fullname fullname + :lang lang + :theme theme} + {:id id})) (s/def ::update-profile (s/keys :req-un [::id ::fullname ::lang ::theme])) @@ -117,39 +109,31 @@ (sm/defmutation ::update-profile [params] (db/with-atomic [conn db/pool] - (update-profile conn params))) + (update-profile conn params) + nil)) ;; --- Mutation: Update Password (defn- validate-password! [conn {:keys [profile-id old-password] :as params}] - (p/let [profile (profile/retrieve-profile conn profile-id) - result (sodi.pwhash/verify old-password (:password profile))] + (let [profile (profile/retrieve-profile conn profile-id) + result (sodi.pwhash/verify old-password (:password profile))] (when-not (:valid result) (ex/raise :type :validation :code ::old-password-not-match)))) -(defn update-password - [conn {:keys [profile-id password]}] - (let [sql "update profile - set password = $2 - where id = $1 - and deleted_at is null - returning id" - password (sodi.pwhash/derive password)] - (-> (db/query-one conn [sql profile-id password]) - (p/then' su/raise-not-found-if-nil) - (p/then' su/constantly-nil)))) - (s/def ::update-profile-password (s/keys :req-un [::profile-id ::password ::old-password])) (sm/defmutation ::update-profile-password - [params] + [{:keys [password profile-id] :as params}] (db/with-atomic [conn db/pool] (validate-password! conn params) - (update-password conn params))) + (db/update! conn :profile + {:password (sodi.pwhash/derive password)} + {:id profile-id}) + nil)) @@ -165,8 +149,8 @@ (sm/defmutation ::update-profile-photo [{:keys [profile-id file] :as params}] (db/with-atomic [conn db/pool] - (p/let [profile (profile/retrieve-profile conn profile-id) - photo (upload-photo conn params)] + (let [profile (profile/retrieve-profile conn profile-id) + photo (upload-photo conn params)] ;; Schedule deletion of old photo (when (and (string? (:photo profile)) @@ -178,20 +162,20 @@ (defn- upload-photo [conn {:keys [file profile-id]}] - (when-not (imgs/valid-image-types? (:mtype file)) + (when-not (imgs/valid-image-types? (:content-type file)) (ex/raise :type :validation :code :image-type-not-allowed :hint "Seems like you are uploading an invalid image.")) - (vu/blocking - (let [thumb-opts {:width 256 - :height 256 - :quality 75 - :format "webp"} - prefix (-> (sodi.prng/random-bytes 8) - (sodi.util/bytes->b64s)) - name (str prefix ".webp") - photo (images/generate-thumbnail2 (fs/path (:path file)) thumb-opts)] - (ust/save! media/media-storage name photo)))) + (let [thumb-opts {:width 256 + :height 256 + :quality 75 + :format "webp"} + prefix (-> (sodi.prng/random-bytes 8) + (sodi.util/bytes->b64s)) + name (str prefix ".webp") + path (fs/path (:tempfile file)) + photo (images/generate-thumbnail2 path thumb-opts)] + (ust/save! media/media-storage name photo))) (defn- update-profile-photo [conn profile-id path] @@ -199,9 +183,10 @@ where id=$2 and deleted_at is null returning id"] - (-> (db/query-one conn [sql (str path) profile-id]) - (p/then' su/raise-not-found-if-nil)))) - + (db/update! conn :profile + {:photo (str path)} + {:id profile-id}) + nil)) ;; --- Mutation: Register Profile @@ -213,7 +198,8 @@ (s/keys :req-un [::email ::password ::fullname])) (defn email-domain-in-whitelist? - "Returns true if email's domain is in the given whitelist or if given whitelist is an empty string." + "Returns true if email's domain is in the given whitelist or if given + whitelist is an empty string." [whitelist email] (if (str/blank? whitelist) true @@ -226,20 +212,18 @@ (when-not (:registration-enabled cfg/config) (ex/raise :type :restriction :code :registration-disabled)) - (when-not (email-domain-in-whitelist? (:registration-domain-whitelist cfg/config) (:email params)) + (when-not (email-domain-in-whitelist? (:registration-domain-whitelist cfg/config) + (:email params)) (ex/raise :type :validation :code ::email-domain-is-not-allowed)) (db/with-atomic [conn db/pool] (check-profile-existence! conn params) - (-> (register-profile conn params) - (p/then (fn [profile] - ;; TODO: send a correct link for email verification - (let [data {:to (:email params) - :name (:fullname params)}] - (p/do! - (emails/send! conn emails/register data) - profile))))))) - + (let [profile (register-profile conn params)] + ;; TODO: send a correct link for email verification + (let [data {:to (:email params) + :name (:fullname params)}] + (emails/send! conn emails/register data) + profile)))) (def ^:private sql:insert-profile "insert into profile (id, fullname, email, password, photo, is_demo) @@ -256,12 +240,11 @@ (defn- check-profile-existence! [conn {:keys [email] :as params}] - (-> (db/query-one conn [sql:profile-existence email]) - (p/then' (fn [result] - (when (:val result) - (ex/raise :type :validation - :code ::email-already-exists)) - params)))) + (let [result (db/exec-one! conn [sql:profile-existence email])] + (when (:val result) + (ex/raise :type :validation + :code ::email-already-exists)) + params)) (defn- create-profile "Create the profile entry on the database with limited input @@ -270,33 +253,42 @@ (let [id (or id (uuid/next)) demo? (if (boolean? demo?) demo? false) password (sodi.pwhash/derive password)] - (db/query-one conn [sql:insert-profile id fullname email password demo?]))) + (db/insert! conn :profile + {:id id + :fullname fullname + :email email + :photo "" + :password password + :is-demo demo?}))) (defn- create-profile-email [conn {:keys [id email] :as profile}] - (-> (db/query-one conn [sql:insert-email id email]) - (p/then' su/constantly-nil))) + (db/insert! conn :profile-email + {:profile-id id + :email email + :is-main true})) (defn register-profile [conn params] - (p/let [prof (create-profile conn params) - _ (create-profile-email conn prof) + (let [prof (create-profile conn params) + _ (create-profile-email conn prof) - team (mt.teams/create-team conn {:profile-id (:id prof) - :name "Default" - :default? true}) - _ (mt.teams/create-team-profile conn {:team-id (:id team) - :profile-id (:id prof)}) + team (mt.teams/create-team conn {:profile-id (:id prof) + :name "Default" + :default? true}) + _ (mt.teams/create-team-profile conn {:team-id (:id team) + :profile-id (:id prof)}) - proj (mt.projects/create-project conn {:profile-id (:id prof) - :team-id (:id team) - :name "Drafts" - :default? true}) - _ (mt.projects/create-project-profile conn {:project-id (:id proj) - :profile-id (:id prof)})] + proj (mt.projects/create-project conn {:profile-id (:id prof) + :team-id (:id team) + :name "Drafts" + :default? true}) + _ (mt.projects/create-project-profile conn {:project-id (:id proj) + :profile-id (:id prof)}) + ] (merge (profile/strip-private-attrs prof) - {:default-team team - :default-project proj}))) + {:default-team (:id team) + :default-project (:id proj)}))) ;; --- Mutation: Request Profile Recovery @@ -312,20 +304,21 @@ (let [token (-> (sodi.prng/random-bytes 32) (sodi.util/bytes->b64s)) sql sql:insert-recovery-token] - (-> (db/query-one conn [sql id token]) - (p/then (constantly (assoc profile :token token)))))) + (db/insert! conn :password-recovery-token + {:profile-id id + :token token}) + (assoc profile :token token))) (send-email-notification [conn profile] - (emails/send! conn - emails/password-recovery + (emails/send! conn emails/password-recovery {:to (:email profile) :token (:token profile) - :name (:fullname profile)}))] + :name (:fullname profile)}) + nil)] (db/with-atomic [conn db/pool] - (-> (retrieve-profile-by-email conn email) - (p/then' su/raise-not-found-if-nil) - (p/then #(create-recovery-token conn %)) - (p/then #(send-email-notification conn %)) - (p/then (constantly nil)))))) + (let [profile (->> (retrieve-profile-by-email conn email) + (create-recovery-token conn))] + (send-email-notification conn profile))))) + ;; --- Mutation: Recover Profile @@ -343,18 +336,17 @@ where token=$1 returning *" sql "select * from password_recovery_token where token=$1"] - (-> (db/query-one conn [sql token]) - (p/then' :profile-id) - (p/then' su/raise-not-found-if-nil)))) + (-> {:token token} + (db/get-by-params conn :password-recovery-token) + (:profile-id)))) (update-password [conn profile-id] (let [sql "update profile set password=$2 where id=$1" pwd (sodi.pwhash/derive password)] - (-> (db/query-one conn [sql profile-id pwd]) - (p/then' (constantly nil)))))] + (db/update! conn :profile {:password pwd} {:id profile-id}) + nil))] (db/with-atomic [conn db/pool] (-> (validate-token conn token) - (p/then (fn [profile-id] (update-password conn profile-id))))))) - + (update-password conn))))) ;; --- Mutation: Delete Profile @@ -372,16 +364,19 @@ ;; Schedule a complete deletion of profile (tasks/schedule! conn {:name "delete-profile" - :delay (tm/duration {:hours 48}) + :delay (dt/duration {:hours 48}) :props {:profile-id profile-id}}) - (mark-profile-as-deleted! conn profile-id))) + (db/update! conn :profile + {:deleted-at (dt/now)} + {:id profile-id}) + nil)) (def ^:private sql:teams-ownership-check "with teams as ( select tpr.team_id as id from team_profile_rel as tpr - where tpr.profile_id = $1 + where tpr.profile_id = ? and tpr.is_owner is true ) select tpr.team_id, @@ -393,18 +388,9 @@ (defn- check-teams-ownership! [conn profile-id] - (-> (db/query conn [sql:teams-ownership-check profile-id]) - (p/then' (fn [rows] - (when-not (empty? rows) - (ex/raise :type :validation - :code :owner-teams-with-people - :hint "The user need to transfer ownership of owned teams." - :context {:teams (mapv :team-id rows)})))))) - -(def ^:private sql:mark-profile-deleted - "update profile set deleted_at=now() where id=$1") - -(defn- mark-profile-as-deleted! - [conn profile-id] - (-> (db/query-one conn [sql:mark-profile-deleted profile-id]) - (p/then' su/constantly-nil))) + (let [rows (db/exec! conn [sql:teams-ownership-check profile-id])] + (when-not (empty? rows) + (ex/raise :type :validation + :code :owner-teams-with-people + :hint "The user need to transfer ownership of owned teams." + :context {:teams (mapv :team-id rows)})))) diff --git a/backend/src/uxbox/services/mutations/projects.clj b/backend/src/uxbox/services/mutations/projects.clj index 12214ed1b6..b23552aaa2 100644 --- a/backend/src/uxbox/services/mutations/projects.clj +++ b/backend/src/uxbox/services/mutations/projects.clj @@ -10,16 +10,14 @@ (ns uxbox.services.mutations.projects (:require [clojure.spec.alpha :as s] - [promesa.core :as p] - [uxbox.config :as cfg] - [uxbox.db :as db] [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] - [uxbox.tasks :as tasks] + [uxbox.common.uuid :as uuid] + [uxbox.config :as cfg] + [uxbox.db :as db] [uxbox.services.mutations :as sm] - [uxbox.services.util :as su] - [uxbox.util.blob :as blob] - [uxbox.common.uuid :as uuid])) + [uxbox.tasks :as tasks] + [uxbox.util.blob :as blob])) ;; --- Helpers & Specs @@ -35,28 +33,28 @@ tpr.can_edit from team_profile_rel as tpr inner join project as p on (p.team_id = tpr.team_id) - where p.id = $1 - and tpr.profile_id = $2 + where p.id = ? + and tpr.profile_id = ? union all select ppr.is_owner, ppr.is_admin, ppr.can_edit from project_profile_rel as ppr - where ppr.project_id = $1 - and ppr.profile_id = $2") + where ppr.project_id = ? + and ppr.profile_id = ?") (defn check-edition-permissions! [conn profile-id project-id] - (-> (db/query conn [sql:project-permissions project-id profile-id]) - (p/then' seq) - (p/then' su/raise-not-found-if-nil) - (p/then' (fn [rows] - (when-not (or (some :can-edit rows) - (some :is-admin rows) - (some :is-owner rows)) - (ex/raise :type :validation - :code :not-authorized)))))) - + (let [rows (db/exec! conn [sql:project-permissions + project-id profile-id + project-id profile-id])] + (when (empty? rows) + (ex/raise :type :not-found)) + (when-not (or (some :can-edit rows) + (some :is-admin rows) + (some :is-owner rows)) + (ex/raise :type :validation + :code :not-authorized)))) ;; --- Mutation: Create Project @@ -72,30 +70,28 @@ (sm/defmutation ::create-project [params] (db/with-atomic [conn db/pool] - (p/let [proj (create-project conn params)] + (let [proj (create-project conn params)] (create-project-profile conn (assoc params :project-id (:id proj))) proj))) -(def ^:private sql:insert-project - "insert into project (id, team_id, name, is_default) - values ($1, $2, $3, $4) - returning *") - (defn create-project [conn {:keys [id profile-id team-id name default?] :as params}] (let [id (or id (uuid/next)) default? (if (boolean? default?) default? false)] - (db/query-one conn [sql:insert-project id team-id name default?]))) - -(def ^:private sql:create-project-profile - "insert into project_profile_rel (project_id, profile_id, is_owner, is_admin, can_edit) - values ($1, $2, true, true, true) - returning *") + (db/insert! conn :project + {:id id + :team-id team-id + :name name + :is-default default?}))) (defn create-project-profile [conn {:keys [project-id profile-id] :as params}] - (-> (db/query-one conn [sql:create-project-profile project-id profile-id]) - (p/then' su/constantly-nil))) + (db/insert! conn :project-profile-rel + {:project-id project-id + :profile-id profile-id + :is-owner true + :is-admin true + :can-edit true})) @@ -107,23 +103,13 @@ (s/keys :req-un [::profile-id ::name ::id])) (sm/defmutation ::rename-project - [{:keys [id profile-id] :as params}] + [{:keys [id profile-id name] :as params}] (db/with-atomic [conn db/pool] - (check-edition-permissions! conn profile-id id) - (rename-project conn params))) - -(def ^:private sql:rename-project - "update project - set name = $2 - where id = $1 - and deleted_at is null - returning *") - -(defn rename-project - [conn {:keys [id name] :as params}] - (db/query-one conn [sql:rename-project id name])) - - + (let [project (db/get-by-id conn :project id {:for-update true})] + (check-edition-permissions! conn profile-id id) + (db/update! conn :project + {:name name} + {:id id})))) ;; --- Mutation: Delete Project @@ -147,10 +133,10 @@ (def ^:private sql:mark-project-deleted "update project set deleted_at = clock_timestamp() - where id = $1 + where id = ? returning id") (defn mark-project-deleted [conn {:keys [id profile-id] :as params}] - (-> (db/query-one conn [sql:mark-project-deleted id]) - (p/then' su/constantly-nil))) + (db/exec! conn [sql:mark-project-deleted id]) + nil) diff --git a/backend/src/uxbox/services/mutations/teams.clj b/backend/src/uxbox/services/mutations/teams.clj index fb375580eb..62c977a763 100644 --- a/backend/src/uxbox/services/mutations/teams.clj +++ b/backend/src/uxbox/services/mutations/teams.clj @@ -10,14 +10,12 @@ (ns uxbox.services.mutations.teams (:require [clojure.spec.alpha :as s] - [promesa.core :as p] - [uxbox.db :as db] [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] + [uxbox.common.uuid :as uuid] + [uxbox.db :as db] [uxbox.services.mutations :as sm] - [uxbox.services.util :as su] - [uxbox.util.blob :as blob] - [uxbox.common.uuid :as uuid])) + [uxbox.util.blob :as blob])) ;; --- Helpers & Specs @@ -37,32 +35,28 @@ (sm/defmutation ::create-team [params] (db/with-atomic [conn db/pool] - (p/let [team (create-team conn params)] + (let [team (create-team conn params)] (create-team-profile conn (assoc params :team-id (:id team))) team))) -(def ^:private sql:insert-team - "insert into team (id, name, photo, is_default) - values ($1, $2, '', $3) - returning *") - -(def ^:private sql:create-team-profile - "insert into team_profile_rel (team_id, profile_id, is_owner, is_admin, can_edit) - values ($1, $2, true, true, true) - returning *") - (defn create-team [conn {:keys [id profile-id name default?] :as params}] (let [id (or id (uuid/next)) default? (if (boolean? default?) default? false)] - (db/query-one conn [sql:insert-team id name default?]))) + (db/insert! conn :team + {:id id + :name name + :photo "" + :is-default default?}))) (defn create-team-profile [conn {:keys [team-id profile-id] :as params}] - (-> (db/query-one conn [sql:create-team-profile team-id profile-id]) - (p/then' su/constantly-nil))) - - + (db/insert! conn :team-profile-rel + {:team-id team-id + :profile-id profile-id + :is-owner true + :is-admin true + :can-edit true})) ;; --- Mutation: Team Edition Permissions @@ -71,18 +65,14 @@ tpr.is_admin, tpr.can_edit from team_profile_rel as tpr - where tpr.profile_id = $1 - and tpr.team_id = $2") + where tpr.profile_id = ? + and tpr.team_id = ?") (defn check-edition-permissions! [conn profile-id team-id] - (-> (db/query-one conn [sql:team-permissions profile-id team-id]) - (p/then' (fn [row] - (when-not (or (:can-edit row) - (:is-admin row) - (:is-owner row)) - (ex/raise :type :validation - :code :not-authorized)))))) - - - + (let [row (db/exec-one! conn [sql:team-permissions profile-id team-id])] + (when-not (or (:can-edit row) + (:is-admin row) + (:is-owner row)) + (ex/raise :type :validation + :code :not-authorized)))) diff --git a/backend/src/uxbox/services/mutations/user_attrs.clj b/backend/src/uxbox/services/mutations/user_attrs.clj deleted file mode 100644 index 42c9520252..0000000000 --- a/backend/src/uxbox/services/mutations/user_attrs.clj +++ /dev/null @@ -1,48 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns uxbox.services.mutations.user-attrs - (:require - [clojure.spec.alpha :as s] - [promesa.core :as p] - [uxbox.db :as db] - [uxbox.common.spec :as us] - [uxbox.services.mutations :as sm] - [uxbox.services.util :as su] - [uxbox.services.queries.user-attrs :refer [decode-row]] - [uxbox.util.blob :as blob])) - -;; --- Update - -(s/def ::user ::us/uuid) -(s/def ::key ::us/string) -(s/def ::val any?) - -(s/def ::upsert-user-attr - (s/keys :req-un [::key ::val ::user])) - -(sm/defmutation ::upsert-user-attr - [{:keys [key val user] :as params}] - (let [sql "insert into user_attrs (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-attr - (s/keys :req-un [::key ::user])) - -(sm/defmutation ::delete-user-attr - [{:keys [user key] :as params}] - (let [sql "delete from user_attrs - where user_id = $2 - and key = $1"] - (-> (db/query-one db/pool [sql key user]) - (p/then' su/constantly-nil)))) diff --git a/backend/src/uxbox/services/notifications.clj b/backend/src/uxbox/services/notifications.clj index 04b5f05bd4..89ecedf295 100644 --- a/backend/src/uxbox/services/notifications.clj +++ b/backend/src/uxbox/services/notifications.clj @@ -7,14 +7,29 @@ (ns uxbox.services.notifications "A websocket based notifications mechanism." (:require - [clojure.tools.logging :as log] [clojure.core.async :as a :refer [>! ! output message))))) - -(defn- close-all! - [{:keys [sch] :as ws}] - (a/close! sch) - (.close ^java.lang.AutoCloseable ws)) + [{:keys [out session-id profile-id] :as ws} message] + (go-try + (when-not (= (:session-id message) session-id) + (>! out message)))) (defn start-loop! - [{:keys [input output sch on-error] :as ws}] - (vu/go-try + [{:keys [in out sub] :as ws}] + (go-try (loop [] (let [timeout (a/timeout 30000) - [val port] (a/alts! [input sch timeout])] - ;; (prn "alts" val "from" (cond (= port input) "input" - ;; (= port sch) "redis" + [val port] (a/alts! [in sub timeout])] + ;; (prn "alts" val "from" (cond (= port in) "input" + ;; (= port sub) "redis" ;; :else "timeout")) (cond ;; Process message coming from connected client - (and (= port input) (not (nil? val))) + (and (= port in) (not (nil? val))) (do - (! output (encode-message {:type :ping})) + (>! out {:type :ping}) (recur)) :else nil))))) +(defn disconnect! + [conn] + (.. conn (getSession) (disconnect))) + (defn- on-subscribed - [{:keys [on-error] :as ws} sch] - (let [ws (assoc ws :sch sch)] - (a/go - (try - ( (redis/subscribe (str fid)) - (p/finally (fn [sch error] - (if error - (on-error error) - (on-subscribed ws sch))))))) + [{:keys [file-id] :as params}] + (let [in (a/chan 32) + out (a/chan 32)] + {:on-connect (fn [conn] + (let [xf (map decode-message) + sub (redis/subscribe (str file-id) xf) + ws (WebSocket. conn in out sub nil params)] + (start-rcv-loop! ws) + (a/go + (a/!! in message))) + + :on-bytes (fn [ws bytes offset len] + #_(prn "websocket" :on-bytes bytes))})) + + diff --git a/backend/src/uxbox/services/queries/colors.clj b/backend/src/uxbox/services/queries/colors.clj index 620ac7463a..e03c3276c0 100644 --- a/backend/src/uxbox/services/queries/colors.clj +++ b/backend/src/uxbox/services/queries/colors.clj @@ -14,16 +14,14 @@ [promesa.exec :as px] [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] - [uxbox.db :as db] - [uxbox.media :as media] - [uxbox.images :as images] - [uxbox.services.queries.teams :as teams] - [uxbox.services.queries :as sq] - [uxbox.services.util :as su] - [uxbox.util.blob :as blob] - [uxbox.util.data :as data] [uxbox.common.uuid :as uuid] - [vertx.core :as vc])) + [uxbox.db :as db] + [uxbox.images :as images] + [uxbox.media :as media] + [uxbox.services.queries :as sq] + [uxbox.services.queries.teams :as teams] + [uxbox.util.blob :as blob] + [uxbox.util.data :as data])) ;; --- Helpers & Specs @@ -32,15 +30,13 @@ (s/def ::team-id ::us/uuid) (s/def ::library-id (s/nilable ::us/uuid)) - - ;; --- Query: Colors Librarys (def ^:private sql:libraries "select lib.*, (select count(*) from color where library_id = lib.id) as num_colors from color_library as lib - where lib.team_id = $1 + where lib.team_id = ? and lib.deleted_at is null order by lib.created_at desc") @@ -51,8 +47,7 @@ [{:keys [profile-id team-id]}] (db/with-atomic [conn db/pool] (teams/check-read-permissions! conn profile-id team-id) - (db/query conn [sql:libraries team-id]))) - + (db/exec! conn [sql:libraries team-id]))) ;; --- Query: Color Library @@ -65,7 +60,7 @@ (sq/defquery ::color-library [{:keys [profile-id id]}] (db/with-atomic [conn db/pool] - (p/let [lib (retrieve-library conn id)] + (let [lib (retrieve-library conn id)] (teams/check-read-permissions! conn profile-id (:team-id lib)) lib))) @@ -74,14 +69,14 @@ (select count(*) from color where library_id = lib.id) as num_colors from color_library as lib where lib.deleted_at is null - and lib.id = $1") + and lib.id = ?") (defn- retrieve-library [conn id] - (-> (db/query-one conn [sql:single-library id]) - (p/then' su/raise-not-found-if-nil))) - - + (let [row (db/exec-one! conn [sql:single-library id])] + (when-not row + (ex/raise :type :not-found)) + row)) ;; --- Query: Colors (by library) @@ -93,7 +88,7 @@ (sq/defquery ::colors [{:keys [profile-id library-id] :as params}] (db/with-atomic [conn db/pool] - (p/let [lib (retrieve-library conn library-id)] + (let [lib (retrieve-library conn library-id)] (teams/check-read-permissions! conn profile-id (:team-id lib)) (retrieve-colors conn library-id)))) @@ -102,13 +97,12 @@ from color as color inner join color_library as lib on (lib.id = color.library_id) where color.deleted_at is null - and color.library_id = $1 + and color.library_id = ? order by created_at desc") (defn- retrieve-colors [conn library-id] - (db/query conn [sql:colors library-id])) - + (db/exec! conn [sql:colors library-id])) ;; --- Query: Color (by ID) @@ -122,7 +116,7 @@ (sq/defquery ::color [{:keys [profile-id id] :as params}] (db/with-atomic [conn db/pool] - (p/let [color (retrieve-color conn id)] + (let [color (retrieve-color conn id)] (teams/check-read-permissions! conn profile-id (:team-id color)) color))) @@ -132,10 +126,12 @@ from color as color inner join color_library as lib on (lib.id = color.library_id) where color.deleted_at is null - and color.id = $1 + and color.id = ? order by created_at desc") (defn retrieve-color [conn id] - (-> (db/query-one conn [sql:single-color id]) - (p/then' su/raise-not-found-if-nil))) + (let [row (db/exec-one! conn [sql:single-color id])] + (when-not row + (ex/raise :type :not-found)) + row)) diff --git a/backend/src/uxbox/services/queries/files.clj b/backend/src/uxbox/services/queries/files.clj index 00734ad3bd..55cec43551 100644 --- a/backend/src/uxbox/services/queries/files.clj +++ b/backend/src/uxbox/services/queries/files.clj @@ -16,7 +16,6 @@ [uxbox.db :as db] [uxbox.images :as images] [uxbox.services.queries :as sq] - [uxbox.services.util :as su] [uxbox.util.blob :as blob])) (declare decode-row) @@ -38,8 +37,8 @@ select p.* from project as p inner join team_profile_rel as tpr on (tpr.team_id = p.team_id) - where tpr.profile_id = $1 - and p.team_id = $2 + where tpr.profile_id = ? + and p.team_id = ? and p.deleted_at is null and (tpr.is_admin = true or tpr.is_owner = true or @@ -48,8 +47,8 @@ select p.* from project as p inner join project_profile_rel as ppr on (ppr.project_id = p.id) - where ppr.profile_id = $1 - and p.team_id = $2 + where ppr.profile_id = ? + and p.team_id = ? and p.deleted_at is null and (ppr.is_admin = true or ppr.is_owner = true or @@ -62,7 +61,7 @@ from file inner join projects as pr on (file.project_id = pr.id) left join page on (file.id = page.file_id) - where file.name ilike ('%' || $3 || '%') + where file.name ilike ('%' || ? || '%') window pages_w as (partition by file.id order by page.created_at range between unbounded preceding and unbounded following) @@ -73,8 +72,12 @@ (sq/defquery ::search-files [{:keys [profile-id team-id search-term] :as params}] - (-> (db/query db/pool [sql:search-files profile-id team-id search-term]) - (p/then (partial mapv decode-row)))) + (let [rows (db/exec! db/pool [sql:search-files + profile-id team-id + profile-id team-id + search-term])] + (mapv decode-row rows))) + ;; --- Query: Draft Files @@ -86,8 +89,8 @@ from file as f inner join file_profile_rel as fp_r on (fp_r.file_id = f.id) left join page as pg on (f.id = pg.file_id) - where fp_r.profile_id = $1 - and f.project_id = $2 + where fp_r.profile_id = ? + and f.project_id = ? and f.deleted_at is null and pg.deleted_at is null and (fp_r.is_admin = true or @@ -104,8 +107,8 @@ (sq/defquery ::files [{:keys [profile-id project-id] :as params}] - (-> (db/query db/pool [sql:files profile-id project-id]) - (p/then (partial mapv decode-row)))) + (->> (db/exec! db/pool [sql:files profile-id project-id]) + (mapv decode-row))) ;; --- Query: File Permissions @@ -114,8 +117,8 @@ fpr.is_admin, fpr.can_edit from file_profile_rel as fpr - where fpr.file_id = $1 - and fpr.profile_id = $2 + where fpr.file_id = ? + and fpr.profile_id = ? union all select tpr.is_owner, tpr.is_admin, @@ -123,28 +126,31 @@ from team_profile_rel as tpr inner join project as p on (p.team_id = tpr.team_id) inner join file as f on (p.id = f.project_id) - where f.id = $1 - and tpr.profile_id = $2 + where f.id = ? + and tpr.profile_id = ? union all select ppr.is_owner, ppr.is_admin, ppr.can_edit from project_profile_rel as ppr inner join file as f on (f.project_id = ppr.project_id) - where f.id = $1 - and ppr.profile_id = $2;") + where f.id = ? + and ppr.profile_id = ?;") (defn check-edition-permissions! [conn profile-id file-id] - (-> (db/query conn [sql:file-permissions file-id profile-id]) - (p/then' seq) - (p/then' su/raise-not-found-if-nil) - (p/then' (fn [rows] - (when-not (or (some :can-edit rows) - (some :is-admin rows) - (some :is-owner rows)) - (ex/raise :type :validation - :code :not-authorized)))))) + (let [rows (db/exec! conn [sql:file-permissions + file-id profile-id + file-id profile-id + file-id profile-id])] + (when (empty? rows) + (ex/raise :type :not-found)) + + (when-not (or (some :can-edit rows) + (some :is-admin rows) + (some :is-owner rows)) + (ex/raise :type :validation + :code :not-authorized)))) ;; --- Query: Images of the File @@ -162,15 +168,15 @@ (def ^:private sql:file-images "select fi.* from file_image as fi - where fi.file_id = $1") + where fi.file_id = ?") (defn retrieve-file-images [conn {:keys [file-id] :as params}] (let [sqlv [sql:file-images file-id] xf (comp (map #(images/resolve-urls % :path :uri)) (map #(images/resolve-urls % :thumb-path :thumb-uri)))] - (-> (db/query conn sqlv) - (p/then' #(into [] xf %))))) + (->> (db/exec! conn sqlv) + (into [] xf)))) ;; --- Query: File (By ID) @@ -179,7 +185,7 @@ array_agg(pg.id) over pages_w as pages from file as f left join page as pg on (f.id = pg.file_id) - where f.id = $1 + where f.id = ? and f.deleted_at is null and pg.deleted_at is null window pages_w as (partition by f.id order by pg.ordering @@ -190,27 +196,26 @@ "select pf.id, pf.fullname, pf.photo from profile as pf inner join file_profile_rel as fpr on (fpr.profile_id = pf.id) - where fpr.file_id = $1 + where fpr.file_id = ? union select pf.id, pf.fullname, pf.photo from profile as pf inner join team_profile_rel as tpr on (tpr.profile_id = pf.id) inner join project as p on (tpr.team_id = p.team_id) inner join file as f on (p.id = f.project_id) - where f.id = $1") + where f.id = ?") (defn retrieve-file [conn id] - (-> (db/query-one conn [sql:file id]) - (p/then' su/raise-not-found-if-nil) - (p/then' decode-row))) + (let [row (db/exec-one! conn [sql:file id])] + (when-not row + (ex/raise :type :not-found)) + (decode-row row))) (defn retrieve-file-users [conn id] - (-> (db/query conn [sql:file-users id]) - (p/then (fn [rows] - (mapv #(images/resolve-media-uris % [:photo :photo-uri]) rows))))) - + (->> (db/exec! conn [sql:file-users id id]) + (mapv #(images/resolve-media-uris % [:photo :photo-uri])))) (s/def ::file-users (s/keys :req-un [::profile-id ::id])) @@ -230,7 +235,6 @@ (check-edition-permissions! conn profile-id id) (retrieve-file conn id))) - ;; --- Helpers (defn decode-row @@ -238,4 +242,4 @@ (when row (cond-> row data (assoc :data (blob/decode data)) - pages (assoc :pages (vec (remove nil? pages)))))) + pages (assoc :pages (vec (.getArray pages)))))) diff --git a/backend/src/uxbox/services/queries/icons.clj b/backend/src/uxbox/services/queries/icons.clj index 5b0eb6b9bd..ae92594038 100644 --- a/backend/src/uxbox/services/queries/icons.clj +++ b/backend/src/uxbox/services/queries/icons.clj @@ -14,16 +14,14 @@ [promesa.exec :as px] [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] - [uxbox.db :as db] - [uxbox.media :as media] - [uxbox.images :as images] - [uxbox.services.queries.teams :as teams] - [uxbox.services.queries :as sq] - [uxbox.services.util :as su] - [uxbox.util.blob :as blob] - [uxbox.util.data :as data] [uxbox.common.uuid :as uuid] - [vertx.core :as vc])) + [uxbox.db :as db] + [uxbox.images :as images] + [uxbox.media :as media] + [uxbox.services.queries :as sq] + [uxbox.services.queries.teams :as teams] + [uxbox.util.blob :as blob] + [uxbox.util.data :as data])) ;; --- Helpers & Specs @@ -39,15 +37,13 @@ (cond-> row metadata (assoc :metadata (blob/decode metadata))))) - - ;; --- Query: Icons Librarys (def ^:private sql:libraries "select lib.*, (select count(*) from icon where library_id = lib.id) as num_icons from icon_library as lib - where lib.team_id = $1 + where lib.team_id = ? and lib.deleted_at is null order by lib.created_at desc") @@ -58,7 +54,7 @@ [{:keys [profile-id team-id]}] (db/with-atomic [conn db/pool] (teams/check-read-permissions! conn profile-id team-id) - (db/query conn [sql:libraries team-id]))) + (db/exec! conn [sql:libraries team-id]))) @@ -72,7 +68,7 @@ (sq/defquery ::icon-library [{:keys [profile-id id]}] (db/with-atomic [conn db/pool] - (p/let [lib (retrieve-library conn id)] + (let [lib (retrieve-library conn id)] (teams/check-read-permissions! conn profile-id (:team-id lib)) lib))) @@ -81,12 +77,14 @@ (select count(*) from icon where library_id = lib.id) as num_icons from icon_library as lib where lib.deleted_at is null - and lib.id = $1") + and lib.id = ?") (defn- retrieve-library [conn id] - (-> (db/query-one conn [sql:single-library id]) - (p/then' su/raise-not-found-if-nil))) + (let [row (db/exec-one! conn [sql:single-library id])] + (when-not row + (ex/raise :type :not-found)) + row)) @@ -100,22 +98,22 @@ (sq/defquery ::icons [{:keys [profile-id library-id] :as params}] (db/with-atomic [conn db/pool] - (p/let [lib (retrieve-library conn library-id)] + (let [lib (retrieve-library conn library-id)] (teams/check-read-permissions! conn profile-id (:team-id lib)) - (-> (retrieve-icons conn library-id) - (p/then' (fn [rows] (mapv decode-row rows))))))) + (->> (retrieve-icons conn library-id) + (mapv decode-row))))) (def ^:private sql:icons "select icon.* from icon as icon inner join icon_library as lib on (lib.id = icon.library_id) where icon.deleted_at is null - and icon.library_id = $1 + and icon.library_id = ? order by created_at desc") (defn- retrieve-icons [conn library-id] - (db/query conn [sql:icons library-id])) + (db/exec! conn [sql:icons library-id])) @@ -130,7 +128,7 @@ (sq/defquery ::icon [{:keys [profile-id id] :as params}] (db/with-atomic [conn db/pool] - (p/let [icon (retrieve-icon conn id)] + (let [icon (retrieve-icon conn id)] (teams/check-read-permissions! conn profile-id (:team-id icon)) (decode-row icon)))) @@ -140,11 +138,13 @@ from icon as icon inner join icon_library as lib on (lib.id = icon.library_id) where icon.deleted_at is null - and icon.id = $1 + and icon.id = ? order by created_at desc") (defn retrieve-icon [conn id] - (-> (db/query-one conn [sql:single-icon id]) - (p/then' su/raise-not-found-if-nil))) + (let [row (db/exec-one! conn [sql:single-icon id])] + (when-not row + (ex/raise :type :not-found)) + row)) diff --git a/backend/src/uxbox/services/queries/images.clj b/backend/src/uxbox/services/queries/images.clj index c6cd106a8f..d790e9bc9d 100644 --- a/backend/src/uxbox/services/queries/images.clj +++ b/backend/src/uxbox/services/queries/images.clj @@ -11,12 +11,12 @@ (:require [clojure.spec.alpha :as s] [promesa.core :as p] + [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] [uxbox.db :as db] [uxbox.images :as images] [uxbox.services.queries.teams :as teams] - [uxbox.services.queries :as sq] - [uxbox.services.util :as su])) + [uxbox.services.queries :as sq])) (s/def ::id ::us/uuid) (s/def ::name ::us/string) @@ -30,7 +30,7 @@ "select lib.*, (select count(*) from image where library_id = lib.id) as num_images from image_library as lib - where lib.team_id = $1 + where lib.team_id = ? and lib.deleted_at is null order by lib.created_at desc") @@ -41,7 +41,7 @@ [{:keys [profile-id team-id]}] (db/with-atomic [conn db/pool] (teams/check-read-permissions! conn profile-id team-id) - (db/query conn [sql:libraries team-id]))) + (db/exec! conn [sql:libraries team-id]))) ;; --- Query: Image Library @@ -54,7 +54,7 @@ (sq/defquery ::image-library [{:keys [profile-id id]}] (db/with-atomic [conn db/pool] - (p/let [lib (retrieve-library conn id)] + (let [lib (retrieve-library conn id)] (teams/check-read-permissions! conn profile-id (:team-id lib)) lib))) @@ -63,13 +63,14 @@ (select count(*) from image where library_id = lib.id) as num_images from image_library as lib where lib.deleted_at is null - and lib.id = $1") + and lib.id = ?") (defn- retrieve-library [conn id] - (-> (db/query-one conn [sql:single-library id]) - (p/then' su/raise-not-found-if-nil))) - + (let [row (db/exec-one! conn [sql:single-library id])] + (when-not row + (ex/raise :type :not-found)) + row)) ;; --- Query: Images (by library) @@ -85,13 +86,11 @@ (sq/defquery ::images [{:keys [profile-id library-id] :as params}] (db/with-atomic [conn db/pool] - (p/let [lib (retrieve-library conn library-id)] + (let [lib (retrieve-library conn library-id)] (teams/check-read-permissions! conn profile-id (:team-id lib)) - (-> (retrieve-images conn library-id) - (p/then' (fn [rows] - (->> rows - (mapv #(images/resolve-urls % :path :uri)) - (mapv #(images/resolve-urls % :thumb-path :thumb-uri))))))))) + (->> (retrieve-images conn library-id) + (mapv #(images/resolve-urls % :path :uri)) + (mapv #(images/resolve-urls % :thumb-path :thumb-uri)))))) (def ^:private sql:images @@ -99,12 +98,12 @@ from image as img inner join image_library as lib on (lib.id = img.library_id) where img.deleted_at is null - and img.library_id = $1 + and img.library_id = ? order by created_at desc") (defn- retrieve-images [conn library-id] - (db/query conn [sql:images library-id])) + (db/exec! conn [sql:images library-id])) @@ -119,7 +118,7 @@ (sq/defquery ::image [{:keys [profile-id id] :as params}] (db/with-atomic [conn db/pool] - (p/let [img (retrieve-image conn id)] + (let [img (retrieve-image conn id)] (teams/check-read-permissions! conn profile-id (:team-id img)) (-> img (images/resolve-urls :path :uri) @@ -131,13 +130,14 @@ from image as img inner join image_library as lib on (lib.id = img.library_id) where img.deleted_at is null - and img.id = $1 + and img.id = ? order by created_at desc") (defn retrieve-image [conn id] - (-> (db/query-one conn [sql:single-image id]) - (p/then' su/raise-not-found-if-nil))) - + (let [row (db/exec-one! conn [sql:single-image id])] + (when-not row + (ex/raise :type :not-found)) + row)) diff --git a/backend/src/uxbox/services/queries/pages.clj b/backend/src/uxbox/services/queries/pages.clj index 4ea47682cc..345d492de0 100644 --- a/backend/src/uxbox/services/queries/pages.clj +++ b/backend/src/uxbox/services/queries/pages.clj @@ -12,12 +12,11 @@ [clojure.spec.alpha :as s] [promesa.core :as p] [uxbox.common.spec :as us] + [uxbox.common.exceptions :as ex] [uxbox.db :as db] [uxbox.services.queries :as sq] - [uxbox.services.util :as su] [uxbox.services.queries.files :as files] - [uxbox.util.blob :as blob] - [uxbox.util.sql :as sql])) + [uxbox.util.blob :as blob])) ;; --- Helpers & Specs @@ -28,8 +27,6 @@ (s/def ::project-id ::us/uuid) (s/def ::file-id ::us/uuid) - - ;; --- Query: Pages (By File ID) (declare retrieve-pages) @@ -46,16 +43,14 @@ (def ^:private sql:pages "select p.* from page as p - where p.file_id = $1 + where p.file_id = ? and p.deleted_at is null order by p.created_at asc") (defn- retrieve-pages [conn {:keys [profile-id file-id] :as params}] - (-> (db/query conn [sql:pages file-id]) - (p/then (partial mapv decode-row)))) - - + (->> (db/exec! conn [sql:pages file-id]) + (mapv decode-row))) ;; --- Query: Single Page (By ID) @@ -66,20 +61,20 @@ (sq/defquery ::page [{:keys [profile-id id] :as params}] - (db/with-atomic [conn db/pool] - (p/let [page (retrieve-page conn id)] + (with-open [conn (db/open)] + (let [page (retrieve-page conn id)] (files/check-edition-permissions! conn profile-id (:file-id page)) page))) (def ^:private sql:page - "select p.* from page as p where id=$1") + "select p.* from page as p where id=?") (defn retrieve-page [conn id] - (-> (db/query-one conn [sql:page id]) - (p/then' su/raise-not-found-if-nil) - (p/then' decode-row))) - + (let [row (db/exec-one! conn [sql:page id])] + (when-not row + (ex/raise :type :not-found)) + (decode-row row))) ;; --- Query: Page Changes @@ -90,10 +85,10 @@ pc.changes, pc.revn from page_change as pc - where pc.page_id=$1 + where pc.page_id=? order by pc.revn asc - limit $2 - offset $3") + limit ? + offset ?") (s/def ::skip ::us/integer) @@ -104,14 +99,14 @@ (defn retrieve-page-changes [conn id skip limit] - (-> (db/query conn [sql:page-changes id limit skip]) - (p/then' #(mapv decode-row %)))) + (->> (db/exec! conn [sql:page-changes id limit skip]) + (mapv decode-row))) (sq/defquery ::page-changes [{:keys [profile-id id skip limit]}] (when *assert* - (-> (db/query db/pool [sql:page-changes id limit skip]) - (p/then' #(mapv decode-row %))))) + (-> (db/exec! db/pool [sql:page-changes id limit skip]) + (mapv decode-row)))) ;; --- Helpers diff --git a/backend/src/uxbox/services/queries/profile.clj b/backend/src/uxbox/services/queries/profile.clj index 897295aabb..55737a2a2e 100644 --- a/backend/src/uxbox/services/queries/profile.clj +++ b/backend/src/uxbox/services/queries/profile.clj @@ -7,14 +7,11 @@ (ns uxbox.services.queries.profile (:require [clojure.spec.alpha :as s] - [promesa.core :as p] - [promesa.exec :as px] [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] [uxbox.db :as db] [uxbox.images :as images] [uxbox.services.queries :as sq] - [uxbox.services.util :as su] [uxbox.common.uuid :as uuid] [uxbox.util.blob :as blob])) @@ -43,7 +40,7 @@ (sq/defquery ::profile [{:keys [profile-id] :as params}] (if profile-id - (db/with-atomic [conn db/pool] + (with-open [conn (db/open)] (retrieve-profile conn profile-id)) {:id uuid/zero :fullname "Anonymous User"})) @@ -57,41 +54,43 @@ "select t.id from team as t inner join team_profile_rel as tpr on (tpr.team_id = t.id) - where tpr.profile_id = $1 + where tpr.profile_id = ? and tpr.is_owner is true and t.is_default is true union all select p.id from project as p inner join project_profile_rel as tpr on (tpr.project_id = p.id) - where tpr.profile_id = $1 + where tpr.profile_id = ? and tpr.is_owner is true and p.is_default is true") (defn retrieve-additional-data [conn id] - (-> (db/query conn [sql:default-team-and-project id]) - (p/then' (fn [[team project]] - {:default-team-id (:id team) - :default-project-id (:id project)})))) + (let [[team project] (db/exec! conn [sql:default-team-and-project id id])] + {:default-team-id (:id team) + :default-project-id (:id project)})) (defn retrieve-profile-data [conn id] - (let [sql "select * from profile where id=$1 and deleted_at is null"] - (db/query-one conn [sql id]))) + (let [sql "select * from profile where id=? and deleted_at is null"] + (db/exec-one! conn [sql id]))) (defn retrieve-profile [conn id] - (p/let [prof (-> (retrieve-profile-data conn id) - (p/then' su/raise-not-found-if-nil) - (p/then' strip-private-attrs) - (p/then' #(images/resolve-media-uris % [:photo :photo-uri]))) - addt (retrieve-additional-data conn id)] - (merge prof addt))) + (let [profile (some-> (retrieve-profile-data conn id) + (images/resolve-urls :photo :photo-uri) + (strip-private-attrs) + (merge (retrieve-additional-data conn id)))] + (when (nil? profile) + (ex/raise :type :not-found + :hint "Object doest not exists.")) + + profile)) ;; --- Attrs Helpers (defn strip-private-attrs "Only selects a publicy visible profile attrs." - [profile] - (select-keys profile [:id :fullname :lang :email :created-at :photo :theme :photo-uri])) + [o] + (select-keys o [:id :fullname :lang :email :created-at :photo :theme :photo-uri])) diff --git a/backend/src/uxbox/services/queries/projects.clj b/backend/src/uxbox/services/queries/projects.clj index cfe5eb7d9e..6636c4b929 100644 --- a/backend/src/uxbox/services/queries/projects.clj +++ b/backend/src/uxbox/services/queries/projects.clj @@ -11,7 +11,6 @@ [uxbox.common.spec :as us] [uxbox.db :as db] [uxbox.services.queries :as sq] - [uxbox.services.util :as su] [uxbox.util.blob :as blob])) (declare decode-row) @@ -32,7 +31,7 @@ and deleted_at is null) as file_count from project as p inner join team_profile_rel as tpr on (tpr.team_id = p.team_id) - where tpr.profile_id = $1 + where tpr.profile_id = ? and p.deleted_at is null and (tpr.is_admin = true or tpr.is_owner = true or @@ -44,7 +43,7 @@ and deleted_at is null) from project as p inner join project_profile_rel as ppr on (ppr.project_id = p.id) - where ppr.profile_id = $1 + where ppr.profile_id = ? and p.deleted_at is null and (ppr.is_admin = true or ppr.is_owner = true or @@ -52,15 +51,15 @@ ) select * from projects - where team_id = $2 + where team_id = ? order by modified_at desc") (def ^:private sql:project-by-id "select p.* from project as p inner join project_profile_rel as ppr on (ppr.project_id = p.id) - where ppr.profile_id = $1 - and p.id = $2 + where ppr.profile_id = ? + and p.id = ? and p.deleted_at is null and (ppr.is_admin = true or ppr.is_owner = true or @@ -78,11 +77,11 @@ (defn retrieve-projects [conn profile-id team-id] - (db/query conn [sql:projects profile-id team-id])) + (db/exec! conn [sql:projects profile-id profile-id team-id])) (defn retrieve-project [conn profile-id id] - (db/query-one conn [sql:project-by-id profile-id id])) + (db/exec-one! conn [sql:project-by-id profile-id id])) (sq/defquery ::projects-by-team [{:keys [profile-id team-id]}] diff --git a/backend/src/uxbox/services/queries/recent_files.clj b/backend/src/uxbox/services/queries/recent_files.clj index bf23a25b96..3f660a3730 100644 --- a/backend/src/uxbox/services/queries/recent_files.clj +++ b/backend/src/uxbox/services/queries/recent_files.clj @@ -25,8 +25,8 @@ from file as f inner join file_profile_rel as fp_r on (fp_r.file_id = f.id) left join page as pg on (f.id = pg.file_id) - where fp_r.profile_id = $1 - and f.project_id = $2 + where fp_r.profile_id = ? + and f.project_id = ? and f.deleted_at is null and pg.deleted_at is null and (fp_r.is_admin = true or @@ -38,10 +38,11 @@ order by f.modified_at desc limit 5") -(defn recent-by-project [profile-id project] +(defn recent-by-project + [profile-id project] (let [project-id (:id project)] - (-> (db/query db/pool [sql:project-files-recent profile-id project-id]) - (p/then (partial mapv decode-row))))) + (->> (db/exec! db/pool [sql:project-files-recent profile-id project-id]) + (mapv decode-row)))) (s/def ::team-id ::us/uuid) (s/def ::profile-id ::us/uuid) @@ -51,8 +52,9 @@ (sq/defquery ::recent-files [{:keys [profile-id team-id]}] - (-> (retrieve-projects db/pool profile-id team-id) - ;; Retrieve for each proyect the 5 more recent files - (p/then #(p/all (map (partial recent-by-project profile-id) %))) - ;; Change the structure so it's a map with project-id as keys - (p/then #(->> % (flatten) (group-by :project-id))))) + (->> (retrieve-projects db/pool profile-id team-id) + ;; Retrieve for each proyect the 5 more recent files + (map (partial recent-by-project profile-id)) + ;; Change the structure so it's a map with project-id as keys + (flatten) + (group-by :project-id))) diff --git a/backend/src/uxbox/services/queries/teams.clj b/backend/src/uxbox/services/queries/teams.clj index 229c4444fc..43a2d48440 100644 --- a/backend/src/uxbox/services/queries/teams.clj +++ b/backend/src/uxbox/services/queries/teams.clj @@ -10,15 +10,12 @@ (ns uxbox.services.queries.teams (:require [clojure.spec.alpha :as s] - [promesa.core :as p] - [uxbox.db :as db] [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] + [uxbox.common.uuid :as uuid] + [uxbox.db :as db] [uxbox.services.queries :as sq] - [uxbox.services.util :as su] - [uxbox.util.blob :as blob] - [uxbox.common.uuid :as uuid])) - + [uxbox.util.blob :as blob])) ;; --- Team Edition Permissions @@ -27,27 +24,25 @@ tpr.is_admin, tpr.can_edit from team_profile_rel as tpr - where tpr.profile_id = $1 - and tpr.team_id = $2") + where tpr.profile_id = ? + and tpr.team_id = ?") (defn check-edition-permissions! [conn profile-id team-id] - (-> (db/query-one conn [sql:team-permissions profile-id team-id]) - (p/then' (fn [row] - (when-not (or (:can-edit row) - (:is-admin row) - (:is-owner row)) - (ex/raise :type :validation - :code :not-authorized)))))) + (let [row (db/exec-one! conn [sql:team-permissions profile-id team-id])] + (when-not (or (:can-edit row) + (:is-admin row) + (:is-owner row)) + (ex/raise :type :validation + :code :not-authorized)))) (defn check-read-permissions! [conn profile-id team-id] - (-> (db/query-one conn [sql:team-permissions profile-id team-id]) - (p/then' (fn [row] - (when-not (or (:can-edit row) - (:is-admin row) - (:is-owner row) - ;; We can read global-project owned items - (= team-id #uuid "00000000-0000-0000-0000-000000000000")) - (ex/raise :type :validation - :code :not-authorized)))))) + (let [row (db/exec-one! conn [sql:team-permissions profile-id team-id])] + (when-not (or (:can-edit row) + (:is-admin row) + (:is-owner row) + ;; We can read global-project owned items + (= team-id #uuid "00000000-0000-0000-0000-000000000000")) + (ex/raise :type :validation + :code :not-authorized)))) diff --git a/backend/src/uxbox/services/queries/user_attrs.clj b/backend/src/uxbox/services/queries/user_attrs.clj deleted file mode 100644 index 8a86c73fea..0000000000 --- a/backend/src/uxbox/services/queries/user_attrs.clj +++ /dev/null @@ -1,37 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns uxbox.services.queries.user-attrs - (:require - [clojure.spec.alpha :as s] - [promesa.core :as p] - [uxbox.common.spec :as us] - [uxbox.db :as db] - [uxbox.services.queries :as sq] - [uxbox.services.util :as su] - [uxbox.util.blob :as blob])) - -(defn decode-row - [{:keys [val] :as row}] - (when row - (cond-> row - val (assoc :val (blob/decode val))))) - -(s/def ::key ::us/string) -(s/def ::user ::us/uuid) - -(s/def ::user-attr - (s/keys :req-un [::key ::user])) - -(sq/defquery ::user-attr - [{:keys [key user]}] - (let [sql "select kv.* - from user_attrs as kv - where kv.user_id = $2 - and kv.key = $1"] - (-> (db/query-one db/pool [sql key user]) - (p/then' su/raise-not-found-if-nil) - (p/then' decode-row)))) diff --git a/backend/src/uxbox/services/queries/viewer.clj b/backend/src/uxbox/services/queries/viewer.clj index 6fe5360892..60113809cc 100644 --- a/backend/src/uxbox/services/queries/viewer.clj +++ b/backend/src/uxbox/services/queries/viewer.clj @@ -14,17 +14,15 @@ [promesa.exec :as px] [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] - [uxbox.db :as db] - [uxbox.media :as media] - [uxbox.images :as images] - [uxbox.services.queries.pages :as pages] - [uxbox.services.queries.files :as files] - [uxbox.services.queries :as sq] - [uxbox.services.util :as su] - [uxbox.util.blob :as blob] - [uxbox.util.data :as data] [uxbox.common.uuid :as uuid] - [vertx.core :as vc])) + [uxbox.db :as db] + [uxbox.images :as images] + [uxbox.media :as media] + [uxbox.services.queries :as sq] + [uxbox.services.queries.files :as files] + [uxbox.services.queries.pages :as pages] + [uxbox.util.blob :as blob] + [uxbox.util.data :as data])) ;; --- Helpers & Specs @@ -37,12 +35,12 @@ sql:project "select p.id, p.name from project as p - where p.id = $1 + where p.id = ? and p.deleted_at is null") (defn- retrieve-project [conn id] - (db/query-one conn [sql:project id])) + (db/exec-one! conn [sql:project id])) (s/def ::share-token ::us/string) (s/def ::viewer-bundle @@ -52,10 +50,10 @@ (sq/defquery ::viewer-bundle [{:keys [profile-id page-id share-token] :as params}] (db/with-atomic [conn db/pool] - (p/let [page (pages/retrieve-page conn page-id) - file (files/retrieve-file conn (:file-id page)) - images (files/retrieve-file-images conn page) - project (retrieve-project conn (:project-id file))] + (let [page (pages/retrieve-page conn page-id) + file (files/retrieve-file conn (:file-id page)) + images (files/retrieve-file-images conn page) + project (retrieve-project conn (:project-id file))] (if (string? share-token) (when (not= share-token (:share-token page)) (ex/raise :type :validation diff --git a/backend/src/uxbox/services/util.clj b/backend/src/uxbox/services/util.clj deleted file mode 100644 index f2b4cde8cc..0000000000 --- a/backend/src/uxbox/services/util.clj +++ /dev/null @@ -1,29 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns uxbox.services.util - (:require - [clojure.tools.logging :as log] - [cuerdas.core :as str] - [vertx.util :as vu] - [uxbox.core :refer [system]] - [uxbox.common.exceptions :as ex] - [uxbox.common.uuid :as uuid] - [uxbox.util.dispatcher :as uds])) - -(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] - (->> (vu/current-context system) - (vu/handle-on-context p))) diff --git a/backend/src/uxbox/tasks.clj b/backend/src/uxbox/tasks.clj index c6a0963a3f..f96db0cf77 100644 --- a/backend/src/uxbox/tasks.clj +++ b/backend/src/uxbox/tasks.clj @@ -13,19 +13,15 @@ [clojure.spec.alpha :as s] [clojure.tools.logging :as log] [mount.core :as mount :refer [defstate]] - [promesa.core :as p] [uxbox.common.spec :as us] [uxbox.config :as cfg] - [uxbox.core :refer [system]] [uxbox.db :as db] [uxbox.tasks.sendmail] [uxbox.tasks.remove-media] [uxbox.tasks.delete-profile] [uxbox.tasks.delete-object] [uxbox.tasks.impl :as impl] - [uxbox.util.time :as dt] - [vertx.core :as vc] - [vertx.timers :as vt])) + [uxbox.util.time :as dt])) ;; --- Public API @@ -48,10 +44,17 @@ "remove-media" #'uxbox.tasks.remove-media/handler "sendmail" #'uxbox.tasks.sendmail/handler}) -(defstate tasks-worker - :start (as-> (impl/worker-verticle {:tasks tasks}) $$ - (vc/deploy! system $$ {:instances 1}) - (deref $$))) +(defstate worker + :start (impl/start-worker! {:tasks tasks}) + :stop (impl/stop! worker)) + +;; (defstate scheduler +;; :start (impl/start-scheduler! tasks) +;; :stop (impl/stop! tasks-worker)) + +;; :start (as-> (impl/worker-verticle {:tasks tasks}) $$ +;; (vc/deploy! system $$ {:instances 1}) +;; (deref $$))) ;; (def ^:private schedule ;; [{:id "every 1 hour" diff --git a/backend/src/uxbox/tasks/delete_object.clj b/backend/src/uxbox/tasks/delete_object.clj index 52812c69f3..652c3f916e 100644 --- a/backend/src/uxbox/tasks/delete_object.clj +++ b/backend/src/uxbox/tasks/delete_object.clj @@ -12,13 +12,11 @@ (:require [clojure.spec.alpha :as s] [clojure.tools.logging :as log] - [promesa.core :as p] [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] [uxbox.db :as db] [uxbox.media :as media] - [uxbox.util.storage :as ust] - [vertx.util :as vu])) + [uxbox.util.storage :as ust])) (s/def ::type keyword?) (s/def ::id ::us/uuid) @@ -40,42 +38,42 @@ (defmethod handle-deletion :image [conn {:keys [id] :as props}] - (let [sql "delete from image where id=$1 and deleted_at is not null"] - (db/query-one conn [sql id]))) + (let [sql "delete from image where id=? and deleted_at is not null"] + (db/exec-one! conn [sql id]))) (defmethod handle-deletion :image-collection [conn {:keys [id] :as props}] (let [sql "delete from image_collection - where id=$1 and deleted_at is not null"] - (db/query-one conn [sql id]))) + where id=? and deleted_at is not null"] + (db/exec-one! conn [sql id]))) (defmethod handle-deletion :icon [conn {:keys [id] :as props}] - (let [sql "delete from icon where id=$1 and deleted_at is not null"] - (db/query-one conn [sql id]))) + (let [sql "delete from icon where id=? and deleted_at is not null"] + (db/exec-one! conn [sql id]))) (defmethod handle-deletion :icon-collection [conn {:keys [id] :as props}] (let [sql "delete from icon_collection - where id=$1 and deleted_at is not null"] - (db/query-one conn [sql id]))) + where id=? and deleted_at is not null"] + (db/exec-one! conn [sql id]))) (defmethod handle-deletion :file [conn {:keys [id] :as props}] - (let [sql "delete from file where id=$1 and deleted_at is not null"] - (db/query-one conn [sql id]))) + (let [sql "delete from file where id=? and deleted_at is not null"] + (db/exec-one! conn [sql id]))) (defmethod handle-deletion :file-image [conn {:keys [id] :as props}] - (let [sql "delete from file_image where id=$1 and deleted_at is not null"] - (db/query-one conn [sql id]))) + (let [sql "delete from file_image where id=? and deleted_at is not null"] + (db/exec-one! conn [sql id]))) (defmethod handle-deletion :page [conn {:keys [id] :as props}] - (let [sql "delete from page where id=$1 and deleted_at is not null"] - (db/query-one conn [sql id]))) + (let [sql "delete from page where id=? and deleted_at is not null"] + (db/exec-one! conn [sql id]))) (defmethod handle-deletion :page-version [conn {:keys [id] :as props}] - (let [sql "delete from page_version where id=$1 and deleted_at is not null"] - (db/query-one conn [sql id]))) + (let [sql "delete from page_version where id=? and deleted_at is not null"] + (db/exec-one! conn [sql id]))) diff --git a/backend/src/uxbox/tasks/delete_profile.clj b/backend/src/uxbox/tasks/delete_profile.clj index 557428310f..74bb6339c9 100644 --- a/backend/src/uxbox/tasks/delete_profile.clj +++ b/backend/src/uxbox/tasks/delete_profile.clj @@ -12,15 +12,12 @@ (:require [clojure.spec.alpha :as s] [clojure.tools.logging :as log] - [promesa.core :as p] [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] [uxbox.db :as db] [uxbox.media :as media] - [uxbox.util.storage :as ust] - [vertx.util :as vu])) + [uxbox.util.storage :as ust])) -(declare select-profile) (declare delete-profile-data) (declare delete-teams) (declare delete-files) @@ -34,38 +31,32 @@ [{:keys [props] :as task}] (us/verify ::props props) (db/with-atomic [conn db/pool] - (-> (select-profile conn (:profile-id props)) - (p/then (fn [profile] - (if (or (:is-demo profile) - (not (nil? (:deleted-at profile)))) - (delete-profile-data conn (:id profile)) - (log/warn "Profile " (:id profile) - "does not match constraints for deletion"))))))) + (let [id (:profile-id props) + profile (db/get-by-id conn :profile id {:for-update true})] + (if (or (:is-demo profile) + (not (nil? (:deleted-at profile)))) + (delete-profile-data conn (:id profile)) + (log/warn "Profile " (:id profile) + "does not match constraints for deletion"))))) (defn- delete-profile-data [conn profile-id] (log/info "Proceding to delete all data related to profile" profile-id) - (p/do! - (delete-teams conn profile-id) - (delete-files conn profile-id) - (delete-profile conn profile-id))) + (delete-teams conn profile-id) + (delete-files conn profile-id) + (delete-profile conn profile-id)) (def ^:private sql:select-profile "select id, is_demo, deleted_at from profile - where id=$1 for update") - -(defn- select-profile - [conn profile-id] - (db/query-one conn [sql:select-profile profile-id])) - + where id=? for update") (def ^:private sql:remove-owned-teams "with teams as ( select distinct tpr.team_id as id from team_profile_rel as tpr - where tpr.profile_id = $1 + where tpr.profile_id = ? and tpr.is_owner is true ), to_delete_teams as ( select tpr.team_id as id @@ -80,8 +71,7 @@ (defn- delete-teams [conn profile-id] - (-> (db/query-one conn [sql:remove-owned-teams profile-id]) - (p/then' (constantly nil)))) + (db/exec-one! conn [sql:remove-owned-teams profile-id])) (def ^:private sql:remove-owned-files "with files_to_delete as ( @@ -89,7 +79,7 @@ fpr.file_id as id from file_profile_rel as fpr inner join file as f on (fpr.file_id = f.id) - where fpr.profile_id = $1 + where fpr.profile_id = ? and fpr.is_owner is true and f.project_id is null ) @@ -99,12 +89,8 @@ (defn- delete-files [conn profile-id] - (-> (db/query-one conn [sql:remove-owned-files profile-id]) - (p/then' (constantly nil)))) + (db/exec-one! conn [sql:remove-owned-files profile-id])) (defn delete-profile [conn profile-id] - (let [sql "delete from profile where id=$1"] - (-> (db/query conn [sql profile-id]) - (p/then' (constantly profile-id))))) - + (db/delete! conn :profile {:id profile-id})) diff --git a/backend/src/uxbox/tasks/impl.clj b/backend/src/uxbox/tasks/impl.clj index 1f91f427ca..dc61c4f143 100644 --- a/backend/src/uxbox/tasks/impl.clj +++ b/backend/src/uxbox/tasks/impl.clj @@ -10,24 +10,24 @@ (ns uxbox.tasks.impl "Async tasks implementation." (:require + [clojure.core.async :as a] [clojure.spec.alpha :as s] [clojure.tools.logging :as log] - [mount.core :as mount :refer [defstate]] - [promesa.core :as p] [uxbox.common.spec :as us] + [uxbox.common.uuid :as uuid] [uxbox.config :as cfg] - [uxbox.core :refer [system]] [uxbox.db :as db] [uxbox.util.blob :as blob] - [uxbox.util.time :as tm] - [vertx.core :as vc] - [vertx.util :as vu] - [vertx.timers :as vt]) + [uxbox.util.time :as dt]) (:import java.time.Duration java.time.Instant java.util.Date)) +(defrecord Worker [stop] + java.lang.AutoCloseable + (close [_] (a/close! stop))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Tasks ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -40,42 +40,42 @@ (def ^:private sql:mark-as-retry "update task set scheduled_at = clock_timestamp() + '5 seconds'::interval, - error = $1, + error = ?, status = 'retry', retry_num = retry_num + 1 - where id = $2;") + where id = ?") (defn- reschedule [conn task error] (let [explain (ex-message error) sqlv [sql:mark-as-retry explain (:id task)]] - (-> (db/query-one conn sqlv) - (p/then' (constantly nil))))) + (db/exec-one! conn sqlv) + nil)) (def ^:private sql:mark-as-failed "update task set scheduled_at = clock_timestamp() + '5 seconds'::interval, - error = $1, + error = ?, status = 'failed' - where id = $2;") + where id = ?;") (defn- mark-as-failed [conn task error] (let [explain (ex-message error) sqlv [sql:mark-as-failed explain (:id task)]] - (-> (db/query-one conn sqlv) - (p/then' (constantly nil))))) + (db/exec-one! conn sqlv) + nil)) (def ^:private sql:mark-as-completed "update task set completed_at = clock_timestamp(), status = 'completed' - where id = $1") + where id = ?") (defn- mark-as-completed [conn task] - (-> (db/query-one conn [sql:mark-as-completed (:id task)]) - (p/then' (constantly nil)))) + (db/exec-one! conn [sql:mark-as-completed (:id task)]) + nil) (defn- handle-task [tasks {:keys [name] :as item}] @@ -89,8 +89,8 @@ (def ^:private sql:select-next-task "select * from task as t where t.scheduled_at <= now() - and t.queue = $1 - and (t.status = 'new' or (t.status = 'retry' and t.retry_num <= $2)) + and t.queue = ? + and (t.status = 'new' or (t.status = 'retry' and t.retry_num <= ?)) order by t.scheduled_at limit 1 for update skip locked") @@ -108,124 +108,130 @@ (with-out-str (.printStackTrace ^Throwable err (java.io.PrintWriter. *out*))))) -(defn- event-loop +(defn- event-loop-fn [{:keys [tasks] :as options}] (let [queue (:queue options "default") max-retries (:max-retries options 3)] (db/with-atomic [conn db/pool] - (-> (db/query-one conn [sql:select-next-task queue max-retries]) - (p/then decode-task-row) - (p/then (fn [item] - (when item - (log/info "Execute task" (:name item)) - (-> (p/do! (handle-task tasks item)) - (p/handle (fn [v e] - (if e - (do - (log-task-error item e) - (if (>= (:retry-num item) max-retries) - (mark-as-failed conn item e) - (reschedule conn item e))) - (mark-as-completed conn item)))) - (p/then' (constantly ::handled)))))))))) + (let [item (-> (db/exec-one! conn [sql:select-next-task queue max-retries]) + (decode-task-row))] + (when item + (log/info "Execute task" (:name item)) + (try + (handle-task tasks item) + (mark-as-completed conn item) + ::handled + (catch Throwable e + (log-task-error item e) + (if (>= (:retry-num item) max-retries) + (mark-as-failed conn item e) + (reschedule conn item e))))))))) -(defn- event-loop-handler +(defn- start-worker-eventloop! [options] - (let [counter (::counter options 1) - mbs (:max-batch-size options 10)] - (-> (event-loop options) - (p/then (fn [result] - (when (and (= result ::handled) - (> mbs counter)) - (event-loop-handler (assoc options ::counter (inc counter))))))))) - -(def ^:private sql:insert-new-task - "insert into task (name, props, queue, scheduled_at) - values ($1, $2, $3, clock_timestamp()+cast($4::text as interval)) - returning id") + (let [stop (::stop options) + mbs (:max-batch-size options 10)] + (a/go-loop [] + (let [timeout (a/timeout 5000) + [val port] (a/alts! [stop timeout])] + (when (= port timeout) + (a/ mbs cnt)) + (recur (inc 1) + (event-loop-fn options)))))) + (recur)))))) (defn- duration->pginterval [^Duration d] (->> (/ (.toMillis d) 1000.0) (format "%s seconds"))) -(defn- on-worker-start - [ctx {:keys [tasks] :as options}] - (vt/schedule! ctx (assoc options - ::vt/fn #'event-loop-handler - ::vt/delay 5000 - ::vt/repeat true))) +(defn start-worker! + [options] + (let [stop (a/chan)] + (a/go + (a/Worker stop))) + +(defn stop! + [worker] + (.close ^java.lang.AutoCloseable worker)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Scheduled Tasks ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(def ^:privatr sql:upsert-scheduled-task - "insert into scheduled_task (id, cron_expr) - values ($1, $2) - on conflict (id) - do update set cron_expr=$2") +;; (def ^:privatr sql:upsert-scheduled-task +;; "insert into scheduled_task (id, cron_expr) +;; values ($1, $2) +;; on conflict (id) +;; do update set cron_expr=$2") -(defn- synchronize-schedule-item - [conn {:keys [id cron]}] - (-> (db/query-one conn [sql:upsert-scheduled-task id (str cron)]) - (p/then' (constantly nil)))) +;; (defn- synchronize-schedule-item +;; [conn {:keys [id cron]}] +;; (-> (db/query-one conn [sql:upsert-scheduled-task id (str cron)]) +;; (p/then' (constantly nil)))) -(defn- synchronize-schedule - [schedule] - (db/with-atomic [conn db/pool] - (p/run! (partial synchronize-schedule-item conn) schedule))) +;; (defn- synchronize-schedule +;; [schedule] +;; (db/with-atomic [conn db/pool] +;; (p/run! (partial synchronize-schedule-item conn) schedule))) -(def ^:private sql:lock-scheduled-task - "select id from scheduled_task where id=$1 for update skip locked") +;; (def ^:private sql:lock-scheduled-task +;; "select id from scheduled_task where id=$1 for update skip locked") -(declare schedule-task) +;; (declare schedule-task) -(defn- log-scheduled-task-error - [item err] - (log/error "Unhandled exception on scheduled task '" (:id item) "' \n" - (with-out-str - (.printStackTrace ^Throwable err (java.io.PrintWriter. *out*))))) +;; (defn- log-scheduled-task-error +;; [item err] +;; (log/error "Unhandled exception on scheduled task '" (:id item) "' \n" +;; (with-out-str +;; (.printStackTrace ^Throwable err (java.io.PrintWriter. *out*))))) -(defn- execute-scheduled-task - [{:keys [id cron] :as stask}] - (db/with-atomic [conn db/pool] - ;; First we try to lock the task in the database, if locking us - ;; successful, then we execute the scheduled task; if locking is - ;; not possible (because other instance is already locked id) we - ;; just skip it and schedule to be executed in the next slot. - (-> (db/query-one conn [sql:lock-scheduled-task id]) - (p/then (fn [result] - (when result - (-> (p/do! ((:fn stask) stask)) - (p/catch (fn [e] - (log-scheduled-task-error stask e) - nil)))))) - (p/finally (fn [v e] - (-> (vu/current-context) - (schedule-task stask))))))) -(defn ms-until-valid - [cron] - (s/assert tm/cron? cron) - (let [^Instant now (tm/now) - ^Instant next (tm/next-valid-instant-from cron now) - ^Duration duration (Duration/between now next)] - (.toMillis duration))) +;; (defn- execute-scheduled-task +;; [{:keys [id cron] :as stask}] +;; (db/with-atomic [conn db/pool] +;; ;; First we try to lock the task in the database, if locking us +;; ;; successful, then we execute the scheduled task; if locking is +;; ;; not possible (because other instance is already locked id) we +;; ;; just skip it and schedule to be executed in the next slot. +;; (-> (db/query-one conn [sql:lock-scheduled-task id]) +;; (p/then (fn [result] +;; (when result +;; (-> (p/do! ((:fn stask) stask)) +;; (p/catch (fn [e] +;; (log-scheduled-task-error stask e) +;; nil)))))) +;; (p/finally (fn [v e] +;; (-> (vu/current-context) +;; (schedule-task stask))))))) +;; (defn ms-until-valid +;; [cron] +;; (s/assert dt/cron? cron) +;; (let [^Instant now (dt/now) +;; ^Instant next (dt/next-valid-instant-from cron now) +;; ^Duration duration (Duration/between now next)] +;; (.toMillis duration))) -(defn- schedule-task - [ctx {:keys [cron] :as stask}] - (let [ms (ms-until-valid cron)] - (vt/schedule! ctx (assoc stask - :ctx ctx - ::vt/once true - ::vt/delay ms - ::vt/fn execute-scheduled-task)))) +;; (defn- schedule-task +;; [ctx {:keys [cron] :as stask}] +;; (let [ms (ms-until-valid cron)] +;; (vt/schedule! ctx (assoc stask +;; :ctx ctx +;; ::vt/once true +;; ::vt/delay ms +;; ::vt/fn execute-scheduled-task)))) -(defn- on-scheduler-start - [ctx {:keys [schedule] :as options}] - (-> (synchronize-schedule schedule) - (p/then' (fn [_] - (run! #(schedule-task ctx %) schedule))))) +;; (defn- on-scheduler-start +;; [ctx {:keys [schedule] :as options}] +;; (-> (synchronize-schedule schedule) +;; (p/then' (fn [_] +;; (run! #(schedule-task ctx %) schedule))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Public API @@ -233,64 +239,71 @@ ;; --- Worker Verticle -(s/def ::callable (s/or :fn fn? :var var?)) -(s/def ::max-batch-size ::us/integer) -(s/def ::max-retries ::us/integer) -(s/def ::tasks (s/map-of string? ::callable)) +;; (s/def ::callable (s/or :fn fn? :var var?)) +;; (s/def ::max-batch-size ::us/integer) +;; (s/def ::max-retries ::us/integer) +;; (s/def ::tasks (s/map-of string? ::callable)) -(s/def ::worker-verticle-options - (s/keys :req-un [::tasks] - :opt-un [::queue ::max-batch-size])) +;; (s/def ::worker-verticle-options +;; (s/keys :req-un [::tasks] +;; :opt-un [::queue ::max-batch-size])) -(defn worker-verticle - [options] - (s/assert ::worker-verticle-options options) - (let [on-start #(on-worker-start % options)] - (vc/verticle {:on-start on-start}))) +;; (defn worker-verticle +;; [options] +;; (s/assert ::worker-verticle-options options) +;; (let [on-start #(on-worker-start % options)] +;; (vc/verticle {:on-start on-start}))) ;; --- Scheduler Verticle -(s/def ::id string?) -(s/def ::cron tm/cron?) -(s/def ::fn ::callable) -(s/def ::props (s/nilable map?)) +;; (s/def ::id string?) +;; (s/def ::cron dt/cron?) +;; (s/def ::fn ::callable) +;; (s/def ::props (s/nilable map?)) -(s/def ::scheduled-task - (s/keys :req-un [::id ::cron ::fn] - :opt-un [::props])) +;; (s/def ::scheduled-task +;; (s/keys :req-un [::id ::cron ::fn] +;; :opt-un [::props])) -(s/def ::schedule (s/coll-of ::scheduled-task)) +;; (s/def ::schedule (s/coll-of ::scheduled-task)) -(s/def ::scheduler-verticle-options - (s/keys :opt-un [::schedule])) +;; (s/def ::scheduler-verticle-options +;; (s/keys :opt-un [::schedule])) -(defn scheduler-verticle - [options] - (s/assert ::scheduler-verticle-options options) - (let [on-start #(on-scheduler-start % options)] - (vc/verticle {:on-start on-start}))) +;; (defn scheduler-verticle +;; [options] +;; (s/assert ::scheduler-verticle-options options) +;; (let [on-start #(on-scheduler-start % options)] +;; (vc/verticle {:on-start on-start}))) ;; --- Schedule API (s/def ::name ::us/string) (s/def ::delay (s/or :int ::us/integer - :duration tm/duration?)) + :duration dt/duration?)) (s/def ::queue ::us/string) (s/def ::task-options (s/keys :req-un [::name] :opt-un [::delay ::props ::queue])) +(def ^:private sql:insert-new-task + "insert into task (id, name, props, queue, scheduled_at) + values (?, ?, ?, ?, clock_timestamp()+cast(?::text as interval)) + returning id") + (defn schedule! [conn {:keys [name delay props queue key] :or {delay 0 props {} queue "default"} :as options}] (us/verify ::task-options options) - (let [duration (tm/duration delay) + (let [duration (dt/duration delay) pginterval (duration->pginterval duration) - props (blob/encode props)] + props (blob/encode props) + id (uuid/next)] (log/info "Schedule task" name ;; "with props" (pr-str props) "to be executed in" (str duration)) - (-> (db/query-one conn [sql:insert-new-task name props queue pginterval]) - (p/then' (fn [task] (:id task)))))) + (db/exec-one! conn [sql:insert-new-task + id name props queue pginterval]) + id)) diff --git a/backend/src/uxbox/tasks/remove_media.clj b/backend/src/uxbox/tasks/remove_media.clj index 5f5b1510bc..3553e95073 100644 --- a/backend/src/uxbox/tasks/remove_media.clj +++ b/backend/src/uxbox/tasks/remove_media.clj @@ -15,8 +15,7 @@ [uxbox.common.exceptions :as ex] [uxbox.common.spec :as us] [uxbox.media :as media] - [uxbox.util.storage :as ust] - [vertx.util :as vu])) + [uxbox.util.storage :as ust])) (s/def ::path ::us/not-empty-string) (s/def ::props @@ -25,8 +24,7 @@ (defn handler [{:keys [props] :as task}] (us/verify ::props props) - (vu/blocking - (when (ust/exists? media/media-storage (:path props)) - (ust/delete! media/media-storage (:path props)) - (log/debug "Media " (:path props) " removed.")))) + (when (ust/exists? media/media-storage (:path props)) + (ust/delete! media/media-storage (:path props)) + (log/debug "Media " (:path props) " removed."))) diff --git a/backend/src/uxbox/tasks/sendmail.clj b/backend/src/uxbox/tasks/sendmail.clj index 95cb3f4375..2fbc9119da 100644 --- a/backend/src/uxbox/tasks/sendmail.clj +++ b/backend/src/uxbox/tasks/sendmail.clj @@ -12,12 +12,10 @@ [clojure.data.json :as json] [clojure.tools.logging :as log] [postal.core :as postal] - [promesa.core :as p] [uxbox.common.data :as d] [uxbox.common.exceptions :as ex] [uxbox.config :as cfg] - [uxbox.util.http :as http] - [vertx.util :as vu])) + [uxbox.util.http :as http])) (defmulti sendmail (fn [config email] (:sendmail-backend config))) @@ -49,21 +47,17 @@ headers {"Authorization" (str "Bearer " apikey) "Content-Type" "application/json"} body (json/write-str params)] - (-> (http/send! {:method :post - :headers headers - :uri "https://api.sendgrid.com/v3/mail/send" - :body body}) - (p/handle - (fn [response error] - (cond - error - (log/error "Error on sending email to sendgrid:" (pr-str error)) - (= 202 (:status response)) - nil - :else - (log/error "Unexpected status from sendgrid:" (pr-str response)))))))) + (try + (let [response (http/send! {:method :post + :headers headers + :uri "https://api.sendgrid.com/v3/mail/send" + :body body})] + (when-not (= 202 (:status response)) + (log/error "Unexpected status from sendgrid:" (pr-str response)))) + (catch Throwable error + (log/error "Error on sending email to sendgrid:" (pr-str error)))))) (defn- get-smtp-config [config] @@ -87,14 +81,13 @@ (defmethod sendmail "smtp" [config email] - (vu/blocking - (let [config (get-smtp-config config) - email (email->postal email) - result (postal/send-message config email)] - (when (not= (:error result) :SUCCESS) - (ex/raise :type :sendmail-error - :code :email-not-sent - :context result))))) + (let [config (get-smtp-config config) + email (email->postal email) + result (postal/send-message config email)] + (when (not= (:error result) :SUCCESS) + (ex/raise :type :sendmail-error + :code :email-not-sent + :context result)))) (defn handler {:uxbox.tasks/name "sendmail"} diff --git a/backend/src/uxbox/util/blob.clj b/backend/src/uxbox/util/blob.clj index a1953555c3..509075c942 100644 --- a/backend/src/uxbox/util/blob.clj +++ b/backend/src/uxbox/util/blob.clj @@ -12,7 +12,6 @@ page data, page options and txlog payload storage." (:require [uxbox.util.transit :as t]) (:import - io.vertx.core.buffer.Buffer java.io.ByteArrayInputStream java.io.ByteArrayOutputStream java.io.DataInputStream @@ -28,9 +27,6 @@ (Class/forName "[B") (->bytes [data] data) - Buffer - (->bytes [data] (.getBytes ^Buffer data)) - String (->bytes [data] (.getBytes ^String data "UTF-8"))) @@ -49,8 +45,7 @@ (.writeShort dos (short 1)) ;; version number (.writeInt dos (int data-len)) (.write dos ^bytes cdata (int 0) clen) - (-> (.toByteArray baos) - (t/bytes->buffer))))) + (.toByteArray baos)))) (declare decode-v1) diff --git a/backend/src/uxbox/util/dispatcher.clj b/backend/src/uxbox/util/dispatcher.clj index 47daa31234..a187a67338 100644 --- a/backend/src/uxbox/util/dispatcher.clj +++ b/backend/src/uxbox/util/dispatcher.clj @@ -9,7 +9,6 @@ (:refer-clojure :exclude [defmethod]) (:require [clojure.spec.alpha :as s] - [promesa.core :as p] [expound.alpha :as expound] [uxbox.common.exceptions :as ex]) (:import @@ -127,14 +126,10 @@ (with-meta (fn [params] (try - (-> (handler params) - (p/catch' (fn [error] - (ex/raise :type :service-error - :name (:spec mdata) - :cause error)))) + (handler params) (catch Throwable error - (p/rejected (ex/error :type :service-error - :name (:spec mdata) - :cause error))))) + (ex/raise :type :service-error + :name (:spec mdata) + :cause error)))) (assoc mdata ::wrap-error true)))) diff --git a/backend/src/uxbox/util/http.clj b/backend/src/uxbox/util/http.clj index f5b8edfa63..4d1ce8c741 100644 --- a/backend/src/uxbox/util/http.clj +++ b/backend/src/uxbox/util/http.clj @@ -16,4 +16,4 @@ (defn send! [req] - (http/send-async req {:client @default-client :as :string})) + (http/send req {:client @default-client :as :string})) diff --git a/backend/src/uxbox/util/migrations.clj b/backend/src/uxbox/util/migrations.clj index ebaedd4cac..29c58d0cf9 100644 --- a/backend/src/uxbox/util/migrations.clj +++ b/backend/src/uxbox/util/migrations.clj @@ -10,8 +10,8 @@ [clojure.java.io :as io] [clojure.spec.alpha :as s] [cuerdas.core :as str] - [promesa.core :as p] - [uxbox.util.pgsql :as pg])) + [next.jdbc :as jdbc])) + (s/def ::name string?) (s/def ::step (s/keys :req-un [::name ::desc ::fn])) @@ -24,75 +24,57 @@ (defn- registered? "Check if concrete migration is already registred." [pool modname stepname] - (let [sql "select * from migrations where module=$1 and step=$2"] - (-> (pg/query pool [sql modname stepname]) - (p/then' (fn [rows] - (pos? (count rows))))))) + (let [sql "select * from migrations where module=? and step=?" + rows (jdbc/execute! pool [sql modname stepname])] + (pos? (count rows)))) (defn- register! "Register a concrete migration into local migrations database." [pool modname stepname] - (let [sql "insert into migrations (module, step) values ($1, $2)"] - (-> (pg/query pool [sql modname stepname]) - (p/then' (constantly nil))))) + (let [sql "insert into migrations (module, step) values (?, ?)"] + (jdbc/execute! pool [sql modname stepname]) + nil)) +(defn- impl-migrate-single + [pool modname {:keys [name] :as migration}] + (letfn [(execute [] + (register! pool modname name) + ((:fn migration) pool))] + (when-not (registered? pool modname (:name migration)) + (log/info (str/format "applying migration %s/%s" modname name)) + (register! pool modname name) + ((:fn migration) pool)))) -(defn- setup! +(defn- impl-migrate + [conn migrations {:keys [fake] :or {fake false}}] + (s/assert ::migrations migrations) + (let [mname (:name migrations) + steps (:steps migrations)] + (jdbc/with-transaction [conn conn] + (run! #(impl-migrate-single conn mname %) steps)))) + +(defprotocol IMigrationContext + (-migrate [_ migration options])) + +;; --- Public Api +(defn setup! "Initialize the database if it is not initialized." - [pool] + [conn] (let [sql (str "create table if not exists migrations (" " module text," " step text," " created_at timestamp DEFAULT current_timestamp," " unique(module, step)" ");")] - (-> (pg/query pool sql) - (p/then' (constantly nil))))) + (jdbc/execute! conn [sql]) + nil)) -(defn- impl-migrate-single - [pool modname {:keys [name] :as migration}] - (letfn [(execute [] - (p/do! (register! pool modname name) - ((:fn migration) pool)))] - (-> (registered? pool modname (:name migration)) - (p/then (fn [registered?] - (when-not registered? - (log/info (str/format "applying migration %s/%s" modname name)) - (execute))))))) - -(defn- impl-migrate - [pool migrations {:keys [fake] :or {fake false}}] - (s/assert ::migrations migrations) - (let [mname (:name migrations) - steps (:steps migrations)] - ;; (println (str/format "Applying migrations for `%s`:" mname)) - (pg/with-atomic [conn pool] - (p/run! #(impl-migrate-single conn mname %) steps)))) - -(defprotocol IMigrationContext - (-migrate [_ migration options])) - -;; --- Public Api - -(defn context - "Create new instance of migration context." - ([pool] (context pool nil)) - ([pool opts] - @(setup! pool) - (reify - java.lang.AutoCloseable - (close [_] #_(.close pool)) - - IMigrationContext - (-migrate [_ migration options] - (impl-migrate pool migration options))))) - -(defn migrate +(defn migrate! "Main entry point for apply a migration." - ([ctx migrations] - (migrate ctx migrations nil)) - ([ctx migrations options] - (-migrate ctx migrations options))) + ([conn migrations] + (migrate! conn migrations nil)) + ([conn migrations options] + (impl-migrate conn migrations options))) (defn resource "Helper for setup migration functions @@ -101,5 +83,5 @@ [path] (fn [pool] (let [sql (slurp (io/resource path))] - (-> (pg/query pool sql) - (p/then' (constantly true)))))) + (jdbc/execute! pool [sql]) + true))) diff --git a/backend/src/uxbox/util/pgsql.clj b/backend/src/uxbox/util/pgsql.clj deleted file mode 100644 index eb3bf2d958..0000000000 --- a/backend/src/uxbox/util/pgsql.clj +++ /dev/null @@ -1,162 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns uxbox.util.pgsql - "Asynchronous posgresql client." - (:require - [promesa.core :as p]) - (:import - clojure.lang.IDeref - java.util.function.Supplier - java.lang.ThreadLocal - io.vertx.core.Vertx - io.vertx.core.Handler - io.vertx.core.AsyncResult - io.vertx.core.buffer.Buffer - io.vertx.pgclient.PgPool - io.vertx.pgclient.PgConnection - io.vertx.sqlclient.impl.ArrayTuple - io.vertx.sqlclient.SqlClient - io.vertx.sqlclient.RowSet - io.vertx.sqlclient.Row - io.vertx.sqlclient.Tuple - io.vertx.sqlclient.Transaction - io.vertx.sqlclient.PoolOptions)) - -(declare impl-execute) -(declare impl-query) -(declare impl-handler) -(declare impl-transact) -(declare seqable->tuple) - -;; --- Public Api - -(defn vertx? - [v] - (instance? Vertx v)) - -(defn pool? - [v] - (instance? PgPool v)) - -(defn bytes->buffer - [data] - (Buffer/buffer ^bytes data)) - -(defn pool - ([uri] (pool uri {})) - ([uri {:keys [system max-size] :or {max-size 5}}] - (let [^PoolOptions poptions (PoolOptions.)] - (when max-size (.setMaxSize poptions max-size)) - (if (vertx? system) - (PgPool/pool ^Vertx system ^String uri poptions) - (PgPool/pool ^String uri poptions))))) - -(defn tl-pool - "Thread local based connection pool." - ([uri] (tl-pool uri {})) - ([uri options] - (let [state (ThreadLocal/withInitial (reify Supplier - (get [_] - (pool uri options))))] - (reify IDeref - (deref [_] - (.get state)))))) - -(defn query - ([conn sqlv] (query conn sqlv {})) - ([conn sqlv opts] - (cond - (vector? sqlv) - (impl-query conn (first sqlv) (rest sqlv) opts) - - (string? sqlv) - (impl-query conn sqlv nil opts) - - :else - (throw (ex-info "Invalid arguments" {:sqlv sqlv}))))) - -(defn query-one - [& args] - (p/map first (apply query args))) - -(defn row->map - [^Row row] - (reduce (fn [acc index] - (let [cname (.getColumnName row index)] - (if-some [value (.getValue row ^int index)] - (assoc acc cname value) - acc))) - {} - (range (.size row)))) - -(defmacro with-atomic - [[bsym psym] & body] - `(impl-transact ~psym (fn [c#] (let [~bsym c#] ~@body)))) - -;; --- Implementation - -(defn- seqable->tuple - [v] - (let [res (ArrayTuple. (count v))] - (run! #(.addValue res %) v) - res)) - -(defn- impl-handler - [resolve reject] - (reify Handler - (handle [_ ar] - (if (.failed ^AsyncResult ar) - (reject (.cause ^AsyncResult ar)) - (resolve (.result ^AsyncResult ar)))))) - -(defn- impl-execute - [^SqlClient conn ^String sql params] - (if (seq params) - (p/create #(.preparedQuery conn sql - ^Tuple (seqable->tuple params) - ^Handler (impl-handler %1 %2))) - (p/create #(.query conn sql - ^Handler (impl-handler %1 %2))))) - -(defn- impl-query - [^SqlClient conn ^String sql params {:keys [xfm] :as opts}] - (let [conn (if (instance? IDeref conn) @conn conn)] - (-> (impl-execute conn sql params) - (p/catch (fn [err] - (p/rejected err))) - (p/then' (fn [rows] - (if xfm - (into [] xfm rows) - (into [] (map vec) rows))))))) - -(defn impl-transact - [pool f] - (let [pool (if (instance? IDeref pool) @pool pool)] - (letfn [(commit [^Transaction tx] - (p/create #(.commit tx (impl-handler %1 %2)))) - (rollback [^Transaction tx] - (p/create #(.rollback tx (impl-handler %1 %2)))) - (on-connect [^PgConnection conn] - (let [tx (.begin conn) - df (p/deferred)] - (-> (f conn) - (p/finally (fn [v e] - (if e - (-> (rollback tx) - (p/finally (fn [& args] - (.close conn) - (p/reject! df e)))) - (-> (commit tx) - (p/finally (fn [_ e'] - (.close conn) - (if e' - (p/reject! df e') - (p/resolve! df v))))))))) - df))] - (-> (p/create #(.getConnection ^PgPool pool (impl-handler %1 %2))) - (p/bind on-connect))))) - diff --git a/backend/src/uxbox/util/redis.clj b/backend/src/uxbox/util/redis.clj index af59bf98f1..dfc3e98a12 100644 --- a/backend/src/uxbox/util/redis.clj +++ b/backend/src/uxbox/util/redis.clj @@ -40,15 +40,14 @@ (defn connect [client] (let [^RedisURI uri (:uri client) - ^RedisClient client (:client client)] - (-> (.connectAsync client StringCodec/UTF8 uri) - (p/then' (fn [^StatefulRedisConnection conn] - (->Connection (.async conn))))))) + ^RedisClient client (:client client) + ^StatefulRedisConnection conn (.connect client StringCodec/UTF8 uri)] + (->Connection (.async conn)))) (defn- impl-subscribe - [^String topic ^StatefulRedisPubSubConnection conn] - (let [cmd (.async conn) - output (a/chan 1 (filter string?)) + [^String topic xf ^StatefulRedisPubSubConnection conn] + (let [cmd (.sync conn) + output (a/chan 1 (comp (filter string?) xf)) buffer (a/chan (a/sliding-buffer 64)) sub (reify RedisPubSubListener (message [it pattern channel message]) @@ -74,15 +73,17 @@ (when (.isOpen conn) (.close conn)))))) - (-> (.subscribe ^RedisPubSubAsyncCommands cmd (into-array String [topic])) - (p/then' (constantly output))))) + (.subscribe ^RedisPubSubCommands cmd (into-array String [topic])) + output)) (defn subscribe - [client topic] - (let [^RedisURI uri (:uri client) - ^RedisClient client (:client client)] - (-> (.connectPubSubAsync client StringCodec/UTF8 uri) - (p/then (partial impl-subscribe topic))))) + ([client topic] + (subscribe client topic (map identity))) + ([client topic xf] + (let [^RedisURI uri (:uri client) + ^RedisClient client (:client client)] + (->> (.connectPubSub client StringCodec/UTF8 uri) + (impl-subscribe topic xf))))) (defn- resolve-to-bool [v] diff --git a/backend/src/uxbox/util/transit.clj b/backend/src/uxbox/util/transit.clj index bc76d4a3d5..0ce8a012cc 100644 --- a/backend/src/uxbox/util/transit.clj +++ b/backend/src/uxbox/util/transit.clj @@ -11,7 +11,6 @@ [uxbox.util.time :as dt] [uxbox.util.data :as data]) (:import - io.vertx.core.buffer.Buffer java.io.ByteArrayInputStream java.io.ByteArrayOutputStream java.io.File)) @@ -85,12 +84,3 @@ ([^bytes data, ^String encoding] (String. data encoding))) -(defn bytes->buffer - [^bytes data] - (Buffer/buffer data)) - -(defn buffer->bytes - [^Buffer data] - (.getBytes data)) - - diff --git a/backend/tests/uxbox/tests/helpers.clj b/backend/tests/uxbox/tests/helpers.clj index aa20deb719..c980a12b39 100644 --- a/backend/tests/uxbox/tests/helpers.clj +++ b/backend/tests/uxbox/tests/helpers.clj @@ -1,7 +1,9 @@ (ns uxbox.tests.helpers (:require + [clojure.java.io :as io] [clojure.spec.alpha :as s] [promesa.core :as p] + [datoteka.core :as fs] [cuerdas.core :as str] [mount.core :as mount] [environ.core :refer [env]] @@ -21,27 +23,34 @@ [uxbox.util.blob :as blob] [uxbox.common.uuid :as uuid] [uxbox.util.storage :as ust] - [uxbox.config :as cfg] - [vertx.util :as vu])) + [uxbox.config :as cfg]) + (:import org.postgresql.ds.PGSimpleDataSource)) -(def ^:dynamic *context* nil) +(defn testing-datasource + [] + (doto (PGSimpleDataSource.) + (.setServerName "postgres") + (.setDatabaseName "uxbox_test") + (.setUser "uxbox") + (.setPassword "uxbox"))) (defn state-init [next] (let [config (cfg/read-test-config env)] - (-> (mount/only #{#'uxbox.config/config - #'uxbox.core/system - #'uxbox.db/pool - #'uxbox.services.init/query-services - #'uxbox.services.init/mutation-services - #'uxbox.migrations/migrations - #'uxbox.media/assets-storage - #'uxbox.media/media-storage}) - (mount/swap {#'uxbox.config/config config}) - (mount/start)) (try - (binding [*context* (vu/get-or-create-context uxbox.core/system)] - (next)) + ;; (Class/forName "org.postgresql.Driver") + (let [pool (testing-datasource)] + (-> (mount/only #{#'uxbox.config/config + #'uxbox.db/pool + #'uxbox.services.init/query-services + #'uxbox.services.init/mutation-services + #'uxbox.migrations/migrations + #'uxbox.media/assets-storage + #'uxbox.media/media-storage}) + (mount/swap {#'uxbox.config/config config + #'uxbox.db/pool pool}) + (mount/start))) + (next) (finally (mount/stop))))) @@ -51,14 +60,12 @@ " FROM information_schema.tables " " WHERE table_schema = 'public' " " AND table_name != 'migrations';")] - - @(db/with-atomic [conn db/pool] - (-> (db/query conn sql) - (p/then #(map :table-name %)) - (p/then (fn [result] - (db/query-one conn (str "TRUNCATE " - (apply str (interpose ", " result)) - " CASCADE;"))))))) + (db/with-atomic [conn db/pool] + (let [result (->> (db/exec! conn [sql]) + (map :table-name))] + (db/exec! conn [(str "TRUNCATE " + (apply str (interpose ", " result)) + " CASCADE;")])))) (try (next) (finally @@ -142,15 +149,8 @@ (defmacro try-on! [expr] `(try - (let [d# (p/deferred)] - (->> #(p/finally (p/do! ~expr) - (fn [v# e#] - (if e# - (p/reject! d# e#) - (p/resolve! d# v#)))) - (vu/run-on-context! *context*)) - (array-map :error nil - :result (deref d#))) + {:error nil + :result ~expr} (catch Exception e# {:error (handle-error e#) :result nil}))) @@ -211,3 +211,11 @@ [e code] (let [data (ex-data e)] (= code (:code data)))) + +(defn tempfile + [source] + (let [rsc (io/resource source) + tmp (fs/create-tempfile)] + (io/copy (io/file rsc) + (io/file tmp)) + tmp)) diff --git a/backend/tests/uxbox/tests/test_emails.clj b/backend/tests/uxbox/tests/test_emails.clj index 248ddb71c7..790f1b2754 100644 --- a/backend/tests/uxbox/tests/test_emails.clj +++ b/backend/tests/uxbox/tests/test_emails.clj @@ -20,10 +20,10 @@ (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)) + (t/is (contains? result :content)) (t/is (contains? result :to)) (t/is (contains? result :reply-to)) - (t/is (vector? (:body result))))) + (t/is (vector? (:content result))))) ;; (t/deftest email-sending-and-sendmail-job ;; (let [res @(emails/send! emails/register {:to "example@uxbox.io" :name "foo"})] diff --git a/backend/tests/uxbox/tests/test_services_colors.clj b/backend/tests/uxbox/tests/test_services_colors.clj index a9808ca57a..ac13bce194 100644 --- a/backend/tests/uxbox/tests/test_services_colors.clj +++ b/backend/tests/uxbox/tests/test_services_colors.clj @@ -1,31 +1,28 @@ (ns uxbox.tests.test-services-colors (:require [clojure.test :as t] - [promesa.core :as p] [datoteka.core :as fs] [clojure.java.io :as io] [uxbox.db :as db] - [uxbox.core :refer [system]] [uxbox.services.mutations :as sm] [uxbox.services.queries :as sq] [uxbox.util.storage :as ust] [uxbox.common.uuid :as uuid] - [uxbox.tests.helpers :as th] - [vertx.core :as vc])) + [uxbox.tests.helpers :as th])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) (t/deftest color-libraries-crud - (let [id (uuid/next) - prof @(th/create-profile db/pool 2) - team (:default-team prof)] + (let [id (uuid/next) + prof (th/create-profile db/pool 2) + team-id (:default-team prof)] (t/testing "create library" (let [data {::sm/type :create-color-library :name "sample library" :profile-id (:id prof) - :team-id (:id team) + :team-id team-id :id id} out (th/try-on! (sm/handle data))] @@ -34,7 +31,7 @@ (let [result (:result out)] (t/is (= id (:id result))) - (t/is (= (:id team) (:team-id result))) + (t/is (= team-id (:team-id result))) (t/is (= (:name data) (:name result)))))) (t/testing "update library" @@ -44,23 +41,11 @@ :id id} out (th/try-on! (sm/handle data))] - ;; (th/print-result! out) - (t/is (nil? (:error out))) - (t/is (nil? (:result out))))) - - (t/testing "query libraries" - (let [data {::sq/type :color-libraries - :profile-id (:id prof) - :team-id (:id team)} - out (th/try-on! (sq/handle data))] - ;; (th/print-result! out) (t/is (nil? (:error out))) (let [result (:result out)] - (t/is (= 1 (count result))) - (t/is (= (:id team) (get-in result [0 :team-id]))) - (t/is (= "renamed" (get-in result [0 :name])))))) + (t/is (= "renamed" (get-in result [:name])))))) (t/testing "delete library" (let [data {::sm/type :delete-color-library @@ -76,7 +61,7 @@ (t/testing "query libraries after delete" (let [data {::sq/type :color-libraries :profile-id (:id prof) - :team-id (:id team)} + :team-id team-id} out (th/try-on! (sq/handle data))] ;; (th/print-result! out) @@ -86,9 +71,9 @@ )) (t/deftest colors-crud - (let [prof @(th/create-profile db/pool 1) - team (:default-team prof) - coll @(th/create-color-library db/pool (:id team) 1) + (let [prof (th/create-profile db/pool 1) + team-id (:default-team prof) + coll (th/create-color-library db/pool team-id 1) color-id (uuid/next)] (t/testing "upload color to library" diff --git a/backend/tests/uxbox/tests/test_services_files.clj b/backend/tests/uxbox/tests/test_services_files.clj index 9e391c3e3b..4f0abc0ed8 100644 --- a/backend/tests/uxbox/tests/test_services_files.clj +++ b/backend/tests/uxbox/tests/test_services_files.clj @@ -1,42 +1,40 @@ (ns uxbox.tests.test-services-files (:require [clojure.test :as t] - [promesa.core :as p] [datoteka.core :as fs] + [uxbox.common.uuid :as uuid] [uxbox.db :as db] - [uxbox.media :as media] - [uxbox.core :refer [system]] [uxbox.http :as http] + [uxbox.media :as media] [uxbox.services.mutations :as sm] [uxbox.services.queries :as sq] [uxbox.tests.helpers :as th] - [uxbox.util.storage :as ust] - [uxbox.common.uuid :as uuid] - [vertx.util :as vu])) + [uxbox.util.storage :as ust])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) (t/deftest files-crud - (let [prof @(th/create-profile db/pool 1) - team (:default-team prof) - proj (:default-project prof) + (let [prof (th/create-profile db/pool 1) + team-id (:default-team prof) + proj-id (:default-project prof) file-id (uuid/next) page-id (uuid/next)] (t/testing "create file" (let [data {::sm/type :create-file :profile-id (:id prof) - :project-id (:id proj) + :project-id proj-id :id file-id :name "test file"} out (th/try-on! (sm/handle data))] + ;; (th/print-result! out) (t/is (nil? (:error out))) (let [result (:result out)] (t/is (= (:name data) (:name result))) - (t/is (= (:id proj) (:project-id result)))))) + (t/is (= proj-id (:project-id result)))))) (t/testing "rename file" (let [data {::sm/type :rename-file @@ -52,7 +50,7 @@ (t/testing "query files" (let [data {::sq/type :files - :project-id (:id proj) + :project-id proj-id :profile-id (:id prof)} out (th/try-on! (sq/handle data))] @@ -65,23 +63,6 @@ (t/is (= "new name" (get-in result [0 :name]))) (t/is (= 1 (count (get-in result [0 :pages]))))))) - (t/testing "query single file with users" - (let [data {::sq/type :file-with-users - :profile-id (:id prof) - :id file-id} - out (th/try-on! (sq/handle data))] - - ;; (th/print-result! out) - (t/is (nil? (:error out))) - - (let [result (:result out)] - (t/is (= file-id (:id result))) - (t/is (= "new name" (:name result))) - (t/is (vector? (:pages result))) - (t/is (= 1 (count (:pages result)))) - (t/is (vector? (:users result))) - (t/is (= (:id prof) (get-in result [:users 0 :id])))))) - (t/testing "query single file without users" (let [data {::sq/type :file :profile-id (:id prof) @@ -128,7 +109,7 @@ (t/testing "query list files after delete" (let [data {::sq/type :files - :project-id (:id proj) + :project-id proj-id :profile-id (:id prof)} out (th/try-on! (sq/handle data))] @@ -140,15 +121,15 @@ )) (t/deftest file-images-crud - (let [prof @(th/create-profile db/pool 1) - team (:default-team prof) - proj (:default-project prof) - file @(th/create-file db/pool (:id prof) (:id proj) 1)] + (let [prof (th/create-profile db/pool 1) + team-id (:default-team prof) + proj-id (:default-project prof) + file (th/create-file db/pool (:id prof) proj-id 1)] (t/testing "upload file image" - (let [content {:name "sample.jpg" - :path "tests/uxbox/tests/_files/sample.jpg" - :mtype "image/jpeg" + (let [content {:filename "sample.jpg" + :tempfile (th/tempfile "uxbox/tests/_files/sample.jpg") + :content-type "image/jpeg" :size 312043} data {::sm/type :upload-file-image :profile-id (:id prof) @@ -168,7 +149,7 @@ (t/is (= (:name data) (:name result))) (t/is (= (:width data) (:width result))) (t/is (= (:height data) (:height result))) - (t/is (= (:mtype content) (:mtype result))) + (t/is (= (:content-type content) (:mtype result))) (t/is (string? (:path result))) (t/is (string? (:uri result))) @@ -176,12 +157,12 @@ (t/is (string? (:thumb-uri result)))))) (t/testing "import from library" - (let [lib @(th/create-image-library db/pool (:id team) 1) + (let [lib (th/create-image-library db/pool team-id 1) image-id (uuid/next) - content {:name "sample.jpg" - :path "tests/uxbox/tests/_files/sample.jpg" - :mtype "image/jpeg" + content {:filename "sample.jpg" + :tempfile (th/tempfile "uxbox/tests/_files/sample.jpg") + :content-type "image/jpeg" :size 312043} data {::sm/type :upload-image diff --git a/backend/tests/uxbox/tests/test_services_icons.clj b/backend/tests/uxbox/tests/test_services_icons.clj index 8d83dac4ee..0faad20ec3 100644 --- a/backend/tests/uxbox/tests/test_services_icons.clj +++ b/backend/tests/uxbox/tests/test_services_icons.clj @@ -1,31 +1,28 @@ (ns uxbox.tests.test-services-icons (:require - [clojure.test :as t] - [promesa.core :as p] - [datoteka.core :as fs] [clojure.java.io :as io] + [clojure.test :as t] + [datoteka.core :as fs] + [uxbox.common.uuid :as uuid] [uxbox.db :as db] - [uxbox.core :refer [system]] [uxbox.services.mutations :as sm] [uxbox.services.queries :as sq] - [uxbox.util.storage :as ust] - [uxbox.common.uuid :as uuid] [uxbox.tests.helpers :as th] - [vertx.core :as vc])) + [uxbox.util.storage :as ust])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) (t/deftest icon-libraries-crud - (let [id (uuid/next) - prof @(th/create-profile db/pool 2) - team (:default-team prof)] + (let [id (uuid/next) + prof (th/create-profile db/pool 2) + team-id (:default-team prof)] (t/testing "create library" (let [data {::sm/type :create-icon-library :name "sample library" :profile-id (:id prof) - :team-id (:id team) + :team-id team-id :id id} out (th/try-on! (sm/handle data))] @@ -34,25 +31,28 @@ (let [result (:result out)] (t/is (= id (:id result))) - (t/is (= (:id team) (:team-id result))) + (t/is (= team-id (:team-id result))) (t/is (= (:name data) (:name result)))))) (t/testing "rename library" (let [data {::sm/type :rename-icon-library :name "renamed" :profile-id (:id prof) - :team-id (:id team) + :team-id team-id :id id} out (th/try-on! (sm/handle data))] ;; (th/print-result! out) (t/is (nil? (:error out))) - (t/is (nil? (:result out))))) + + (let [result (:result out)] + (t/is (= id (:id result))) + (t/is (= "renamed" (:name result)))))) (t/testing "query libraries" (let [data {::sq/type :icon-libraries :profile-id (:id prof) - :team-id (:id team)} + :team-id team-id} out (th/try-on! (sq/handle data))] ;; (th/print-result! out) @@ -77,7 +77,7 @@ (t/testing "query libraries after delete" (let [data {::sq/type :icon-libraries :profile-id (:id prof) - :team-id (:id team)} + :team-id team-id} out (th/try-on! (sq/handle data))] ;; (th/print-result! out) @@ -88,9 +88,9 @@ )) (t/deftest icons-crud - (let [prof @(th/create-profile db/pool 1) - team (:default-team prof) - coll @(th/create-icon-library db/pool (:id team) 1) + (let [prof (th/create-profile db/pool 1) + team-id (:default-team prof) + coll (th/create-icon-library db/pool team-id 1) icon-id (uuid/next)] (t/testing "upload icon to library" diff --git a/backend/tests/uxbox/tests/test_services_images.clj b/backend/tests/uxbox/tests/test_services_images.clj index d6a8f79453..9869d718ab 100644 --- a/backend/tests/uxbox/tests/test_services_images.clj +++ b/backend/tests/uxbox/tests/test_services_images.clj @@ -1,31 +1,27 @@ (ns uxbox.tests.test-services-images (:require [clojure.test :as t] - [promesa.core :as p] [datoteka.core :as fs] - [clojure.java.io :as io] + [uxbox.common.uuid :as uuid] [uxbox.db :as db] - [uxbox.core :refer [system]] [uxbox.services.mutations :as sm] [uxbox.services.queries :as sq] - [uxbox.util.storage :as ust] - [uxbox.common.uuid :as uuid] [uxbox.tests.helpers :as th] - [vertx.core :as vc])) + [uxbox.util.storage :as ust])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) (t/deftest image-libraries-crud (let [id (uuid/next) - prof @(th/create-profile db/pool 2) - team (:default-team prof)] + prof (th/create-profile db/pool 2) + team-id (:default-team prof)] (t/testing "create library" (let [data {::sm/type :create-image-library :name "sample library" :profile-id (:id prof) - :team-id (:id team) + :team-id team-id :id id} out (th/try-on! (sm/handle data))] @@ -33,7 +29,7 @@ (t/is (nil? (:error out))) (let [result (:result out)] - (t/is (= (:id team) (:team-id result))) + (t/is (= team-id (:team-id result))) (t/is (= (:name data) (:name result)))))) (t/testing "rename library" @@ -45,7 +41,10 @@ ;; (th/print-result! out) (t/is (nil? (:error out))) - (t/is (nil? (:result out))))) + + (let [result (:result out)] + (t/is (= id (:id result))) + (t/is (= "renamed" (:name result)))))) (t/testing "query single library" (let [data {::sq/type :image-library @@ -62,7 +61,7 @@ (t/testing "query libraries" (let [data {::sq/type :image-libraries - :team-id (:id team) + :team-id team-id :profile-id (:id prof)} out (th/try-on! (sq/handle data))] @@ -87,7 +86,7 @@ (t/testing "query libraries after delete" (let [data {::sq/type :image-libraries :profile-id (:id prof) - :team-id (:id team)} + :team-id team-id} out (th/try-on! (sq/handle data))] ;; (th/print-result! out) @@ -96,15 +95,15 @@ )) (t/deftest images-crud - (let [prof @(th/create-profile db/pool 1) - team (:default-team prof) - lib @(th/create-image-library db/pool (:id team) 1) + (let [prof (th/create-profile db/pool 1) + team-id (:default-team prof) + lib (th/create-image-library db/pool team-id 1) image-id (uuid/next)] (t/testing "upload image to library" - (let [content {:name "sample.jpg" - :path "tests/uxbox/tests/_files/sample.jpg" - :mtype "image/jpeg" + (let [content {:filename "sample.jpg" + :tempfile (th/tempfile "uxbox/tests/_files/sample.jpg") + :content-type "image/jpeg" :size 312043} data {::sm/type :upload-image :id image-id @@ -127,7 +126,8 @@ (t/is (string? (get-in out [:result :path]))) (t/is (string? (get-in out [:result :thumb-path]))) (t/is (string? (get-in out [:result :uri]))) - (t/is (string? (get-in out [:result :thumb-uri]))))) + (t/is (string? (get-in out [:result :thumb-uri]))) + )) (t/testing "list images by library" (let [data {::sq/type :images diff --git a/backend/tests/uxbox/tests/test_services_pages.clj b/backend/tests/uxbox/tests/test_services_pages.clj index 17991163bf..b61508da97 100644 --- a/backend/tests/uxbox/tests/test_services_pages.clj +++ b/backend/tests/uxbox/tests/test_services_pages.clj @@ -15,10 +15,10 @@ (t/use-fixtures :each th/database-reset) (t/deftest pages-crud - (let [prof @(th/create-profile db/pool 1) - team (:default-team prof) - proj (:default-project prof) - file @(th/create-file db/pool (:id prof) (:id proj) 1) + (let [prof (th/create-profile db/pool 1) + team-id (:default-team prof) + proj-id (:default-project prof) + file (th/create-file db/pool (:id prof) proj-id 1) page-id (uuid/next)] (t/testing "create page" @@ -48,7 +48,7 @@ :id page-id} out (th/try-on! (sm/handle data))] - (th/print-result! out) + ;; (th/print-result! out) (t/is (nil? (:error out))) (let [result (:result out)] (t/is (string? (:share-token result)))))) @@ -93,10 +93,10 @@ )) (t/deftest update-page-data - (let [prof @(th/create-profile db/pool 1) - team (:default-team prof) - proj (:default-project prof) - file @(th/create-file db/pool (:id prof) (:id proj) 1) + (let [prof (th/create-profile db/pool 1) + team-id (:default-team prof) + proj-id (:default-project prof) + file (th/create-file db/pool (:id prof) proj-id 1) page-id (uuid/next)] (t/testing "create empty page" @@ -167,11 +167,11 @@ (t/deftest update-page-data-2 - (let [prof @(th/create-profile db/pool 1) - team (:default-team prof) - proj (:default-project prof) - file @(th/create-file db/pool (:id prof) (:id proj) 1) - page @(th/create-page db/pool (:id prof) (:id file) 1)] + (let [prof (th/create-profile db/pool 1) + team-id (:default-team prof) + proj-id (:default-project prof) + file (th/create-file db/pool (:id prof) proj-id 1) + page (th/create-page db/pool (:id prof) (:id file) 1)] (t/testing "lagging changes" (let [sid (uuid/next) data {::sm/type :update-page diff --git a/backend/tests/uxbox/tests/test_services_profile.clj b/backend/tests/uxbox/tests/test_services_profile.clj index a2b8a66bb8..6a3fec76fe 100644 --- a/backend/tests/uxbox/tests/test_services_profile.clj +++ b/backend/tests/uxbox/tests/test_services_profile.clj @@ -12,7 +12,6 @@ [clojure.test :as t] [clojure.java.io :as io] [mockery.core :refer [with-mocks]] - [promesa.core :as p] [cuerdas.core :as str] [datoteka.core :as fs] [uxbox.db :as db] @@ -25,7 +24,7 @@ (t/use-fixtures :each th/database-reset) (t/deftest profile-login - (let [profile @(th/create-profile db/pool 1)] + (let [profile (th/create-profile db/pool 1)] (t/testing "failed" (let [event {::sm/type :login :email "profile1.test@nodomain.com" @@ -55,8 +54,7 @@ (t/deftest profile-query-and-manipulation - (let [profile @(th/create-profile db/pool 1)] - + (let [profile (th/create-profile db/pool 1)] (t/testing "query profile" (let [data {::sq/type :profile :profile-id (:id profile)} @@ -74,124 +72,133 @@ (let [data (assoc profile ::sm/type :update-profile :fullname "Full Name" - :name "profile222" :lang "en" :theme "dark") out (th/try-on! (sm/handle data))] - ;; (th/print-result! out) - (t/is (nil? (:error out))) - - (let [result (:result out)] - (t/is (= (:fullname data) (:fullname result))) - (t/is (= (:email data) (:email result))) - (t/is (= (:theme data) (:theme result))) - (t/is (not (contains? result :password)))))) - - (t/testing "update photo" - (let [data {::sm/type :update-profile-photo - :profile-id (:id profile) - :file {:name "sample.jpg" - :path "tests/uxbox/tests/_files/sample.jpg" - :size 123123 - :mtype "image/jpeg"}} - out (th/try-on! (sm/handle data))] - - ;; (th/print-result! out) - (t/is (nil? (:error out))) - - (let [result (:result out)] - (t/is (= (:id profile) (:id result)))))))) - - -(t/deftest profile-deletion - (let [prof @(th/create-profile db/pool 1) - team (:default-team prof) - proj (:default-project prof) - file @(th/create-file db/pool (:id prof) (:id proj) 1) - page @(th/create-page db/pool (:id prof) (:id file) 1)] - - (t/testing "try to delete profile not marked for deletion" - (let [params {:props {:profile-id (:id prof)}} - out (th/try-on! (uxbox.tasks.delete-profile/handler params))] - ;; (th/print-result! out) (t/is (nil? (:error out))) (t/is (nil? (:result out))))) - (t/testing "query profile after delete" + (t/testing "query profile after update" (let [data {::sq/type :profile - :profile-id (:id prof)} + :profile-id (:id profile)} out (th/try-on! (sq/handle data))] ;; (th/print-result! out) (t/is (nil? (:error out))) (let [result (:result out)] - (t/is (= (:fullname prof) (:fullname result)))))) + (t/is (= "Full Name" (:fullname result))) + (t/is (= "en" (:lang result))) + (t/is (= "dark" (:theme result)))))) - (t/testing "mark profile for deletion" - (with-mocks - [mock {:target 'uxbox.tasks/schedule! :return nil}] + ;; (t/testing "update photo" + ;; (let [data {::sm/type :update-profile-photo + ;; :profile-id (:id profile) + ;; :file {:name "sample.jpg" + ;; :path "tests/uxbox/tests/_files/sample.jpg" + ;; :size 123123 + ;; :mtype "image/jpeg"}} + ;; out (th/try-on! (sm/handle data))] - (let [data {::sm/type :delete-profile - :profile-id (:id prof)} - out (th/try-on! (sm/handle data))] - ;; (th/print-result! out) - (t/is (nil? (:error out))) - (t/is (nil? (:result out)))) + ;; ;; (th/print-result! out) + ;; (t/is (nil? (:error out))) - ;; check the mock - (let [mock (deref mock) - mock-params (second (:call-args mock))] - (t/is (true? (:called? mock))) - (t/is (= 1 (:call-count mock))) - (t/is (= "delete-profile" (:name mock-params))) - (t/is (= (:id prof) (get-in mock-params [:props :profile-id])))))) + ;; (let [result (:result out)] + ;; (t/is (= (:id profile) (:id result)))))) + )) - (t/testing "query files after profile soft deletion" - (let [data {::sq/type :files - :project-id (:id proj) - :profile-id (:id prof)} - out (th/try-on! (sq/handle data))] - ;; (th/print-result! out) - (t/is (nil? (:error out))) - (t/is (= 1 (count (:result out)))))) - (t/testing "try to delete profile marked for deletion" - (let [params {:props {:profile-id (:id prof)}} - out (th/try-on! (uxbox.tasks.delete-profile/handler params))] +#_(t/deftest profile-deletion + (let [prof (th/create-profile db/pool 1) + team (:default-team prof) + proj (:default-project prof) + file (th/create-file db/pool (:id prof) (:id proj) 1) + page (th/create-page db/pool (:id prof) (:id file) 1)] - ;; (th/print-result! out) - (t/is (nil? (:error out))) - (t/is (= (:id prof) (:result out))))) + ;; (t/testing "try to delete profile not marked for deletion" + ;; (let [params {:props {:profile-id (:id prof)}} + ;; out (th/try-on! (uxbox.tasks.delete-profile/handler params))] - (t/testing "query profile after delete" - (let [data {::sq/type :profile - :profile-id (:id prof)} - out (th/try-on! (sq/handle data))] + ;; ;; (th/print-result! out) + ;; (t/is (nil? (:error out))) + ;; (t/is (nil? (:result out))))) - ;; (th/print-result! out) + ;; (t/testing "query profile after delete" + ;; (let [data {::sq/type :profile + ;; :profile-id (:id prof)} + ;; out (th/try-on! (sq/handle data))] - (let [error (:error out) - error-data (ex-data error)] - (t/is (th/ex-info? error)) - (t/is (= (:type error-data) :service-error)) - (t/is (= (:name error-data) :uxbox.services.queries.profile/profile))) + ;; ;; (th/print-result! out) + ;; (t/is (nil? (:error out))) - (let [error (ex-cause (:error out)) - error-data (ex-data error)] - (t/is (th/ex-info? error)) - (t/is (= (:type error-data) :not-found))))) + ;; (let [result (:result out)] + ;; (t/is (= (:fullname prof) (:fullname result)))))) - (t/testing "query files after profile permanent deletion" - (let [data {::sq/type :files - :project-id (:id proj) - :profile-id (:id prof)} - out (th/try-on! (sq/handle data))] - ;; (th/print-result! out) - (t/is (nil? (:error out))) - (t/is (= 0 (count (:result out)))))))) + ;; (t/testing "mark profile for deletion" + ;; (with-mocks + ;; [mock {:target 'uxbox.tasks/schedule! :return nil}] + + ;; (let [data {::sm/type :delete-profile + ;; :profile-id (:id prof)} + ;; out (th/try-on! (sm/handle data))] + ;; ;; (th/print-result! out) + ;; (t/is (nil? (:error out))) + ;; (t/is (nil? (:result out)))) + + ;; ;; check the mock + ;; (let [mock (deref mock) + ;; mock-params (second (:call-args mock))] + ;; (t/is (true? (:called? mock))) + ;; (t/is (= 1 (:call-count mock))) + ;; (t/is (= "delete-profile" (:name mock-params))) + ;; (t/is (= (:id prof) (get-in mock-params [:props :profile-id])))))) + + ;; (t/testing "query files after profile soft deletion" + ;; (let [data {::sq/type :files + ;; :project-id (:id proj) + ;; :profile-id (:id prof)} + ;; out (th/try-on! (sq/handle data))] + ;; ;; (th/print-result! out) + ;; (t/is (nil? (:error out))) + ;; (t/is (= 1 (count (:result out)))))) + + ;; (t/testing "try to delete profile marked for deletion" + ;; (let [params {:props {:profile-id (:id prof)}} + ;; out (th/try-on! (uxbox.tasks.delete-profile/handler params))] + + ;; ;; (th/print-result! out) + ;; (t/is (nil? (:error out))) + ;; (t/is (= (:id prof) (:result out))))) + + ;; (t/testing "query profile after delete" + ;; (let [data {::sq/type :profile + ;; :profile-id (:id prof)} + ;; out (th/try-on! (sq/handle data))] + + ;; ;; (th/print-result! out) + + ;; (let [error (:error out) + ;; error-data (ex-data error)] + ;; (t/is (th/ex-info? error)) + ;; (t/is (= (:type error-data) :service-error)) + ;; (t/is (= (:name error-data) :uxbox.services.queries.profile/profile))) + + ;; (let [error (ex-cause (:error out)) + ;; error-data (ex-data error)] + ;; (t/is (th/ex-info? error)) + ;; (t/is (= (:type error-data) :not-found))))) + + ;; (t/testing "query files after profile permanent deletion" + ;; (let [data {::sq/type :files + ;; :project-id (:id proj) + ;; :profile-id (:id prof)} + ;; out (th/try-on! (sq/handle data))] + ;; ;; (th/print-result! out) + ;; (t/is (nil? (:error out))) + ;; (t/is (= 0 (count (:result out)))))) + )) (t/deftest registration-domain-whitelist diff --git a/backend/tests/uxbox/tests/test_services_projects.clj b/backend/tests/uxbox/tests/test_services_projects.clj index 1750651892..c0cd886bab 100644 --- a/backend/tests/uxbox/tests/test_services_projects.clj +++ b/backend/tests/uxbox/tests/test_services_projects.clj @@ -13,8 +13,8 @@ (t/use-fixtures :each th/database-reset) (t/deftest projects-crud - (let [prof @(th/create-profile db/pool 1) - team @(th/create-team db/pool (:id prof) 1) + (let [prof (th/create-profile db/pool 1) + team (th/create-team db/pool (:id prof) 1) project-id (uuid/next)] (t/testing "create a project" @@ -51,7 +51,6 @@ out (th/try-on! (sm/handle data))] ;; (th/print-result! out) (t/is (nil? (:error out))) - (let [result (:result out)] (t/is (= (:id data) (:id result))) (t/is (= (:name data) (:name result))) diff --git a/backend/tests/uxbox/tests/test_services_viewer.clj b/backend/tests/uxbox/tests/test_services_viewer.clj index 3242c756b8..47761dc912 100644 --- a/backend/tests/uxbox/tests/test_services_viewer.clj +++ b/backend/tests/uxbox/tests/test_services_viewer.clj @@ -1,30 +1,27 @@ (ns uxbox.tests.test-services-viewer (:require [clojure.test :as t] - [promesa.core :as p] [datoteka.core :as fs] + [uxbox.common.uuid :as uuid] [uxbox.db :as db] - [uxbox.media :as media] - [uxbox.core :refer [system]] [uxbox.http :as http] + [uxbox.media :as media] [uxbox.services.mutations :as sm] [uxbox.services.queries :as sq] [uxbox.tests.helpers :as th] - [uxbox.util.storage :as ust] - [uxbox.common.uuid :as uuid] - [vertx.util :as vu])) + [uxbox.util.storage :as ust])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) (t/deftest retrieve-bundle - (let [prof @(th/create-profile db/pool 1) - prof2 @(th/create-profile db/pool 2) - team (:default-team prof) - proj (:default-project prof) + (let [prof (th/create-profile db/pool 1) + prof2 (th/create-profile db/pool 2) + team-id (:default-team prof) + proj-id (:default-project prof) - file @(th/create-file db/pool (:id prof) (:id proj) 1) - page @(th/create-page db/pool (:id prof) (:id file) 1) + file (th/create-file db/pool (:id prof) proj-id 1) + page (th/create-page db/pool (:id prof) (:id file) 1) token (atom nil)] diff --git a/backend/vendor/vertx/.gitignore b/backend/vendor/vertx/.gitignore deleted file mode 100644 index ad69c79547..0000000000 --- a/backend/vendor/vertx/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -/target -/classes -/checkouts -pom.xml.asc -*.jar -*.class -/.lein-* -/.nrepl-port -/*-init.clj -/out -/repl -/.cpcache -/.rebel* diff --git a/backend/vendor/vertx/LICENSE b/backend/vendor/vertx/LICENSE deleted file mode 100644 index 14e2f777f6..0000000000 --- a/backend/vendor/vertx/LICENSE +++ /dev/null @@ -1,373 +0,0 @@ -Mozilla Public License Version 2.0 -================================== - -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - 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/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. diff --git a/backend/vendor/vertx/README.md b/backend/vendor/vertx/README.md deleted file mode 100644 index c06cb45861..0000000000 --- a/backend/vendor/vertx/README.md +++ /dev/null @@ -1,250 +0,0 @@ -# vertx-clojure - -A lightweight clojure adapter for vertx toolkit. - -- **STATUS**: *alpha*, in design and prototyping phase. -- **AUDIENCE**: this is not a vertx documentation, this readme intends - to explain only the clojure api - -Example code on `resources/user.clj` file. - - -## Install - -Using `deps.edn`: - -```clojure -vertx-clojure/vertx {:mvn/version "0.0.0-SNAPSHOT"} -``` - -Using Leiningen: - -```clojure -[vertx-clojure/vertx "0.0.0-SNAPSHOT"] -``` - -## User Guide - - -### Verticles - -Verticles is the basic "unit of execution" in the vertx toolkit. The -concept is very simular to actors with the exception that a verticle -does not have a inbox, and verticles communicates with other verticles -or with the rest of the world using **eventbus**. - -For create a verticle, you will need to create first a system instance -(a name that we give to the `Vertx` instance): - -```clojure -(require '[vertx.core :as vc]) - -(def system (vc/system)) -``` - -Then, you can proceed to create a verticle. A verticle concist on -three functions: `on-start`, `on-stop` and `on-error` (where the -`on-start` is mandatory the rest optional). - -Lets define a dummy verticle that just prints hello world on start -callback: - -```clojure -(defn on-start - [ctx] - (println "Hello world")) - -(def dummy-verticle - (vc/verticle {:on-start on-start})) -``` - -The `dummy-verticle` is a verticle factory, nothing is running at this -momment. For run the verticle we need to deploy it using the -previously created `system` instance: - -```clojure -(vc/deploy! system dummy-verticle) -``` - -The `deploy!` return value is a `CompletionStage` so you can deref it -like a regular `Future` or use **funcool/promesa** for chain more -complex transformations. The return value implements `AutoCloseable` -that will allow to undeploy the verticle. - -The `deploy!` function also accepts an additional parameter for -options, and at this momment it only accepts as single option: - -- `:instances` - number of instances to launch of the same verticle. -- `:worker` - use worker thread pool or the default event-loop. - - -### Event Bus - -The **eventbus** is the central communication system for verticles. It -has different patterns of communication. On this documentation we will -cover the: `publish/subscribe` and `request/reply`. - -Lets define a simple echo verticle: - -```clojure -(require '[vertx.eventbus :as ve]) - -(defn on-message - [msg] - (:body msg)) - -(defn on-start - [ctx] - (vc/consumer ctx "test.echo" on-message)) - -(def echo-verticle - (vc/verticle {:on-start on-start})) -``` - -And then, lets deploy 4 instances of it: - -```clojure -(vc/deploy! system echo-verticle {:instances 4}) -``` - -Now, depending on how you send the messages to the "test.echo" topic, -the message will be send to a single instance of will be broadcasted -to all verticle instances subscribed to it. - -To send a message and expect a response we need to use the -`ve/request!` function: - -```clojure -@(ve/request! system {:foo "bar"}) -;; => #vertx.eventbus.Msg{:body {:foo "bar"}} -``` - -The return value of `on-message` callback will be used as a reply and -it can be any plain value or a `CompletionStage`. - -When you want to send a message but you don't need the return value, -there is the `ve/send!` function. And finally, if you want to send a -message to all instances subscribed to a topic, you will need to use -the `ve/publish!` function. - - -### Http Server (vertx.http) - -**STATUS**: pre-alpha: experimental & incomplete - -This part will explain the low-level api for the http server. It is -intended to be used as a building block for a more higher-level api or -when you know that you exactly need for a performance sensitive -applications. - -The `vertx.http` exposes two main functions `handler` and -`server`. Lets start creating a simple "hello world" http server: - -``` -(require '[vertx.http :as vh]) - -(defn hello-world-handler - [req] - {:status 200 - :body "Hello world\n"}) - -(defn on-start - [ctx] - (vh/server {:handler (vh/handler hello-world-handler) - :port 2021})) - -(->> (vc/verticle {:on-start on-start}) - (vc/deploy! system)) -``` - -NOTE: you can start the server without creating a verticle but you -will loss the advantage of scaling (using verticle instances -parameter). - -The `req` object is a plain map with the following keys: - -- `:method` the HTTP method. -- `:path` the PATH of the requested URI. -- `:headers` a map with string lower-cased keys of headers. -- `:vertx.http/request` the underlying vertx request instance. -- `:vertx.http/response` the underlying vertx response instance. - -And the response object to the ring response, it can contain -`:status`, `:body` and `:headers`. - - -**WARNING:** at this moment there are no way to obtain directly the -body of request using clojure api, this is in **design** phase and we -need to think how to expose it correctly without constraint too much -the user code (if you have suggestions, please open an issue). - -**NOTE**: If you want completly bypass the clojure api, pass a vertx -`Handler` instance to server instead of using -`vertx.http/handler`. There is the `vertx.util/fn->handler` helper -that converts a plain clojure function into raw `Handler` instance. - - -### Web Server (vertx.web) - -**STATUS**: alpha - -This part will explain the higher-level http/web server api. It is a -general purpose with more clojure friendly api. It uses -`reitit-core`for the routing and `sieppari` for interceptors. - -Lets start with a complete example: - -```clojure -(require '[vertx.http :as vh]) -(require '[vertx.web :as vw]) -(require '[vertx.web.middleware :as vwm]) - -(defn hello-world-handler - [req] - {:status 200 - :body "Hello world!\n"}) - -(defn on-start - [ctx] - (let [routes [["/" {:middleware [vwm/cookies] - :handler hello-world-handler - :method :get}]] - handler (vw/handler ctx - (vw/assets "/static/*" {:root "resources/public/static"}) - (vw/router routes))] - (vh/server {:handler handler - :port 2022}))) - -(->> (vc/verticle {:on-start on-start}) - (vc/deploy! system)) -``` - -The routes are defined using `reitit-core`. The request object is very -similar to the one explained in `vertx.http`. - -The main difference with `vertx.http` is that the handler is called -when the body is ready to be used and is available under `:body` -keyword on the request. - -All additional features such that reading the query/form params, -parse/write cookies, cors and file uploads are provided with additional middleware -wrappers: - -- `vertx.web.middleware/uploads` parses the vertx uploaded file data - structure and expose it as clojure maps under `:uploads` key. -- `vertx.web.middleware/params` parses the query string and form - params in the body if the content-type is appropriate and exposes - them under `:params`. -- `vertx.web.middleware/cors` properly sets the CORS headers. -- `vertx.web.middleware/cookies` handles the cookies reading from - the request and cookies writing from the response. - - -## License ## - -``` -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/. -``` - diff --git a/backend/vendor/vertx/deps.edn b/backend/vendor/vertx/deps.edn deleted file mode 100644 index 31b8d84f0e..0000000000 --- a/backend/vendor/vertx/deps.edn +++ /dev/null @@ -1,35 +0,0 @@ -{:deps - {org.clojure/tools.logging {:mvn/version "0.5.0"} - funcool/promesa {:mvn/version "5.0.0"} - metosin/reitit-core {:mvn/version "0.3.10"} - org.clojure/core.async {:mvn/version "1.1.587"} - io.vertx/vertx-core {:mvn/version "4.0.0-milestone4"} - io.vertx/vertx-web {:mvn/version "4.0.0-milestone4"} - io.vertx/vertx-web-client {:mvn/version "4.0.0-milestone4"}} - :paths ["src" "resources"] - :aliases - {:dev - {:extra-deps - {com.bhauman/rebel-readline {:mvn/version "0.1.4"} - metosin/jsonista {:mvn/version "0.2.5"} - mount/mount {:mvn/version "0.1.16"} - org.clojure/clojure {:mvn/version "1.10.1"} - io.netty/netty-transport-native-epoll {:mvn/version "4.1.39.Final"} - environ/environ {:mvn/version "1.1.0"} - metosin/pohjavirta {:mvn/version "0.0.1-alpha5"} - org.clojure/tools.namespace {:mvn/version "0.3.1"}} - :extra-paths ["test"]} - :repl - {:main-opts ["-m" "rebel-readline.main"]} - - :jar - {:extra-deps {seancorfield/depstar {:mvn/version "0.3.4"}} - :main-opts ["-m" "hf.depstar.jar", "target/vertx.jar"]} - - :ancient - {:main-opts ["-m" "deps-ancient.deps-ancient"] - :extra-deps {deps-ancient {:mvn/version "RELEASE"}}} - }} - - - diff --git a/backend/vendor/vertx/mvn-upload.sh b/backend/vendor/vertx/mvn-upload.sh deleted file mode 100755 index 5d7f11d524..0000000000 --- a/backend/vendor/vertx/mvn-upload.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -mvn deploy:deploy-file -Dfile=target/vertx.jar -DpomFile=pom.xml -DrepositoryId=clojars -Durl=https://clojars.org/repo/ diff --git a/backend/vendor/vertx/pom.xml b/backend/vendor/vertx/pom.xml deleted file mode 100644 index 3b681c5404..0000000000 --- a/backend/vendor/vertx/pom.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - 4.0.0 - vertx-clojure - vertx - 0.0.0-SNAPSHOT - vertx-clojure - Vert.x adapter for Clojure - https://github.com/funcool/vertx-clojure - - scm:git:git://github.com/vertx-clojure/vertx.git - scm:git:ssh://git@github.com/vertx-clojure/vertx.git - https://github.com/vertx-clojure/vertx - master - - - src - - - - org.clojure - clojure - 1.10.1 - - - org.clojure - tools.logging - 0.5.0 - - - io.vertx - vertx-core - 4.0.0-milestone4 - - - metosin - reitit-core - 0.3.10 - - - io.vertx - vertx-web - 4.0.0-milestone4 - - - funcool - promesa - 5.0.0 - - - io.vertx - vertx-web-client - 4.0.0-milestone4 - - - org.clojure - core.async - 0.7.559 - - - - - clojars - https://repo.clojars.org/ - - - diff --git a/backend/vendor/vertx/src/vertx/core.clj b/backend/vendor/vertx/src/vertx/core.clj deleted file mode 100644 index bbb70ec232..0000000000 --- a/backend/vendor/vertx/src/vertx/core.clj +++ /dev/null @@ -1,201 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns vertx.core - (:require - [clojure.spec.alpha :as s] - [promesa.core :as p] - [vertx.eventbus :as vxe] - [vertx.impl :as impl]) - (:import - io.vertx.core.AsyncResult - io.vertx.core.Context - io.vertx.core.DeploymentOptions - io.vertx.core.Handler - io.vertx.core.Promise - io.vertx.core.Verticle - io.vertx.core.Vertx - io.vertx.core.VertxOptions - java.util.function.Supplier)) - -(declare opts->deployment-options) -(declare opts->vertx-options) -(declare build-verticle) -(declare build-actor) -(declare build-disposable) - -;; --- Public Api - -(s/def :vertx.core$system/threads pos?) -(s/def :vertx.core$system/on-error fn?) -(s/def ::system-options - (s/keys :opt-un [:vertx.core$system/threads - :vertx.core$system/on-error])) - -(defn system - "Creates a new vertx actor system instance." - ([] (system {})) - ([options] - (s/assert ::system-options options) - (let [^VertxOptions opts (opts->vertx-options options) - ^Vertx vsm (Vertx/vertx opts)] - (vxe/configure! vsm opts) - vsm))) - -(defn stop - [^Vertx o] - (.close o)) - -(s/def :vertx.core$verticle/on-start fn?) -(s/def :vertx.core$verticle/on-stop fn?) -(s/def :vertx.core$verticle/on-error fn?) -(s/def ::verticle-options - (s/keys :req-un [:vertx.core$verticle/on-start] - :opt-un [:vertx.core$verticle/on-stop - :vertx.core$verticle/on-error])) - -(defn verticle - "Creates a verticle instance (factory)." - [options] - (s/assert ::verticle-options options) - ^{::verticle true ::options options} - (reify - Supplier - (get [_] (build-verticle options)))) - -(defn verticle? - "Return `true` if `v` is instance of `IVerticleFactory`." - [v] - (true? (::verticle (meta v)))) - -(s/def :vertx.core$actor/on-message fn?) -(s/def ::actor-options - (s/keys :req-un [:vertx.core$actor/on-message] - :opt-un [:vertx.core$verticle/on-start - :vertx.core$verticle/on-error - :vertx.core$verticle/on-stop])) - -(defn actor - "A shortcut for create a verticle instance (factory) that consumes a - specific topic." - [topic options] - (s/assert string? topic) - (s/assert ::actor-options options) - ^{::verticle true ::options options ::topic topic} - (reify - Supplier - (get [_] (build-actor topic options)))) - -(s/def :vertx.core$deploy/instances pos?) -(s/def :vertx.core$deploy/worker boolean?) -(s/def ::deploy-options - (s/keys :opt-un [:vertx.core$deploy/worker - :vertx.core$deploy/instances])) - -(defn deploy! - "Deploy a verticle." - ([vsm supplier] (deploy! vsm supplier nil)) - ([vsm supplier options] - (s/assert verticle? supplier) - (s/assert ::deploy-options options) - (let [d (p/deferred) - o (opts->deployment-options options)] - (.deployVerticle ^Vertx vsm - ^Supplier supplier - ^DeploymentOptions o - ^Handler (impl/deferred->handler d)) - (p/then' d (fn [id] (build-disposable vsm id)))))) - -(defn undeploy! - "Undeploy the verticle, this function should be rarelly used because - the easiest way to undeplo is executin the callable returned by - `deploy!` function." - [vsm id] - (s/assert string? id) - (let [d (p/deferred)] - (.undeploy ^Vertx (impl/resolve-system vsm) - ^String id - ^Handler (impl/deferred->handler d)) - d)) - -;; --- Impl - -(defn- build-verticle - [{:keys [on-start on-stop on-error] - :or {on-error (constantly nil) - on-stop (constantly nil)} - :as options}] - (let [vsm (volatile! nil) - ctx (volatile! nil) - lst (volatile! nil)] - (reify Verticle - (init [_ instance context] - (vreset! vsm instance) - (vreset! ctx context)) - (getVertx [_] @vsm) - (^void start [_ ^Promise o] - (-> (p/do! (on-start @ctx)) - (p/handle (fn [state error] - (if error - (do - (.fail o ^Throwable error) - (on-error @ctx error)) - (do - (when (map? state) - (vswap! lst merge state)) - (.complete o))))))) - (^void stop [_ ^Promise o] - (p/handle (p/do! (on-stop @ctx @lst)) - (fn [_ err] - (if err - (do (on-error err) - (.fail o ^Throwable err)) - (.complete o)))))))) - -(defn- build-actor - [topic {:keys [on-message on-error on-stop on-start] - :or {on-error (constantly nil) - on-start (constantly {}) - on-stop (constantly nil)}}] - (letfn [(-on-start [ctx] - (let [state (on-start ctx) - state (if (map? state) state {}) - consumer (vxe/consumer ctx topic on-message)] - (assoc state ::consumer consumer)))] - (build-verticle {:on-error on-error - :on-stop on-stop - :on-start -on-start}))) - -(defn- build-disposable - [vsm id] - (reify - clojure.lang.IDeref - (deref [_] id) - - clojure.lang.IFn - (invoke [_] (undeploy! vsm id)) - - java.io.Closeable - (close [_] - @(undeploy! vsm id)))) - -(defn- opts->deployment-options - [{:keys [instances worker]}] - (let [opts (DeploymentOptions.)] - (when instances (.setInstances opts (int instances))) - (when worker (.setWorker opts worker)) - opts)) - -(defn- opts->vertx-options - [{:keys [threads worker-threads on-error]}] - (let [opts (VertxOptions.)] - (when threads (.setEventLoopPoolSize opts (int threads))) - (when worker-threads (.setWorkerPoolSize opts (int worker-threads))) - #_(when on-error (.exceptionHandler opts (impl/fn->handler on-error))) - opts)) - - - diff --git a/backend/vendor/vertx/src/vertx/eventbus.clj b/backend/vendor/vertx/src/vertx/eventbus.clj deleted file mode 100644 index 35238edb81..0000000000 --- a/backend/vendor/vertx/src/vertx/eventbus.clj +++ /dev/null @@ -1,125 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns vertx.eventbus - (:require [promesa.core :as p] - [vertx.impl :as impl]) - (:import - io.vertx.core.Vertx - io.vertx.core.Handler - io.vertx.core.Context - io.vertx.core.eventbus.Message - io.vertx.core.eventbus.MessageConsumer - io.vertx.core.eventbus.DeliveryOptions - io.vertx.core.eventbus.EventBus - io.vertx.core.eventbus.MessageCodec - java.util.function.Supplier)) - -(declare opts->delivery-opts) -(declare resolve-eventbus) -(declare build-message-codec) -(declare build-message) - -;; --- Public Api - -(defn consumer - [vsm topic f] - (let [^EventBus bus (resolve-eventbus vsm) - ^MessageConsumer consumer (.consumer bus ^String topic)] - (.handler consumer (reify Handler - (handle [_ msg] - (.pause consumer) - (-> (p/do! (f vsm (build-message msg))) - (p/handle (fn [res err] - (.resume consumer) - (.reply msg (or res err) - (opts->delivery-opts {})))))))) - (reify java.lang.AutoCloseable - (close [it] - (.unregister consumer))))) - -(defn publish! - ([vsm topic msg] (publish! vsm topic msg {})) - ([vsm topic msg opts] - (let [bus (resolve-eventbus vsm) - opts (opts->delivery-opts opts)] - (.publish ^EventBus bus - ^String topic - ^Object msg - ^DeliveryOptions opts) - nil))) - -(defn send! - ([vsm topic msg] (send! vsm topic msg {})) - ([vsm topic msg opts] - (let [bus (resolve-eventbus vsm) - opts (opts->delivery-opts opts)] - (.send ^EventBus bus - ^String topic - ^Object msg - ^DeliveryOptions opts) - nil))) - -(defn request! - ([vsm topic msg] (request! vsm topic msg {})) - ([vsm topic msg opts] - (let [bus (resolve-eventbus vsm) - opts (opts->delivery-opts opts) - d (p/deferred)] - (.request ^EventBus bus - ^String topic - ^Object msg - ^DeliveryOptions opts - ^Handler (impl/deferred->handler d)) - (p/then' d build-message)))) - -(defn configure! - [vsm opts] - (let [^EventBus bus (resolve-eventbus vsm)] - (.registerCodec bus (build-message-codec)))) - -(defrecord Msg [body]) - -(defn message? - [v] - (instance? Msg v)) - -;; --- Impl - -(defn- resolve-eventbus - [o] - (cond - (instance? Vertx o) (.eventBus ^Vertx o) - (instance? Context o) (resolve-eventbus (.owner ^Context o)) - (instance? EventBus o) o - :else (throw (ex-info "unexpected argument" {})))) - -(defn- build-message-codec - [] - ;; TODO: implement the wire encode/decode using transit+msgpack - (reify MessageCodec - (encodeToWire [_ buffer data]) - (decodeFromWire [_ pos buffer]) - (transform [_ data] data) - (name [_] "clj:msgpack") - (^byte systemCodecID [_] (byte -1)))) - -(defn- build-message - [^Message msg] - (let [metadata {::reply-to (.replyAddress msg) - ::send? (.isSend msg) - ::address (.address msg)} - body (.body msg)] - (Msg. body metadata nil))) - -(defn- opts->delivery-opts - [{:keys [codec local?]}] - (let [^DeliveryOptions opts (DeliveryOptions.)] - (.setCodecName opts (or codec "clj:msgpack")) - (when local? (.setLocalOnly opts true)) - opts)) - - diff --git a/backend/vendor/vertx/src/vertx/http.clj b/backend/vendor/vertx/src/vertx/http.clj deleted file mode 100644 index 0e37afb6e7..0000000000 --- a/backend/vendor/vertx/src/vertx/http.clj +++ /dev/null @@ -1,154 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns vertx.http - "Enables `raw` access to the http facilites of vertx. If you want more - clojure idiomatic api, refer to the `vertx.web` namespace." - (:require [clojure.spec.alpha :as s] - [promesa.core :as p] - [vertx.util :as util] - [vertx.impl :as impl]) - (:import - java.util.Map$Entry - clojure.lang.MapEntry - io.vertx.core.Vertx - io.vertx.core.Verticle - io.vertx.core.Handler - io.vertx.core.Future - io.vertx.core.MultiMap - io.vertx.core.Context - io.vertx.core.buffer.Buffer - io.vertx.core.http.HttpServer - io.vertx.core.http.HttpServerRequest - io.vertx.core.http.HttpServerResponse - io.vertx.core.http.HttpServerOptions - io.vertx.core.http.ServerWebSocket)) - -(declare opts->http-server-options) -(declare resolve-handler) - -;; --- Public Api - -(declare -handle-response) -(declare -handle-body) - -(defn ->headers - [^MultiMap headers] - (let [it (.iterator ^MultiMap headers)] - (loop [m (transient {})] - (if (.hasNext it) - (let [^Map$Entry me (.next it) - key (.toLowerCase ^String (.getKey me)) - val (.getValue me)] - (recur (assoc! m key val))) - (persistent! m))))) - -(defn- ->request - [^HttpServerRequest request] - {:method (-> request .rawMethod .toLowerCase keyword) - :path (.path request) - :headers (->headers (.headers request)) - ::request request - ::response (.response request)}) - -(defn handler - [vsm f] - (reify Handler - (handle [this request] - (let [ctx (->request request)] - (-handle-response (f ctx) ctx))))) - -(s/def :vertx.http/handler - (s/or :fn fn? :handler #(instance? Handler %))) -(s/def :vertx.http/host string?) -(s/def :vertx.http/port pos?) -(s/def ::server-options - (s/keys :req-un [:vertx.http/handler] - :opt-un [:vertx.http/host - :vertx.http/port])) - -(defn server - "Starts a vertx http server." - [vsm {:keys [handler] :as options}] - (s/assert ::server-options options) - (let [^Vertx vsm (impl/resolve-system vsm) - ^HttpServerOptions opts (opts->http-server-options options) - ^HttpServer srv (.createHttpServer vsm opts) - ^Handler handler (resolve-handler handler)] - (doto srv - (.requestHandler handler) - (.listen)) - srv)) - -;; --- Impl - -(defn- opts->http-server-options - [{:keys [host port]}] - (let [opts (HttpServerOptions.)] - (.setReuseAddress opts true) - (.setReusePort opts true) - (.setTcpNoDelay opts true) - (.setTcpFastOpen opts true) - (when host (.setHost opts ^String host)) - (when port (.setPort opts ^int port)) - opts)) - -(defn- resolve-handler - [handler] - (cond - (fn? handler) (impl/fn->handler handler) - (instance? Handler handler) handler - :else (throw (ex-info "invalid handler" {})))) - -(defn- assign-status-and-headers! - [^HttpServerResponse res response] - (let [headers (:headers response) - status (:status response 200)] - (when (map? headers) - (util/doseq [[key val] headers] - (.putHeader res ^String (name key) ^String (str val)))) - (.setStatusCode res status))) - -(defprotocol IAsyncResponse - (-handle-response [_ _])) - -(defprotocol IAsyncBody - (-handle-body [_ _])) - -(extend-protocol IAsyncResponse - java.util.concurrent.CompletionStage - (-handle-response [data ctx] - (p/then' data #(-handle-response % ctx))) - - clojure.lang.IPersistentMap - (-handle-response [data ctx] - (let [body (:body data) - res (::response ctx)] - (assign-status-and-headers! res data) - (-handle-body body res))) - - nil - (-handle-response [sws ctx])) - -(extend-protocol IAsyncBody - (Class/forName "[B") - (-handle-body [data res] - (.end ^HttpServerResponse res (Buffer/buffer ^bytes data))) - - Buffer - (-handle-body [data res] - (.end ^HttpServerResponse res ^Buffer data)) - - nil - (-handle-body [data res] - (.putHeader ^HttpServerResponse res "content-length" "0") - (.end ^HttpServerResponse res)) - - String - (-handle-body [data res] - (let [length (count data)] - (.putHeader ^HttpServerResponse res "content-length" (str length)) - (.end ^HttpServerResponse res data)))) diff --git a/backend/vendor/vertx/src/vertx/impl.clj b/backend/vendor/vertx/src/vertx/impl.clj deleted file mode 100644 index febe145bb2..0000000000 --- a/backend/vendor/vertx/src/vertx/impl.clj +++ /dev/null @@ -1,55 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns vertx.impl - "Implementation helpers." - (:refer-clojure :exclude [doseq]) - (:require [promesa.core :as p]) - (:import - io.vertx.core.Vertx - io.vertx.core.Handler - io.vertx.core.Context - io.vertx.core.AsyncResult - java.util.function.Supplier)) - -(defn resolve-system - [o] - (cond - (instance? Vertx o) o - (instance? Context o) (.owner ^Context o) - :else (throw (ex-info "unexpected parameters" {:o o})))) - -(defn fn->supplier - [f] - (reify Supplier - (get [_] (f)))) - -(defn fn->handler - [f] - (reify Handler - (handle [_ v] - (f v)))) - -(defn deferred->handler - [d] - (reify Handler - (handle [_ ar] - (if (.failed ^AsyncResult ar) - (p/reject! d (.cause ^AsyncResult ar)) - (p/resolve! d (.result ^AsyncResult ar)))))) - -(defmacro doseq - "A faster version of doseq." - [[bsym csym] & body] - (let [itsym (gensym "iterator")] - `(let [~itsym (.iterator ~(with-meta csym {:tag 'java.lang.Iterable}))] - (loop [] - (when (.hasNext ~(with-meta itsym {:tag 'java.util.Iterator})) - (let [~bsym (.next ~itsym)] - ~@body - (recur))))))) - - diff --git a/backend/vendor/vertx/src/vertx/stream.clj b/backend/vendor/vertx/src/vertx/stream.clj deleted file mode 100644 index 2909ec91e6..0000000000 --- a/backend/vendor/vertx/src/vertx/stream.clj +++ /dev/null @@ -1,70 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019-2020 Andrey Antukh - -(ns vertx.stream - "A stream abstraction on top of core.async with awareness of vertx - execution context." - (:refer-clojure :exclude [loop]) - (:require - [clojure.spec.alpha :as s] - [clojure.core.async :as a] - [clojure.core :as c] - [promesa.core :as p] - [vertx.impl :as impl] - [vertx.util :as vu])) - -;; --- Streams - -(defmacro loop - [& args] - `(let [ctx# (vu/current-context)] - (binding [p/*loop-run-fn* #(vu/run-on-context! ctx# %)] - (p/loop ~@args)))) - -(defn stream - ([] (a/chan)) - ([b] (a/chan b)) - ([b c] (a/chan b c)) - ([b c e] (a/chan b c e))) - -(defn take! - [c] - (let [d (p/deferred) - ctx (vu/current-context)] - (a/take! c (fn [res] - (vu/run-on-context! ctx #(p/resolve! d res)))) - d)) - -(defn poll! - [c] - (a/poll! c)) - -(defn put! - [c v] - (let [d (p/deferred) - ctx (vu/current-context)] - (a/put! c v (fn [res] - (vu/run-on-context! ctx #(p/resolve! d res)))) - d)) - -(defn offer! - [c v] - (a/offer! c v)) - -(defn alts! - ([ports] (alts! ports {})) - ([ports opts] - (let [d (p/deferred) - ctx (vu/current-context) - deliver #(vu/run-on-context! ctx (fn [] (p/resolve! d %))) - ret (a/do-alts deliver ports opts)] - (if ret - (p/resolved @ret) - d)))) - -(defn close! - [c] - (a/close! c)) diff --git a/backend/vendor/vertx/src/vertx/timers.clj b/backend/vendor/vertx/src/vertx/timers.clj deleted file mode 100644 index ff345a4dd4..0000000000 --- a/backend/vendor/vertx/src/vertx/timers.clj +++ /dev/null @@ -1,76 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns vertx.timers - "The timers and async scheduled tasks." - (:require - [clojure.spec.alpha :as s] - [promesa.core :as p] - [vertx.impl :as impl]) - (:import - io.vertx.core.Vertx - io.vertx.core.Handler)) - -;; --- Low Level API - -(defn schedule-once! - [vsm ms f] - (let [^Vertx system (impl/resolve-system vsm) - ^Handler handler (impl/fn->handler (fn [v] (f))) - timer-id (.setTimer system ms handler)] - (reify - java.lang.AutoCloseable - (close [_] - (.cancelTimer system timer-id))))) - -(defn schedule-periodic! - [vsm ms f] - (let [^Vertx system (impl/resolve-system vsm) - ^Handler handler (impl/fn->handler (fn [v] (f))) - timer-id (.setPeriodic system ms handler)] - (reify - java.lang.AutoCloseable - (close [_] - (.cancelTimer system timer-id))))) - -;; --- High Level API - -(s/def ::once boolean?) -(s/def ::repeat boolean?) -(s/def ::delay integer?) -(s/def ::fn (s/or :fn fn? :var var?)) - -(s/def ::schedule-opts - (s/keys :req [::fn ::delay] :opt [::once ::repeat])) - -(defn schedule! - "High level schedule function." - [vsm {:keys [::once ::repeat ::delay] :as opts}] - (s/assert ::schedule-opts opts) - - (when (and (not once) (not repeat)) - (throw (IllegalArgumentException. "you should specify `once` or `repeat` params"))) - - (let [system (impl/resolve-system vsm) - state (atom nil) - taskfn (fn wrapped-task [] - (-> (p/do! ((::fn opts) opts)) - (p/catch' (constantly nil)) ; explicitly ignore all errors - (p/then' (fn [_] ; the user needs to catch errors - (if repeat - (let [tid (schedule-once! vsm delay wrapped-task)] - (reset! state tid) - nil)) - (do - (reset! state nil) - nil))))) - tid (reset! state (schedule-once! vsm delay taskfn))] - (reify - java.lang.AutoCloseable - (close [this] - (when (compare-and-set! state tid nil) - (.cancelTimer ^Vertx system tid)))))) - diff --git a/backend/vendor/vertx/src/vertx/util.clj b/backend/vendor/vertx/src/vertx/util.clj deleted file mode 100644 index 1116bc55d8..0000000000 --- a/backend/vendor/vertx/src/vertx/util.clj +++ /dev/null @@ -1,139 +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-2020 Andrey Antukh - -(ns vertx.util - (:refer-clojure :exclude [loop doseq]) - (:require - [clojure.spec.alpha :as s] - [clojure.core.async :as a] - [clojure.core :as c] - [promesa.core :as p] - [vertx.impl :as impl]) - (:import - io.vertx.core.AsyncResult - io.vertx.core.Context - io.vertx.core.Handler - io.vertx.core.Promise - io.vertx.core.Vertx)) - -(defn get-or-create-context - [vsm] - (.getOrCreateContext ^Vertx (impl/resolve-system vsm))) - -(defn current-context - "Returns the current context or nil." - [] - (Vertx/currentContext)) - -(defmacro blocking - [& body] - (let [sym-vsm (with-meta (gensym "blocking") - {:tag 'io.vertx.core.Vertx}) - sym-e (with-meta (gensym "blocking") - {:tag 'java.lang.Throwable}) - sym-prm (gensym "blocking") - sym-ar (gensym "blocking")] - `(let [~sym-vsm (-> (current-context) - (impl/resolve-system)) - d# (p/deferred)] - (.executeBlocking - ~sym-vsm - (reify Handler - (handle [_ ~sym-prm] - (let [prm# ~(with-meta sym-prm {:tag 'io.vertx.core.Promise})] - (try - (.complete prm# (do ~@body)) - (catch Throwable ~sym-e - (.fail prm# ~sym-e)))))) - true - (reify Handler - (handle [_ ~sym-ar] - (let [ar# ~(with-meta sym-ar {:tag 'io.vertx.core.AsyncResult})] - (if (.failed ar#) - (p/reject! d# (.cause ar#)) - (p/resolve! d# (.result ar#))))))) - d#))) - -(defn wrap-blocking - ([f] (wrap-blocking (current-context) f)) - ([ctx f] - (let [^Vertx vsm (impl/resolve-system ctx)] - (fn [& args] - (let [d (p/deferred)] - (.executeBlocking - vsm - (reify Handler - (handle [_ prm] - (try - (.complete ^Promise prm (apply f args)) - (catch Throwable e - (.fail ^Promise prm e))))) - true - (reify Handler - (handle [_ ar] - (if (.failed ^AsyncResult ar) - (p/reject! d (.cause ^AsyncResult ar)) - (p/resolve! d (.result ^AsyncResult ar)))))) - d))))) - -(defn handle-on-context - "Attaches the context (current if not explicitly provided) to the - promise execution chain." - ([prm] (handle-on-context prm (current-context))) - ([prm ctx] - (assert (instance? Context ctx) "`ctx` should be a valid Context instance") - (let [d (p/deferred)] - (p/finally prm (fn [v e] - (.runOnContext - ^Context ctx - ^Handler (reify Handler - (handle [_ v'] - (if e - (p/reject! d e) - (p/resolve! d v))))))) - d))) - -(defn run-on-context! - "Run callbale on context." - [ctx f] - (.runOnContext ^Context ctx - ^Handler (reify Handler - (handle [_ v'] - (f))))) - - -(defmacro loop - [& args] - `(let [ctx# (current-context)] - (binding [p/*loop-run-fn* #(run-on-context! ctx# %)] - (p/loop ~@args)))) - -(defmacro doseq - "A faster version of doseq." - [[bsym csym] & body] - (let [itsym (gensym "iterator")] - `(let [~itsym (.iterator ~(with-meta csym {:tag 'java.lang.Iterable}))] - (c/loop [] - (when (.hasNext ~(with-meta itsym {:tag 'java.util.Iterator})) - (let [~bsym (.next ~itsym)] - ~@body - (recur))))))) - - -(defmacro go-try - [& body] - `(a/go - (try - ~@body - (catch Throwable e# e#)))) - -(defmacro - -(ns vertx.web - "High level api for http servers." - (:require - [clojure.tools.logging :as log] - [clojure.spec.alpha :as s] - [promesa.core :as p] - [reitit.core :as rt] - [reitit.middleware :as rmw] - [vertx.http :as http] - [vertx.impl :as impl]) - (:import - clojure.lang.IPersistentMap - clojure.lang.Keyword - io.vertx.core.Future - io.vertx.core.Handler - io.vertx.core.Vertx - io.vertx.core.buffer.Buffer - io.vertx.core.http.Cookie - io.vertx.core.http.HttpServer - io.vertx.core.http.HttpServerOptions - io.vertx.core.http.HttpServerRequest - io.vertx.core.http.HttpServerResponse - io.vertx.core.http.ServerWebSocket - io.vertx.ext.web.Route - io.vertx.ext.web.Router - io.vertx.ext.web.RoutingContext - io.vertx.ext.web.handler.BodyHandler - io.vertx.ext.web.handler.LoggerHandler - io.vertx.ext.web.handler.ResponseTimeHandler - io.vertx.ext.web.handler.StaticHandler)) - -;; --- Public Api - -(s/def ::wrap-handler - (s/or :fn fn? - :vec (s/every fn? :kind vector?))) - -(defn- ->request - [^RoutingContext routing-context] - (let [^HttpServerRequest request (.request ^RoutingContext routing-context) - ^HttpServerResponse response (.response ^RoutingContext routing-context) - ^Vertx system (.vertx routing-context)] - {:body (.getBody routing-context) - :path (.path request) - :headers (http/->headers (.headers request)) - :method (-> request .rawMethod .toLowerCase keyword) - ::http/request request - ::http/response response - ;; ::execution-context (.getContext system) - ::routing-context routing-context})) - -(defn handler - "Wraps a user defined funcion based handler into a vertx-web aware - handler (with support for multipart uploads)." - [vsm & handlers] - (let [^Vertx vsm (impl/resolve-system vsm) - ^Router router (Router/router vsm)] - (reduce #(%2 %1) router handlers))) - -(defn assets - ([path] (assets path {})) - ([path {:keys [root] :or {root "public"} :as options}] - (fn [^Router router] - (let [^Route route (.route router path) - ^Handler handler (doto (StaticHandler/create) - (.setCachingEnabled false) - (.setWebRoot root) - (.setDirectoryListing true))] - (.handler route handler) - ;; A hack for lie to body handler that request is already handled. - (.handler route - (reify Handler - (handle [_ rc] - (.put ^RoutingContext rc "__body-handled" true) - (.next ^RoutingContext rc)))) - router)))) - -(defn- default-handler - [ctx] - (if (::match ctx) - {:status 405} - {:status 404})) - -(defn- default-on-error - [err req] - (log/error err) - {:status 500 - :body "Internal server error!\n"}) - -(defn- router-handler - [router {:keys [path method] :as ctx}] - (if-let [{:keys [result path-params] :as match} (rt/match-by-path router path)] - (let [handler-fn (:handler result) - ctx (assoc ctx ::match match :path-params path-params)] - (handler-fn ctx)) - (default-handler ctx))) - -(defn router - ([routes] (router routes {})) - ([routes {:keys [delete-uploads? - upload-dir - on-error - log-requests? - time-response?] - :or {delete-uploads? true - upload-dir "/tmp/vertx.uploads" - on-error default-on-error - log-requests? false - time-response? true} - :as options}] - (let [rtr (rt/router routes {:compile rmw/compile-result}) - rtf #(router-handler rtr %)] - (fn [^Router router] - (let [^Route route (.route router)] - (when time-response? (.handler route (ResponseTimeHandler/create))) - (when log-requests? (.handler route (LoggerHandler/create))) - - (doto route - (.failureHandler - (reify Handler - (handle [_ rc] - (let [err (.failure ^RoutingContext rc) - req (.get ^RoutingContext rc "vertx$clj$req")] - (-> (p/do! (on-error err req)) - (http/-handle-response req)))))) - - (.handler - (doto (BodyHandler/create true) - (.setDeleteUploadedFilesOnEnd delete-uploads?) - (.setUploadsDirectory upload-dir))) - - (.handler - (reify Handler - (handle [_ rc] - (let [req (->request rc) - efn (fn [err] - (.put ^RoutingContext rc "vertx$clj$req" req) - (.fail ^RoutingContext rc ^Throwable err))] - (try - (let [result (rtf req)] - (-> (http/-handle-response result req) - (p/catch' efn))) - (catch Exception err - (efn err))))))))) - router)))) diff --git a/backend/vendor/vertx/src/vertx/web/client.clj b/backend/vendor/vertx/src/vertx/web/client.clj deleted file mode 100644 index 9cfd73fec4..0000000000 --- a/backend/vendor/vertx/src/vertx/web/client.clj +++ /dev/null @@ -1,51 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns vertx.web.client - "High level http client." - (:refer-clojure :exclude [get]) - (:require - [clojure.spec.alpha :as s] - [promesa.core :as p] - [reitit.core :as rt] - [vertx.http :as http] - [vertx.impl :as impl]) - (:import - clojure.lang.IPersistentMap - clojure.lang.Keyword - io.vertx.core.Future - io.vertx.core.Handler - io.vertx.core.Vertx - io.vertx.core.buffer.Buffer - io.vertx.core.http.HttpMethod - io.vertx.ext.web.client.HttpRequest - io.vertx.ext.web.client.HttpResponse - io.vertx.ext.web.client.WebClientSession - io.vertx.ext.web.client.WebClient)) - -;; TODO: accept options - -(defn create - ([vsm] (create vsm {})) - ([vsm opts] - (let [^Vertx system (impl/resolve-system vsm)] - (WebClient/create system)))) - -(defn session - [client] - (WebClientSession/create client)) - -(defn get - ([session url] (get session url {})) - ([session url opts] - (let [^HttpRequest req (.getAbs ^WebClientSession session url) - d (p/deferred)] - (.send req (impl/deferred->handler d)) - (p/then d (fn [^HttpResponse res] - {:body (.bodyAsBuffer res) - :status (.statusCode res) - :headers (http/->headers (.headers res))}))))) - diff --git a/backend/vendor/vertx/src/vertx/web/middleware.clj b/backend/vendor/vertx/src/vertx/web/middleware.clj deleted file mode 100644 index b3a2954b4a..0000000000 --- a/backend/vendor/vertx/src/vertx/web/middleware.clj +++ /dev/null @@ -1,217 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns vertx.web.middleware - "Common middleware's." - (:require - [clojure.spec.alpha :as s] - [clojure.string :as str] - [promesa.core :as p] - [reitit.core :as r] - [vertx.http :as http] - [vertx.web :as web] - [vertx.util :as util]) - (:import - clojure.lang.Keyword - clojure.lang.MapEntry - io.vertx.core.Future - io.vertx.core.Handler - io.vertx.core.MultiMap - io.vertx.core.Vertx - io.vertx.core.http.Cookie - io.vertx.core.http.HttpServerRequest - io.vertx.core.http.HttpServerResponse - io.vertx.ext.web.FileUpload - io.vertx.ext.web.RoutingContext - java.util.Map - java.util.Map$Entry)) - -;; --- Cookies - -(defn- build-cookie - [name data] - (cond-> (Cookie/cookie ^String name ^String (:value data)) - (:http-only data) (.setHttpOnly true) - (:domain data) (.setDomain (:domain data)) - (:path data) (.setPath (:path data)) - (:secure data) (.setSecure true))) - -(defn- handle-cookies-response - [request {:keys [cookies] :as response}] - (let [^HttpServerResponse res (::http/response request)] - (util/doseq [[key val] cookies] - (if (nil? val) - (.removeCookie res key) - (.addCookie res (build-cookie key val)))))) - -(defn- cookie->vector - [^Cookie item] - [(.getName item) (.getValue item)]) - -(defn- wrap-cookies - [handler] - (let [xf (map cookie->vector)] - (fn [request] - (let [req (::http/request request) - cookies (.cookieMap ^HttpServerRequest req) - cookies (into {} xf (vals cookies))] - (-> (p/do! (handler (assoc request :cookies cookies))) - (p/then' (fn [response] - (when (and (map? response) - (map? (:cookies response))) - (handle-cookies-response request response)) - response))))))) - -(def cookies - {:name ::cookies - :compile (constantly wrap-cookies)}) - - -;; --- Params - -(defn- parse-params - [^HttpServerRequest request] - (let [params (.params request) - it (.iterator ^MultiMap params)] - (loop [m (transient {})] - (if (.hasNext it) - (let [^Map$Entry o (.next it) - key (keyword (.toLowerCase ^String (.getKey o))) - prv (get m key ::default) - val (.getValue o)] - (cond - (= prv ::default) - (recur (assoc! m key val)) - - (vector? prv) - (recur (assoc! m key (conj prv val))) - - :else - (recur (assoc! m key [prv val])))) - (persistent! m))))) - -(defn- wrap-params - [handler] - (fn [request] - (let [req (::http/request request) - params (parse-params req)] - (handler (assoc request :params params))))) - -(def params - {:name ::params - :compile (constantly wrap-params)}) - - -;; --- Uploads - -(defn- wrap-uploads - [handler] - (fn [request] - (let [rctx (::web/routing-context request) - uploads (.fileUploads ^RoutingContext rctx) - uploads (reduce (fn [acc ^FileUpload upload] - (assoc acc - (keyword (.name upload)) - {:type :uploaded-file - :mtype (.contentType upload) - :path (.uploadedFileName upload) - :name (.fileName upload) - :size (.size upload)})) - {} - uploads)] - (handler (assoc request :uploads uploads))))) - -(def uploads - {:name ::uploads - :compile (constantly wrap-uploads)}) - -;; --- Errors - -(defn- wrap-errors - [handler on-error] - (fn [request] - (-> (p/do! (handler request)) - (p/catch (fn [error] - (on-error error request)))))) - -(def errors - {:name ::errors - :compile (constantly wrap-errors)}) - - -;; --- CORS - -(s/def ::origin string?) -(s/def ::allow-credentials boolean?) -(s/def ::allow-methods (s/every keyword? :kind set?)) -(s/def ::allow-headers (s/every keyword? :kind set?)) -(s/def ::expose-headers (s/every keyword? :kind set?)) -(s/def ::max-age number?) - -(s/def ::cors-opts - (s/keys :req-un [::origin] - :opt-un [::allow-headers - ::allow-methods - ::expose-headers - ::max-age])) - -(defn wrap-cors - [handler opts] - (s/assert ::cors-opts opts) - (letfn [(preflight? [{:keys [method headers] :as ctx}] - (and (= method :options) - (contains? headers "origin") - (contains? headers "access-control-request-method"))) - - (normalize [data] - (str/join ", " (map name data))) - - (allow-origin? [headers] - (let [origin (:origin opts) - value (get headers "origin")] - (cond - (nil? value) value - (= origin "*") origin - (set? origin) (origin value) - (= origin value) origin))) - - (get-headers [{:keys [headers] :as ctx}] - (when-let [origin (allow-origin? headers)] - (cond-> {"access-control-allow-origin" origin - "access-control-allow-methods" "GET, OPTIONS, HEAD"} - - (:allow-methods opts) - (assoc "access-control-allow-methods" - (-> (normalize (:allow-methods opts)) - (str/upper-case))) - - (:allow-credentials opts) - (assoc "access-control-allow-credentials" "true") - - (:expose-headers opts) - (assoc "access-control-expose-headers" - (-> (normalize (:expose-headers opts)) - (str/lower-case))) - - (:max-age opts) - (assoc "access-control-max-age" (:max-age opts)) - - (:allow-headers opts) - (assoc "access-control-allow-headers" - (-> (normalize (:allow-headers opts)) - (str/lower-case))))))] - (fn [request] - (if (preflight? request) - {:status 204 :headers (get-headers request)} - (-> (p/do! (handler request)) - (p/then (fn [response] - (if (map? response) - (update response :headers merge (get-headers request)) - response)))))))) - -(def cors - {:name ::cors - :compile (constantly wrap-cors)}) diff --git a/backend/vendor/vertx/src/vertx/web/websockets.clj b/backend/vendor/vertx/src/vertx/web/websockets.clj deleted file mode 100644 index 1235930e7b..0000000000 --- a/backend/vendor/vertx/src/vertx/web/websockets.clj +++ /dev/null @@ -1,116 +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-2020 Andrey Antukh - -(ns vertx.web.websockets - "Web Sockets." - (:require - [clojure.tools.logging :as log] - [clojure.core.async :as a] - [promesa.core :as p] - [vertx.http :as vh] - [vertx.web :as vw] - [vertx.impl :as vi] - [vertx.util :as vu] - [vertx.eventbus :as ve]) - (:import - java.lang.AutoCloseable - io.vertx.core.AsyncResult - io.vertx.core.Promise - io.vertx.core.Handler - io.vertx.core.Vertx - io.vertx.core.buffer.Buffer - io.vertx.core.http.HttpServerRequest - io.vertx.core.http.HttpServerResponse - io.vertx.core.http.ServerWebSocket)) - -(defrecord WebSocket [conn input output on-error] - AutoCloseable - (close [it] - (a/close! input) - (a/close! output) - (.close ^ServerWebSocket conn (short 403)))) - -(defn- write-to-websocket - [conn on-error message] - (let [r (a/chan 1) - h (reify Handler - (handle [_ ar] - (if (.failed ^AsyncResult ar) - (a/put! r (.cause ^AsyncResult ar)) - (a/close! r))))] - - (cond - (string? message) - (.writeTextMessage ^ServerWebSocket conn - ^String message - ^Handler h) - - (instance? Buffer message) - (.writeBinaryMessage ^ServerWebSocket conn - ^Buffer message - ^Handler h) - - :else - (a/put! r (ex-info "invalid message type" {:message message}))) - r)) - -(defn- default-on-error - [^Throwable err] - (log/error "Unexpected exception on websocket handler:\n" - (with-out-str - (.printStackTrace err (java.io.PrintWriter. *out*))))) - -(defn websocket - [{:keys [handler on-error - input-buffer-size - output-buffer-size] - :or {on-error default-on-error - input-buffer-size 64 - output-buffer-size 64}}] - (reify - vh/IAsyncResponse - (-handle-response [it request] - (let [^HttpServerRequest req (::vh/request request) - ^ServerWebSocket conn (.upgrade req) - - inp-s (a/chan input-buffer-size) - out-s (a/chan output-buffer-size) - - ctx (vu/current-context) - ws (->WebSocket conn inp-s out-s on-error) - - impl-on-error - (fn [err] - (.close ^AutoCloseable ws) - (on-error err)) - - impl-on-close - (fn [_] - (a/close! inp-s) - (a/close! out-s)) - - impl-on-message - (fn [message] - (when-not (a/offer! inp-s message) - (.pause conn) - (a/put! inp-s message - (fn [res] - (when-not (false? res) - (.resume conn))))))] - - (.exceptionHandler conn ^Handler (vi/fn->handler impl-on-error)) - (.textMessageHandler conn ^Handler (vi/fn->handler impl-on-message)) - (.closeHandler conn ^Handler (vi/fn->handler impl-on-close)) - - (a/go-loop [] - (let [msg (a/ - -(ns vertx-tests.main - (:require [clojure.test :as t])) - -(defn -main - [& args] - (let [{:keys [fail]} (t/run-all-tests #"^vertx-tests.*")] - (if (pos? fail) - (System/exit fail) - (System/exit 0)))) diff --git a/backend/vendor/vertx/test/vertx_tests/test_core.clj b/backend/vendor/vertx/test/vertx_tests/test_core.clj deleted file mode 100644 index 3546417d63..0000000000 --- a/backend/vendor/vertx/test/vertx_tests/test_core.clj +++ /dev/null @@ -1,53 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) 2019 Andrey Antukh - -(ns vertx-tests.test-core - (:require [clojure.test :as t] - [vertx.core :as vx] - [vertx.eventbus :as vxe])) - -(def sleep #(Thread/sleep %)) - -(t/deftest start-stop-verticle - (with-open [vsm (vx/system)] - (let [state (atom {})] - (let [on-start (fn [_] (swap! state assoc :start true) {:a 1}) - on-stop (fn [_ s] (swap! state assoc :stop true :inner (:a s))) - verticle (vx/verticle {:on-start on-start :on-stop on-stop})] - ;; Start and stop verticle - (.close @(vx/deploy! vsm verticle)) - - ;; Checks - (t/is (:start @state)) - (t/is (:stop @state)) - (t/is (= (:inner @state) 1)))))) - -(t/deftest start-stop-actor - (with-open [vsm (vx/system)] - (let [state (atom {})] - (let [on-start (fn [_] (swap! state assoc :start true) {:a 1}) - on-stop (fn [_ s] (swap! state assoc :stop true :inner (:a s))) - rcvlock (promise) - on-message #(deliver rcvlock %2) - verticle (vx/actor "test.topic" {:on-message on-message - :on-start on-start - :on-stop on-stop})] - (with-open [vid @(vx/deploy! vsm verticle)] - ;; Checks - (t/is (true? (:start @state))) - (t/is (nil? (:stop @state))) - - (vxe/send! vsm "test.topic" {:num 1}) - - ;; Checks - (t/is (vxe/message? @rcvlock)) - (t/is (= {:num 1} (:body @rcvlock)))) - - (t/is (= (:inner @state) 1)) - (t/is (true? (:stop @state))))))) - - - diff --git a/backend/vendor/vertx/tools.clj b/backend/vendor/vertx/tools.clj deleted file mode 100644 index 74b5e83136..0000000000 --- a/backend/vendor/vertx/tools.clj +++ /dev/null @@ -1,17 +0,0 @@ -(require '[clojure.java.shell :as shell] - '[clojure.main]) -(require '[rebel-readline.core] - '[rebel-readline.clojure.main] - '[rebel-readline.clojure.line-reader] - '[rebel-readline.clojure.service.local]) - -(defmulti task first) - -(defmethod task :default - [args] - (let [all-tasks (-> task methods (dissoc :default) keys sort) - interposed (->> all-tasks (interpose ", ") (apply str))] - (println "Unknown or missing task. Choose one of:" interposed) - (System/exit 1))) - -(task *command-line-args*) diff --git a/frontend/src/uxbox/main/data/workspace/notifications.cljs b/frontend/src/uxbox/main/data/workspace/notifications.cljs index 430ae20db5..bdb1e2d301 100644 --- a/frontend/src/uxbox/main/data/workspace/notifications.cljs +++ b/frontend/src/uxbox/main/data/workspace/notifications.cljs @@ -39,7 +39,8 @@ ptk/UpdateEvent (update [_ state] (let [sid (:session-id state) - url (ws/url (str "/notifications/" file-id "/" sid))] + url (ws/url "/ws/notifications" {:file-id file-id + :session-id sid})] (assoc-in state [:ws file-id] (ws/open url)))) ptk/WatchEvent diff --git a/frontend/src/uxbox/util/http_api.cljs b/frontend/src/uxbox/util/http_api.cljs index 72499545ea..b788717a7d 100644 --- a/frontend/src/uxbox/util/http_api.cljs +++ b/frontend/src/uxbox/util/http_api.cljs @@ -35,13 +35,15 @@ [{:keys [body headers auth method query url response-type] :or {auth true response-type :text}}] (let [headers (merge {"Accept" "application/transit+json,*/*"} - default-headers + (when (map? body) default-headers) headers) request {:method method :url url :headers headers :query query - :body (when (not= :get method) (t/encode body))} + :body (if (map? body) + (t/encode body) + body)} options {:response-type response-type :credentials? auth}] (http/send! request options))) diff --git a/frontend/src/uxbox/util/websockets.cljs b/frontend/src/uxbox/util/websockets.cljs index db3999e6e3..f12a3ea0a0 100644 --- a/frontend/src/uxbox/util/websockets.cljs +++ b/frontend/src/uxbox/util/websockets.cljs @@ -25,13 +25,17 @@ (-close [_] "close websocket")) (defn url - [path] - (let [url (.parse Uri cfg/url)] - (.setPath url path) - (if (= (.getScheme url) "http") - (.setScheme url "ws") - (.setScheme url "wss")) - (.toString url))) + ([path] (url path {})) + ([path params] + (let [uri (.parse Uri cfg/url)] + (.setPath uri path) + (if (= (.getScheme uri) "http") + (.setScheme uri "ws") + (.setScheme uri "wss")) + (run! (fn [[k v]] + (.setParameterValue uri (name k) (str v))) + params) + (.toString uri)))) (defn open [uri]