├── doc └── intro.md ├── .gitignore ├── project.clj ├── test └── taika │ └── core_test.clj ├── src └── taika │ ├── auth.clj │ └── core.clj └── README.md /doc/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction to taika 2 | 3 | TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .lein-repl-history 13 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject com.zenboxapp/taika "0.1.4" 2 | :description "A wrapper around the Firebase REST API" 3 | :url "http://www.github.com/cloudfuji/taika" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.6.0"] 7 | [clj-http "1.0.0"] 8 | [cheshire "5.3.1"] 9 | [com.firebase/firebase-token-generator "1.0.2"] 10 | [midje "1.6.3"]] 11 | :plugins [[lein-midje "3.0.0"]]) -------------------------------------------------------------------------------- /test/taika/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns taika.core-test 2 | (:require [midje.sweet :refer :all] 3 | [taika.core :as taika])) 4 | 5 | ; Firebase database URL used in tests 6 | (def url "https://test-db.firebaseIO.com/users.json") 7 | 8 | (fact "`db-url` should return a proper Firebase database URL" 9 | (taika/db-url "test-db" "/users") => url) 10 | 11 | (fact "`request` should call client method with correct data" 12 | (taika/request --method-- "test-db" "/users" {:name "Boobin"}) => {"success" true} 13 | (provided 14 | (--method-- url {:query-params {:pretty-print true} :body "{\"name\":\"Boobin\"}"} {:as :json}) => {:body "{\"success\":true}"})) -------------------------------------------------------------------------------- /src/taika/auth.clj: -------------------------------------------------------------------------------- 1 | (ns taika.auth 2 | (:require [clojure.string :as string] 3 | [clj-http.client :as client] 4 | [cheshire.core :as json]) 5 | (:import [com.firebase.firebase-token-generator.security.token] 6 | [org.json.JSONOBject])) 7 | 8 | (defn token-generator [secret-key] 9 | (com.firebase.security.token.TokenGenerator. secret-key)) 10 | 11 | (defn create-token [token-generator auth-data & [admin?]] 12 | (let [token-options (doto (com.firebase.security.token.TokenOptions.) 13 | (.setAdmin (or admin? false))) 14 | auth-json (org.json.JSONObject. (java.util.HashMap. auth-data))] 15 | (.createToken token-generator auth-json token-options))) 16 | -------------------------------------------------------------------------------- /src/taika/core.clj: -------------------------------------------------------------------------------- 1 | (ns taika.core 2 | (:require [clojure.string :as string] 3 | [clj-http.client :as client] 4 | [cheshire.core :as json] 5 | [taika.auth :as auth]) 6 | (:refer-clojure :exclude [read])) 7 | 8 | (defn recursive-merge 9 | "Recursively merge hash maps." 10 | [a b] 11 | (if (and (map? a) (map? b)) 12 | (merge-with recursive-merge a b) 13 | (if (map? a) a b))) 14 | 15 | (def firebase-tld "firebaseIO.com") 16 | 17 | (defn db-base-url [db-name] 18 | "Returns a proper Firebase base url given a database name" 19 | (str "https://" db-name "." firebase-tld)) 20 | 21 | (defn db-url [db-name path] 22 | "Returns a proper Firebase url given a database name and path" 23 | (str (db-base-url db-name) path ".json")) 24 | 25 | (defn request [method db-name path data & [auth options]] 26 | "Request method used by other functions." 27 | (let [request-options (reduce recursive-merge [{:query-params {:pretty-print true}} 28 | (when auth {:query-params {:auth auth}}) 29 | (when (not (nil? data)) {:body (json/generate-string data)}) 30 | options]) 31 | url (db-url db-name path)] 32 | (-> (method url request-options {:as :json}) 33 | :body 34 | json/parse-string))) 35 | 36 | (defn write! [db-name path data & [auth options]] 37 | "Creates or destructively replaces data in a Firebase database at a given path" 38 | (request client/put db-name path data auth options)) 39 | 40 | (defn update! 41 | "Updates data in a Firebase database at a given path via destructively merging." 42 | [db-name path data & [auth options]] 43 | (request client/patch db-name path data auth options)) 44 | 45 | (defn push! 46 | "Appends data to a list in a Firebase db at a given path. See https://www.firebase.com/docs/javascript/firebase/push.html for more information." 47 | [db-name path data & [auth options]] 48 | (request client/post db-name path data auth options)) 49 | 50 | (defn remove! [db-name path & [auth options]] 51 | "Destroys data from Firebase database at a given path" 52 | (request client/delete db-name path nil auth options)) 53 | 54 | (defn read 55 | "Retrieves data from Firebase database at a given path" 56 | [db-name path & [auth queury-params options]] 57 | (request client/get db-name path nil auth (merge {:query-params (or queury-params {})} options))) 58 | 59 | (defn update-rules! 60 | "Updates security rules on Firebase - See https://www.firebase.com/docs/security/security-rules.html for more information. 61 | WARNING: Completely replaces existing security rules." 62 | [db-name secret-key rule-update] 63 | (client/put (db-url db-name "/.settings/rules") {:query-params {:auth secret-key} 64 | :body (json/generate-string rule-update {:pretty true})})) 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Taika (大火) 2 | 3 | A Clojure wrapper around the Firebase REST API. 4 | 5 | **Note that this is not a Firebase client in Clojure, it's just the REST API** 6 | 7 | ## Installation 8 | [![Clojars Project](http://clojars.org/com.zenboxapp/taika/latest-version.svg)](http://clojars.org/com.zenboxapp/taika) 9 | 10 | ## Usage 11 | 12 | Taika tries to stay close to the wording used in the [Firebase REST Documentation](https://www.firebase.com/docs/rest-api.html). Make sure that either 1.) you've read the security documentation and have configured things appropriately, or 2.) have read/write access turned on for everything while testing. Security will likely be the biggest pain while getting started. 13 | 14 | Add Taika to you project 15 | 16 | ```clojure 17 | (require '[taika.core :as taika] 18 | '[taika.auth :as taika-auth]) 19 | ``` 20 | 21 | Create a token generator and an auth token: 22 | 23 | ```clojure 24 | (def user-auth-token 25 | (let [token-generator (taika-auth/token-generator "SECRET-KEY") 26 | auth-data {:username "taika" :team_id 100} 27 | admin? false] 28 | (taika-auth/create-token token-generator auth-data admin?))) 29 | ``` 30 | See the [Custom Token Generation](https://www.firebase.com/docs/security/custom-login.html) to read more about the token structure, and the [Security Auth Variable](https://www.firebase.com/docs/security/rule-expressions/auth.html) page to understand what to put in the auth-data map for Firebase's [Security Rules](https://www.firebase.com/docs/security/security-rules.html). 31 | 32 | Create a new entry in the Firebase database (note we're using 10 as the customer's id/handle here): 33 | 34 | ```clojure 35 | (taika/write! "db-name" "/customers" {10 {:name "Samuel Calans"}} user-auth-token) 36 | ; => {"10" {"name" "Samuel Calans"}} 37 | ``` 38 | 39 | The user-auth-token is optional, and only needed if your security rules require it, for example with reading data: 40 | 41 | ```clojure 42 | (taika/read "db-name" "/customers/10") 43 | ; => {"name" "Samuel Calans"} 44 | ``` 45 | 46 | Update (merge) a given entry: 47 | 48 | ```clojure 49 | (taika/update! "db-name" "/customers" {10 {:name "Samuel Hayes" :area "SF"}} user-auth-token) 50 | ; => {"10" {"name" "Samuel Hayes" :area "SF"}} 51 | ``` 52 | 53 | Push to a list (see ["Lists of Data"](https://www.firebase.com/docs/managing-lists.html): 54 | 55 | ```clojure 56 | (taika/push! "db-name" "/example-lists" {:firebase true} user-auth-token) 57 | ; => {"name" "-IoZ3DZlTTQIkR0c7iVK"} 58 | ``` 59 | 60 | Destructively update (replace) an entry: 61 | 62 | ```clojure 63 | (taika/write! "db-name" "/customers" {10 {:name "Noah Maranchi"}} user-auth-token) 64 | ; => {"10" {:name "Noah Maranchi"}} 65 | ``` 66 | 67 | Destroy data: 68 | 69 | ```clojure 70 | (taika/destroy! "db-name" "/customers/10" user-auth-token) 71 | ; => nil 72 | ``` 73 | 74 | Finally, you can update the security rules from Taika as well. Given a Clojure map, Taika will replace the **ENTIRE** rule document with it. This method requires your secret key as well. Be careful with this! 75 | 76 | ```clojure 77 | (taike/update-rules! "db-name" "SECRET-KEY" {:rules {:customers 78 | {:write true 79 | :read true}}}) 80 | ``` 81 | 82 | ## TODO 83 | 84 | * Use `clj-http`'s connection pooling to speed up serial requests. Right now it's not too slow, but could be faster. 85 | * Write tests - not sure how to approach this for a purely 3rd-party wrapper. Any suggestions/pull-requests readily welcome. 86 | 87 | ## License 88 | 89 | Copyright © 2013 Bushido Inc 90 | 91 | Distributed under the Eclipse Public License, the same as Clojure. 92 | --------------------------------------------------------------------------------