git.fiddlerwoaroof.com
Browse code

feat(garagiste): improve object store protocol

Ed Langley authored on 01/09/2020 21:15:09
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
-