diff --git a/backend/deps.edn b/backend/deps.edn index 2580d01fc0..ba2aa49759 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -20,6 +20,7 @@ ;; 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"} vertx-clojure/vertx {:local/root "vendor/vertx" diff --git a/backend/src/uxbox/config.clj b/backend/src/uxbox/config.clj index 88a7fc3f69..0b4661e15b 100644 --- a/backend/src/uxbox/config.clj +++ b/backend/src/uxbox/config.clj @@ -25,6 +25,8 @@ :database-uri "postgresql://127.0.0.1/uxbox" :database-username "uxbox" :database-password "uxbox" + + :redis-uri "redis://redis/0" :media-directory "resources/public/media" :assets-directory "resources/public/static" :media-uri "http://localhost:6060/media/" @@ -44,6 +46,7 @@ (s/def ::database-username (s/nilable ::us/string)) (s/def ::database-password (s/nilable ::us/string)) (s/def ::database-uri ::us/string) +(s/def ::redis-uri ::us/string) (s/def ::assets-uri ::us/string) (s/def ::assets-directory ::us/string) (s/def ::media-uri ::us/string) diff --git a/backend/src/uxbox/redis.clj b/backend/src/uxbox/redis.clj new file mode 100644 index 0000000000..8c8ae0f594 --- /dev/null +++ b/backend/src/uxbox/redis.clj @@ -0,0 +1,49 @@ +;; 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.redis + (:refer-clojure :exclude [run!]) + (:require + [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]) + (:import + java.lang.AutoCloseable)) + +;; --- Connection Handling & State + +(defn- create-client + [config] + (let [uri (:redis-uri config "redis://redis/0")] + (log/info "creating redis client with" uri) + (redis/client uri))) + +(defstate client + :start (create-client cfg/config) + :stop (.close ^AutoCloseable client)) + +(defstate conn + :start (redis/connect client) + :stop (.close ^AutoCloseable conn)) + +;; --- API FORWARD + +(defmacro with-conn + [& args] + `(redis/with-conn ~@args)) + +(defn run! + [conn cmd params] + (let [ctx (vu/get-or-create-context system)] + (-> (redis/run! conn cmd params) + (vu/handle-on-context ctx)))) diff --git a/backend/src/uxbox/util/redis.clj b/backend/src/uxbox/util/redis.clj new file mode 100644 index 0000000000..4fab67410e --- /dev/null +++ b/backend/src/uxbox/util/redis.clj @@ -0,0 +1,99 @@ +;; 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.redis + "Asynchronous posgresql client." + (:refer-clojure :exclude [get set run!]) + (:require + [promesa.core :as p]) + (:import + io.lettuce.core.RedisClient + io.lettuce.core.RedisURI + io.lettuce.core.codec.StringCodec + io.lettuce.core.api.async.RedisAsyncCommands + io.lettuce.core.api.StatefulRedisConnection + )) + +(defrecord Client [conn uri] + java.lang.AutoCloseable + (close [_] + (.shutdown ^RedisClient conn))) + +(defrecord Connection [cmd conn] + java.lang.AutoCloseable + (close [_] + (.close ^StatefulRedisConnection conn))) + +(defn client + [uri] + (->Client (RedisClient/create) (RedisURI/create uri))) + +(defn connect + [client] + (let [^RedisURI uri (:uri client) + ^RedisClient conn (:conn client) + ^StatefulRedisConnection conn' (.connect conn StringCodec/UTF8 uri)] + (->Connection (.async conn') conn'))) + +(declare impl-with-conn) + +(defmacro with-conn + [[csym sym] & body] + `(impl-with-conn ~sym (fn [~csym] ~@body))) + +(defn impl-with-conn + [client f] + (let [^RedisURI uri (:uri client) + ^RedisClient conn (:conn client)] + (-> (.connectAsync conn StringCodec/UTF8 uri) + (p/then (fn [^StatefulRedisConnection conn] + (let [cmd (.async conn) + conn (->Connection cmd conn)] + (-> (p/do! (f conn)) + (p/handle (fn [v e] + (.close conn) + (if e + (throw e) + v)))))))))) + +(defn- resolve-to-bool + [v] + (if (= v 1) + true + false)) + +(defmulti impl-run (fn [conn cmd parmas] cmd)) + +(defn run! + [conn cmd params] + (let [^RedisAsyncCommands conn (:cmd conn)] + (impl-run conn cmd params))) + +(defmethod impl-run :get + [conn _ {:keys [key]}] + (.get ^RedisAsyncCommands conn ^String key)) + +(defmethod impl-run :set + [conn _ {:keys [key val]}] + (.set ^RedisAsyncCommands conn ^String key ^String val)) + +(defmethod impl-run :smembers + [conn _ {:keys [key]}] + (-> (.smembers ^RedisAsyncCommands conn ^String key) + (p/then' #(into #{} %)))) + +(defmethod impl-run :sadd + [conn _ {:keys [key val]}] + (let [keys (into-array String [val])] + (-> (.sadd ^RedisAsyncCommands conn ^String key ^"[S;" keys) + (p/then resolve-to-bool)))) + +(defmethod impl-run :srem + [conn _ {:keys [key val]}] + (let [keys (into-array String [val])] + (-> (.srem ^RedisAsyncCommands conn ^String key ^"[S;" keys) + (p/then resolve-to-bool)))) + diff --git a/backend/tests/user.clj b/backend/tests/user.clj index 2c1f43b82e..c94a8636eb 100644 --- a/backend/tests/user.clj +++ b/backend/tests/user.clj @@ -23,6 +23,7 @@ [promesa.exec :as px] [uxbox.migrations] [uxbox.db :as db] + [uxbox.redis :as rd] [uxbox.util.storage :as st] [uxbox.util.time :as tm] [uxbox.util.blob :as blob]