-
Notifications
You must be signed in to change notification settings - Fork 0
/
api.cljs
161 lines (140 loc) · 4.93 KB
/
api.cljs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
(ns dgknght.app-lib.api
(:refer-clojure :exclude [get])
(:require-macros [cljs.core.async.macros :refer [go]])
(:require [clojure.string :as string]
[cljs.core.async :refer [<!]]
[cljs-http.client :as http]
[camel-snake-kebab.core :refer [->kebab-case-keyword]]
[camel-snake-kebab.extras :refer [transform-keys]]
[lambdaisland.uri :refer [map->query-string]]
[goog.string :as gstr]
[dgknght.app-lib.core :refer [present?]]
[dgknght.app-lib.web :as web]))
(defonce defaults (atom {}))
(defn- success?
[{:keys [status]}]
(<= 200 status 299))
(defn- log-failure
[response action model]
(.error js/console
(gstr/format
"Unable to %s the model %s: %s"
action
(prn-str model)
(prn-str response)))
response)
(defn path
[& segments]
(apply web/path (concat [:api] segments)))
(defn append-query-string
[path criteria]
(if (empty? criteria)
path
(let [query-string (map->query-string criteria)]
(str path "?" query-string))))
(defn header
[req k v]
(update-in (or req {}) [:headers] assoc k v))
(defn content-type
[req content-type]
(header req "Content-Type" content-type))
(defn accept
[req content-type]
(header req "Accept" content-type))
(defn json-params
[req params]
(assoc req :json-params params))
(defn multipart-params
[req params]
(assoc req :multipart-params params))
(defn request
([] (request {}))
([options]
(-> {}
(content-type "application/json")
(accept "application/json")
(merge @defaults options))))
(defn parse-json
[json]
(transform-keys ->kebab-case-keyword
(js->clj (.parse js/JSON json))))
(def ^:private error-map
{404 "not found"
403 "forbidden"
401 "unauthorized"
400 "bad request"
500 "server error"})
(defn extract-error
[{:keys [body status]}]
(if-let [parsed (when (present? body)
(if (string? body)
(parse-json body)
body))]
(if-let [val-errors (:dgknght.app-lib.validation/errors parsed)]
(string/join ", " (-> val-errors vals flatten))
(or (some #(% parsed) [:error
:message])
body))
(get-in error-map [status] (str "Unknown error: " status))))
(defn- respond
[response action path success-fn error-fn]
(if (success? response)
(-> response :body success-fn)
(-> response
(log-failure action path)
extract-error
error-fn)))
(defn get
([path success-fn error-fn]
(get path {} success-fn error-fn))
([path criteria success-fn error-fn]
(get path criteria {} success-fn error-fn))
([path criteria options success-fn error-fn]
(go (let [req (request options)
response (<! (http/get (append-query-string path criteria)
req))]
(respond response :get path success-fn error-fn)))))
(defn post
([path success-fn error-fn] (post path {} success-fn error-fn))
([path model success-fn error-fn] (post path model {} success-fn error-fn))
([path model options success-fn error-fn]
(go (let [response (<! (http/post path (-> options
request
(json-params model))))]
(respond response :post path success-fn error-fn)))))
(defn post-files
"Post files to the server. The files can be extracted from a drop event like this:
(defn- handle-drop
[e page-state]
(.preventDefault e)
(let [file-list (aget e \"dataTransfer\" \"files\")
file-count (aget file-list \"length\")
files (mapv #(.item file-list %) (range file-count))]
(swap! page-state update-in [:invitations :import-data :files] (fnil concat []) files)))"
([path content success-fn error-fn]
(post-files path content {} success-fn error-fn))
([uri content options success-fn error-fn]
(go (let [{:keys [success body]
:as response} (<! (http/post uri (request (merge options {:multipart-params content}))))]
(if success
(success-fn body)
(error-fn (extract-error response)))))))
(defn patch
([path model success-fn error-fn]
(patch path model {} success-fn error-fn))
([path model options success-fn error-fn]
(go (let [response (<! (http/patch path (-> options
request
(json-params model))))]
(respond response :patch path success-fn error-fn)))))
(defn delete
([path success-fn error-fn]
(delete path {} success-fn error-fn))
([path options success-fn error-fn]
(go (let [response (<! (http/delete path (request options)))]
(if (success? response)
(success-fn)
(-> response
(log-failure "delete" path)
extract-error
error-fn))))))