-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
241 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,17 @@ | ||
/target | ||
/classes | ||
/checkouts | ||
profiles.clj | ||
pom.xml | ||
pom.xml.asc | ||
*.jar | ||
*.class | ||
/lib/ | ||
/classes/ | ||
/target/ | ||
/checkouts/ | ||
.lein-deps-sum | ||
.lein-repl-history | ||
.lein-plugins/ | ||
.lein-failures | ||
.nrepl-port | ||
.cpcache/ | ||
/.lein-* | ||
/.nrepl-port | ||
/.prepl-port | ||
.hgignore | ||
.hg/ | ||
|
||
# IntelliJ | ||
|
||
*.iml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,14 @@ | ||
# remongo | ||
# remongo | ||
|
||
A Clojure library synchronizing Re-frame DB's with MongoDB via Realm | ||
|
||
## Usage | ||
|
||
FIXME | ||
|
||
## License | ||
|
||
"Copyright" © 2021 David Bergman | ||
|
||
This program and the accompanying materials are made available under the | ||
terms of the Unlicense license. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Introduction to remongo | ||
|
||
TODO: write [great documentation](http://jacobian.org/writing/what-to-write/) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
(defproject remongo "0.1.0" | ||
:description "ClojureScript library synchronizing Re-frame DB's and MongoDB via Realm" | ||
:url "http://example.com/FIXME" | ||
:license {:name "Unlicense" | ||
:url "https://unlicense.org/"} | ||
:dependencies [[org.clojure/clojure "1.10.1"] | ||
[org.clojure/core.async "1.3.618"] | ||
[com.taoensso/timbre "5.1.2" :exclusions [io.aviso/pretty com.taoensso/encore]]] | ||
:repl-options {:init-ns remongo.core}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
(ns remongo.mongo | ||
"Wrapper and utilities for dealing with MongoDB, including in the context of Reframe" | ||
(:require | ||
[cljs.core.async :as async :refer [go <!]] | ||
[cljs.core.async.interop :refer-macros [<p!]] | ||
[taoensso.timbre :as timbre] | ||
["realm-web" :as realm] | ||
[psf.web.utils :as utils] | ||
[psf.sim.utils :refer [dissoc-in]])) | ||
|
||
(def REALM-APP (atom nil)) | ||
(def REALM-CRED (atom nil)) | ||
(def REALM-MONGO-CLIENT (atom nil)) | ||
|
||
|
||
(defn ^realm/User current-user | ||
"The currently logged in user, if any" | ||
[] | ||
(.-currentUser ^realm/App @REALM-APP)) | ||
|
||
(defn <login | ||
"Login using default user, returning a channel returning either an ID or nil" | ||
[] | ||
(timbre/info "About to get credentials") | ||
(let [^realm/App app @REALM-APP | ||
cred (.anonymous realm/Credentials) | ||
cred' (utils/string-object cred)] | ||
(timbre/info "Got credentials:" cred') | ||
(reset! REALM-CRED cred) | ||
(timbre/info "About to login") | ||
(go | ||
(try | ||
(let [user (<p! (.logIn app cred))] | ||
(timbre/info "Logged in with user " user " and ID: " (.-id (current-user))) | ||
user) | ||
(catch js/Error err | ||
(timbre/error "Login error: " err)))))) | ||
|
||
(defn init-app | ||
"Initialize the Realm app" | ||
[app-id] | ||
;; Create a Realm app proxy | ||
(timbre/info "About to create Realm app") | ||
(let [^realm/App app (realm/App. (clj->js {:id app-id})) | ||
app' (utils/string-object app)] | ||
(timbre/info "Created Realm app: " app') | ||
(reset! REALM-APP app))) | ||
|
||
;; Neeed to turn off inference temporarily, due to the 'mongoClient' not being found | ||
(set! *warn-on-infer* false) | ||
|
||
(defn mongo-collection | ||
"Get the MongoDB collection for the given name" | ||
[^js/String db-name ^js/String coll-name] | ||
(-> @REALM-MONGO-CLIENT | ||
(.db db-name) | ||
(.collection coll-name))) | ||
|
||
(defn- make-false | ||
"Make anything falsey into false" | ||
[x] | ||
(or x false)) | ||
|
||
(defn <findOne | ||
"Find one MongoDB document, returning a channel to the result, with false being the value if not found" | ||
[db-name coll-name condition] | ||
;; NOTE: we need to handle falsey values, and make sure it is indeed false, since | ||
;; we can't put nil onto channels | ||
(go (-> (mongo-collection db-name coll-name) (.findOne (clj->js condition)) (<p!) js->clj make-false))) | ||
|
||
(defn <find | ||
"Find many MongoDB documents for given collection name and condition, returning a channel holding sequence" | ||
[db-name coll-name condition] | ||
(go (-> (mongo-collection db-name coll-name) (.find (clj->js condition)) (<p!) js->clj))) | ||
|
||
(defn <insertMany | ||
"Insert many documents into a MongoDB collection, returning a channel of result" | ||
[db-name coll-name docs] | ||
(go | ||
(if (empty? docs) | ||
(do | ||
(timbre/warn "Trying to insert empty sequence into collection" coll-name) | ||
true) | ||
(-> (mongo-collection db-name coll-name) (.insertMany (clj->js docs)) (<p!) js->clj)))) | ||
|
||
(defn <deleteMany | ||
"Delete many documents from a MongoDB collection, given a condition, returning result in channel" | ||
[db-name coll-name condition] | ||
(go (-> (mongo-collection db-name coll-name) (.deleteMany (clj->js condition)) (<p!) js->clj))) | ||
|
||
(defn <updateOne | ||
"Update one document in a MongoDB collection, given condition and options, returning result in channel" | ||
[db-name coll-name condition doc & {:keys [upsert] :or {upsert true}}] | ||
(let [opts {:upsert upsert} | ||
props {:$set (clj->js doc)}] | ||
(go (-> (mongo-collection db-name coll-name) | ||
(.updateOne (clj->js condition) | ||
(clj->js props) | ||
(clj->js opts)) | ||
(<p!) js->clj)))) | ||
|
||
(defn <init | ||
"Initialize the Realm connection, returning a channel with either user or nil" | ||
[app-id app-variant] | ||
(init-app app-id) | ||
(go | ||
(let [^realm/User user (<! (<login)) | ||
_ (timbre/info "Getting user from channel: " user " with ID " (when user (.-id user))) | ||
^realm/MongoDB client (.mongoClient user app-variant)] | ||
(timbre/info "Got MongoDB client: " client) | ||
(reset! REALM-MONGO-CLIENT client) | ||
user))) | ||
|
||
(defn <sync-save-layer | ||
"Use a sync layer to save extracts from Reframe DB to MongoDB, returning the updated Reframd DB" | ||
[sync-info db-chan layer] | ||
(go | ||
(let [db (<! db-chan) | ||
collection (:collection layer) | ||
db-name (or (:db layer) (:db sync-info))] | ||
(timbre/info "<sync-save-layer with layer" layer) | ||
(case (:kind layer) | ||
:single (let [_res (<! (<updateOne db-name collection (:keys layer) db))] | ||
(dissoc-in db (:path layer))) | ||
:many (let [path-value (get-in db (:path layer)) | ||
path-key (:path-key layer) | ||
items (if path-key (vals path-value) path-value) | ||
_ (timbre/info "Got" (count items) "items for collection" collection) | ||
;; TODO: we are currently always deleting and reinserting these documents | ||
del-result (<! (<deleteMany db-name collection {})) | ||
_ (timbre/info "Deleting from DB" db-name "and collection" collection | ||
"with result:" (js->clj del-result)) | ||
ins-result (<! (<insertMany db-name collection items)) | ||
_ (timbre/info "Inserting into DB" db-name "and collection" collection | ||
"with result:" (js->clj ins-result))] | ||
(timbre/info "Trying to dissoc" (:path layer) "from DB") | ||
(dissoc-in db (:path layer))) | ||
(do (timbre/error "Trying to save with invalid sync layer:" layer) db))))) | ||
|
||
(defn make-string | ||
"Turn a single keyword into a string and a sequence of keywords into strings" | ||
[k] | ||
(cond | ||
(seqable? k) (into (empty k) (map make-string k)) | ||
(keyword? k) (name k) | ||
:else k)) | ||
|
||
(defn <sync-load-layer | ||
"Load data from MongoDB according to sync layer, adding on Reframe DB we get from channel" | ||
[sync-info db-chan layer] | ||
(go | ||
(let [db (<! db-chan) | ||
collection (:collection layer) | ||
db-name (or (:db layer) (:db sync-info)) | ||
path (make-string (:path layer)) | ||
path-key (make-string (:path-key layer)) | ||
_ (timbre/info "<sync-load-layer about to load layer" layer "with path" path) | ||
db' | ||
(case (:kind layer) | ||
:single (let [obj (<! (<findOne db-name collection (:keys layer))) | ||
db' (cond | ||
(false? obj) (do (timbre/warn "<sync-load-layer could not find any" collection) | ||
db) | ||
;; If we have an empty path, we replace the whole Reframe DB | ||
(empty? path) obj | ||
:else (assoc-in db path obj))] | ||
db') | ||
:many (let [items (<! (<find db-name collection {})) | ||
_ (timbre/info "<sync-load-layer got" (count items) "items from collection" collection | ||
"using path key" path-key "and path" path) | ||
;; We have to fit the items into the Reframe DB, either as is, or as a map | ||
path-value (if path-key (into {} (map (fn [item] [(get item path-key) item]) items)) | ||
items) | ||
_ (timbre/info "Keys are " (when (map? path-value) (keys path-value))) | ||
db' (assoc-in db path path-value)] | ||
db') | ||
(do (timbre/error "Trying to load with invalid sync layer:" layer) db))] | ||
db'))) | ||
|
||
(defn <sync-save | ||
"Save a Reframe DB to MongoDB intelligently, returning the fitered (often empty) Reframe DB" | ||
[db opts] | ||
(let [ch (async/chan) | ||
layers (:layers opts)] | ||
(async/put! ch db) | ||
(reduce (partial <sync-save-layer opts) ch (reverse layers)))) | ||
|
||
(defn <sync-load | ||
"Load a Reframe DB from MongoDB intelligently, returning the actual Reframe DB" | ||
[opts] | ||
(let [ch (async/chan) | ||
layers (:layers opts)] | ||
(timbre/info "<sync-load with layers" layers) | ||
(async/put! ch {}) | ||
(reduce (partial <sync-load-layer opts) ch layers))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
(ns remongo.core-test | ||
(:require [clojure.test :refer :all] | ||
[remongo.core :refer :all])) | ||
|
||
(deftest a-test | ||
(testing "FIXME, I fail." | ||
(is (= 0 1)))) |