Browse code
feat(garagiste): improve object store protocol
Ed Langley authored on 01/09/2020 21:15:09
Showing 1 changed files
Showing 1 changed files
... | ... |
@@ -12,6 +12,7 @@ |
12 | 12 |
((%database :reader database |
13 | 13 |
:initform (make-hash-table)))) |
14 | 14 |
(defgeneric add-object (store object)) |
15 |
+(defgeneric objects-of-type (store type)) |
|
15 | 16 |
(defgeneric get-object (store type id)) |
16 | 17 |
(defgeneric ingest-jsonapi (store object)) |
17 | 18 |
(defgeneric stored-types (store)) |
... | ... |
@@ -29,7 +30,7 @@ |
29 | 30 |
((%id :accessor id) |
30 | 31 |
(%shipments :accessor shipments-a))) |
31 | 32 |
(defmethod add-object ((store jsonapi-datastore) (object batch)) |
32 |
- (setf (object :batches (id object)) |
|
33 |
+ (setf (object store :batches (id object)) |
|
33 | 34 |
(alexandria:alist-hash-table |
34 | 35 |
`(("relationships" . ,(mapcar (lambda (it) |
35 | 36 |
(alexandria:alist-hash-table |
... | ... |
@@ -77,7 +78,7 @@ |
77 | 78 |
(alexandria:once-only (h-t) |
78 | 79 |
`(let-lazy ,(loop for (name key . rest) in bindings |
79 | 80 |
collect `(,name (gethash ,key ,h-t ,@rest))) |
80 |
- ,@body))) |
|
81 |
+ ,@body))) |
|
81 | 82 |
|
82 | 83 |
(defmacro destructure-resource (resource &body body) |
83 | 84 |
`(hash-table-dbind ((id "id") |
... | ... |
@@ -86,19 +87,22 @@ |
86 | 87 |
(relationships "relationships" 'empty) |
87 | 88 |
(links "links" 'empty) |
88 | 89 |
(meta "meta" 'empty)) |
89 |
- ,resource |
|
90 |
- ,@body)) |
|
90 |
+ ,resource |
|
91 |
+ ,@body)) |
|
91 | 92 |
|
93 |
+(defmethod objects-of-type ((store jsonapi-datastore) type) |
|
94 |
+ (gethash (normalize-type type) |
|
95 |
+ (database store))) |
|
92 | 96 |
|
93 | 97 |
(defgeneric id-resolver-gf (store resource)) |
94 | 98 |
(defmethod id-resolver-gf ((store jsonapi-datastore) (resource hash-table)) |
95 | 99 |
(destructure-resource resource |
96 |
- (get-object store type id))) |
|
100 |
+ (get-object store type id))) |
|
97 | 101 |
(defmethod id-resolver-gf ((store jsonapi-datastore) (resource string)) |
98 | 102 |
(patmatch:let-pat* (((vector type id) (fwoar.string-utils:split "/" resource :count 1))) |
99 |
- (get-object store |
|
100 |
- type |
|
101 |
- id))) |
|
103 |
+ (get-object store |
|
104 |
+ type |
|
105 |
+ id))) |
|
102 | 106 |
|
103 | 107 |
(defun id-resolver (store) |
104 | 108 |
(lambda (resource) |
... | ... |
@@ -106,7 +110,7 @@ |
106 | 110 |
|
107 | 111 |
(defmethod add-object :before ((store recording-datastore) (object hash-table)) |
108 | 112 |
(vector-push-extend (destructure-resource object |
109 |
- (list :add-object id type attributes)) |
|
113 |
+ (list :add-object id type attributes)) |
|
110 | 114 |
(record store))) |
111 | 115 |
|
112 | 116 |
(defmethod stored-types ((store jsonapi-datastore)) |
... | ... |
@@ -120,30 +124,30 @@ |
120 | 124 |
(jsonapi "jsonapi" 'empty) |
121 | 125 |
(links "links" 'empty) |
122 | 126 |
(included "included" 'empty)) |
123 |
- object |
|
124 |
- (when (and (eql data 'empty) |
|
125 |
- (eql errors 'empty) |
|
126 |
- (eql meta 'empty)) |
|
127 |
- (warn "one of data, errors or meta must be specified in a toplevel object")) |
|
128 |
- (when (not (or (eql data 'empty) |
|
129 |
- (eql errors 'empty))) |
|
130 |
- (warn "only one of data or errors may be specified")) |
|
131 |
- (unless (when (eql data 'empty) |
|
132 |
- (not (eql included 'empty))) |
|
133 |
- (warn "included may only be specified if data is specified")) |
|
134 |
- (if (typep included 'sequence) |
|
135 |
- (add-object store included) |
|
136 |
- (unless (eql included 'empty) |
|
137 |
- (warn "included should be a sequence") |
|
138 |
- (add-object store included))) |
|
139 |
- (add-object store data))) |
|
127 |
+ object |
|
128 |
+ (when (and (eql data 'empty) |
|
129 |
+ (eql errors 'empty) |
|
130 |
+ (eql meta 'empty)) |
|
131 |
+ (warn "one of data, errors or meta must be specified in a toplevel object")) |
|
132 |
+ (when (not (or (eql data 'empty) |
|
133 |
+ (eql errors 'empty))) |
|
134 |
+ (warn "only one of data or errors may be specified")) |
|
135 |
+ (unless (when (eql data 'empty) |
|
136 |
+ (not (eql included 'empty))) |
|
137 |
+ (warn "included may only be specified if data is specified")) |
|
138 |
+ (if (typep included 'sequence) |
|
139 |
+ (add-object store included) |
|
140 |
+ (unless (eql included 'empty) |
|
141 |
+ (warn "included should be a sequence") |
|
142 |
+ (add-object store included))) |
|
143 |
+ (add-object store data))) |
|
140 | 144 |
|
141 | 145 |
(defun resource-identifier-p (resource) |
142 | 146 |
(destructure-resource resource |
143 |
- (and resource |
|
144 |
- (eql attributes 'empty) |
|
145 |
- (eql relationships 'empty) |
|
146 |
- (eql links 'empty)))) |
|
147 |
+ (and resource |
|
148 |
+ (eql attributes 'empty) |
|
149 |
+ (eql relationships 'empty) |
|
150 |
+ (eql links 'empty)))) |
|
147 | 151 |
|
148 | 152 |
(defmacro with-json-output ((stream &key indent) &body body) |
149 | 153 |
"Set up a JSON streaming encoder context on STREAM, then evaluate BODY." |
... | ... |
@@ -156,21 +160,21 @@ |
156 | 160 |
|
157 | 161 |
(defmethod add-object ((store jsonapi-datastore) (object hash-table)) |
158 | 162 |
(destructure-resource object |
159 |
- (let* ((store-ht (database store)) |
|
160 |
- (type-store (alexandria:ensure-gethash (normalize-type type) |
|
161 |
- store-ht |
|
162 |
- (make-hash-table :test 'equal))) |
|
163 |
- (old-value (gethash id type-store))) |
|
164 |
- (unless (eql relationships 'empty) |
|
165 |
- (loop for v being the hash-values of relationships |
|
166 |
- for data = (gethash "data" v) |
|
167 |
- when data do |
|
168 |
- (add-object store data))) |
|
169 |
- (if (resource-identifier-p old-value) |
|
170 |
- (setf (gethash id type-store) object) |
|
171 |
- (if (resource-identifier-p object) |
|
172 |
- old-value |
|
173 |
- (setf (gethash id type-store) object)))))) |
|
163 |
+ (let* ((store-ht (database store)) |
|
164 |
+ (type-store (alexandria:ensure-gethash (normalize-type type) |
|
165 |
+ store-ht |
|
166 |
+ (make-hash-table :test 'equal))) |
|
167 |
+ (old-value (gethash id type-store))) |
|
168 |
+ (unless (eql relationships 'empty) |
|
169 |
+ (loop for v being the hash-values of relationships |
|
170 |
+ for data = (gethash "data" v) |
|
171 |
+ when data do |
|
172 |
+ (add-object store data))) |
|
173 |
+ (if (resource-identifier-p old-value) |
|
174 |
+ (setf (gethash id type-store) object) |
|
175 |
+ (if (resource-identifier-p object) |
|
176 |
+ old-value |
|
177 |
+ (setf (gethash id type-store) object)))))) |
|
174 | 178 |
|
175 | 179 |
(defmethod add-object ((store jsonapi-datastore) (objects sequence)) |
176 | 180 |
(map 'list |
... | ... |
@@ -187,10 +191,10 @@ |
187 | 191 |
|
188 | 192 |
(defun summarize-objects-of-type (store type map-fn filter-fn) |
189 | 193 |
(serapeum:collecting |
190 |
- (serapeum:do-hash-table (k v (gethash type (database store))) |
|
191 |
- (declare (ignore k)) |
|
192 |
- (when (funcall filter-fn v) |
|
193 |
- (collect (funcall map-fn v)))))) |
|
194 |
+ (serapeum:do-hash-table (k v (gethash type (database store))) |
|
195 |
+ (declare (ignore k)) |
|
196 |
+ (when (funcall filter-fn v) |
|
197 |
+ (collect (funcall map-fn v)))))) |
|
194 | 198 |
|
195 | 199 |
(defun get-signin-page () |
196 | 200 |
(let ((c-j (make-instance 'drakma:cookie-jar))) |
... | ... |
@@ -201,8 +205,8 @@ |
201 | 205 |
|
202 | 206 |
(defun extract-auth-token (doc) |
203 | 207 |
(lquery:$1 (inline doc) |
204 |
- "form input[type=hidden][name=authenticity_token]" |
|
205 |
- (attr "value"))) |
|
208 |
+ "form input[type=hidden][name=authenticity_token]" |
|
209 |
+ (attr "value"))) |
|
206 | 210 |
|
207 | 211 |
(defvar *csrf-token* nil) |
208 | 212 |
(defun request-json (url cj &rest r &key (method :get) (parameters ())) |
... | ... |
@@ -243,12 +247,12 @@ |
243 | 247 |
(user-home (login-garagiste auth-token cookie-jar))) |
244 | 248 |
(setf *csrf-token* |
245 | 249 |
(lquery:$1 (initialize user-home) |
246 |
- "meta[name=csrf-token]" |
|
247 |
- (attr "content"))) |
|
250 |
+ "meta[name=csrf-token]" |
|
251 |
+ (attr "content"))) |
|
248 | 252 |
(values (yason:parse |
249 | 253 |
(lquery:$1 (initialize user-home) |
250 |
- "div[data-react-class=Portal]" |
|
251 |
- (attr "data-react-props"))) |
|
254 |
+ "div[data-react-class=Portal]" |
|
255 |
+ (attr "data-react-props"))) |
|
252 | 256 |
cookie-jar)))) |
253 | 257 |
|
254 | 258 |
(defun user-id (ud) |
... | ... |
@@ -263,6 +267,10 @@ |
263 | 267 |
(request-json (format nil "https://app.garagiste.com/customers/~a/requests/" user-id) |
264 | 268 |
cookie-jar)) |
265 | 269 |
|
270 |
+(defun offerings (user-id cookie-jar) |
|
271 |
+ (offering-json (format nil "https://app.garagiste.com/customers/~a/offerings/" user-id) |
|
272 |
+ cookie-jar)) |
|
273 |
+ |
|
266 | 274 |
(defun orders (user-id cookie-jar) |
267 | 275 |
(request-json (format nil "https://app.garagiste.com/customers/~a/orders/" user-id) |
268 | 276 |
cookie-jar)) |
... | ... |
@@ -273,27 +281,26 @@ |
273 | 281 |
|
274 | 282 |
|
275 | 283 |
(data-lens:defalias simplify-order |
276 |
- (data-lens:<>1 (lambda (it) |
|
277 |
- (mapcan (lambda (key el) |
|
278 |
- (list key el)) |
|
279 |
- '(:unit-price :count :total-price :offering-name) |
|
280 |
- it)) |
|
281 |
- (data-lens:juxt (data-lens:key "unit_price") |
|
282 |
- (data-lens:key "count") |
|
283 |
- (data-lens:key "total_price") |
|
284 |
- (data-lens:key "offering_name")))) |
|
284 |
+ (data-lens:<>1 (lambda (it) |
|
285 |
+ (mapcan (lambda (key el) |
|
286 |
+ (list key el)) |
|
287 |
+ '(:unit-price :count :total-price :offering-name) |
|
288 |
+ it)) |
|
289 |
+ (data-lens:juxt (data-lens:key "unit_price") |
|
290 |
+ (data-lens:key "count") |
|
291 |
+ (data-lens:key "total_price") |
|
292 |
+ (data-lens:key "offering_name")))) |
|
285 | 293 |
|
286 | 294 |
(data-lens:defalias summarize-orders |
287 |
- (data-lens:<>1 (data-lens:denest) |
|
288 |
- (data-lens:transform-head |
|
289 |
- (lambda (it) |
|
290 |
- (list :id it))) |
|
291 |
- (data-lens:juxt (data-lens:juxt (data-lens:<>1 'normalize-type |
|
292 |
- (data-lens:key "type")) |
|
293 |
- (data-lens:key "id")) |
|
294 |
- (data-lens:<>1 'simplify-order |
|
295 |
- (data-lens:key "attributes"))))) |
|
295 |
+ (data-lens:<>1 (data-lens:denest) |
|
296 |
+ (data-lens:transform-head |
|
297 |
+ (lambda (it) |
|
298 |
+ (list :id it))) |
|
299 |
+ (data-lens:juxt (data-lens:juxt (data-lens:<>1 'normalize-type |
|
300 |
+ (data-lens:key "type")) |
|
301 |
+ (data-lens:key "id")) |
|
302 |
+ (data-lens:<>1 'simplify-order |
|
303 |
+ (data-lens:key "attributes"))))) |
|
296 | 304 |
|
297 | 305 |
(defclass test-store (jsonapi-datastore recording-datastore) |
298 | 306 |
()) |
299 |
- |