git.fiddlerwoaroof.com
Browse code

Refactoring and angularizing

- Converted feed addition to angular

- Streamlined the various serialization methods
- now, there is a "serialize" function in rss.lisp
- This function generates a alist by default
- But, it's a bit tricky: you can pass two functions to it, the
first transforms the alist the second transforms the pairs in the
alist

- The serialization improvements helped reduce duplication between the
to-json helpers and the to-db helpers

fiddlerwoaroof authored on 20/09/2015 06:42:03
Showing 2 changed files
... ...
@@ -28,7 +28,6 @@
28 28
 (load "tables.lisp")
29 29
 
30 30
 (defpackage :whitespace.rss
31
-  (:shadow "to-json")
32 31
   (:use #:cl #:alexandria #:postmodern #:lquery #:cl-syntax #:cl-annot.syntax #:cl-annot.class
33 32
         #:whitespace.tables #:iterate))
34 33
 (load "rss.lisp")
... ...
@@ -156,9 +155,9 @@
156 155
         (cl-markup:raw
157 156
           (unless demo
158 157
             (cl-markup:markup
159
-              (:form :action "/feeds/add" :name "add-form" :id "add-form" :method "post"
160
-               (:input :type "text" :name "url" :class "urltext" "")
161
-               (:input :type "hidden" :name "api" :value "no" "")
158
+              (:form :name "add-form" :id "add-form" :ng-submit "addFeed()"
159
+               (:input :type "text" :name "url" :class "urltext" :ng-model "addForm.url" :placeholder "http://example.com/feed.rss . . ." "")
160
+               (:input :type "hidden" :name "api" :value "yes" "")
162 161
                (:button :type "submit" :class "fsub" "+")))))
163 162
         (:img :ng-class "{spinner: true, hide: feeds.result !== undefined}" :src "/static/images/spinner.gif" "")
164 163
         (:section :ng-class "{feed: true, closed: !feed.closed}" :ng-repeat "feed in feeds.result"
... ...
@@ -190,25 +189,27 @@
190 189
 
191 190
 ; ; ;  Ultimately, this will only serialize the feed if the client
192 191
 (cl-oid-connect:def-route ("/feeds/add" (params) :method :post :app *app*)
193
-  (ningle.context:with-context-variables (session)
194
-    (cl-oid-connect:require-login
195
-      (let ((user-info (car (gethash :app-user session)))
196
-            (result '(302 (:location "/")))
197
-            (api (string= (cl-oid-connect:assoc-cdr "api" params) "yes"))) 
192
+  (ningle.context:with-context-variables (session) 
193
+    (let ((user-info (car (gethash :app-user session)))
194
+          (result '(302 (:location "/")))
195
+          (api (string= (cl-oid-connect:assoc-cdr "api" params 'string=) "yes")) 
196
+          (url (cl-oid-connect:assoc-cdr "url" params 'string=)) 
197
+          (plump-parser:*tag-dispatchers* plump-parser:*xml-tags*))
198
+      (cl-oid-connect:require-login
198 199
         (when (neither-null params user-info)
199 200
           (handler-case
200
-            (let* ((url (cl-oid-connect:assoc-cdr "url" params 'string=))
201
-                   (feed (drakma:http-request url))
202
-                   (plump-parser:*tag-dispatchers* plump-parser:*xml-tags*)
203
-                   (doc (plump:parse feed)))
204
-              (store-feed doc (slot-value user-info 'id))
201
+            (let* ((doc (plump:parse (drakma:http-request url)))
202
+                   (uid (slot-value user-info 'id))
203
+                   (added-feed (store-feed doc uid)))
205 204
               (when api
206
-                (setf result `(200 (:Content-Type "application/json") ,(jsonapi-encoder t "success")))))
207
-            (cl-postgres-error:unique-violation ()
208
-                                                (when api
209
-                                                  (setf result
210
-                                                        `(400 () ,(jsonapi-encoder nil "Feed already saved")))))))
211
-        result))))
205
+                (setf result `(200 (:Content-Type "application/json") ,(jsonapi-encoder t added-feed)))))
206
+            (cl-postgres-error:unique-violation
207
+              ()
208
+              (when api
209
+                (setf result
210
+                      `(400 () ,(jsonapi-encoder nil "Feed already saved"))))))))
211
+      result)))
212
+
212 213
 ;;; TODO: add needs to return the new content, so that angular can append it
213 214
 
214 215
 (cl-oid-connect:def-route ("/feeds/json" (params) :app *app*)
... ...
@@ -399,3 +400,4 @@
399 400
                 *app*)) :port 9090 :server server)
400 401
           *handler*)))
401 402
 
403
+; vim: foldmethod=marker foldmarker=(,) foldminlines=3 :
... ...
@@ -34,68 +34,83 @@
34 34
 
35 35
 (load "tables.lisp")
36 36
 
37
-(setf (symbol-function 'rss-item-encoder)
38
-      (jonathan.helper:compile-encoder () (title link description category comments enclosure guid
39
-                                                 pub-date source)
40
-        (list :|title| title :|link| link :|description| description
41
-              :|category| category :|comments| comments :|enclosure| enclosure
42
-              :|guid| guid :|pub-date| pub-date :|source| source)))
43
-
44
-(setf (symbol-function 'rss-feed-encoder)
45
-      (jonathan.helper:compile-encoder () (title link description items)
46
-        (list :|title| title :|link| link :|description| description :|items| items)))
47
-
48
-(defmethod jonathan:%to-json ((obj rss-feed))
49
-  (jonathan:with-object
50
-    (jonathan:write-key-value "title" (coerce (rss-feed-title obj) 'simple-string))
51
-    (jonathan:write-key-value "link" (coerce (rss-feed-link obj) 'simple-string))
52
-    (jonathan:write-key-value "description" (coerce (rss-feed-description obj) 'simple-string))
53
-    (jonathan:write-key-value "items" (rss-feed-items obj))))
54
-
55
-(defmethod jonathan:%to-json ((obj rss-item))
56
-  (jonathan:with-object
57
-    (jonathan:write-key-value "title" (coerce (rss-item-title obj) 'simple-string))
58
-    (jonathan:write-key-value "link" (coerce (rss-item-link obj) 'simple-string))
59
-    (jonathan:write-key-value "description" (coerce (rss-item-description-raw obj) 'simple-string))
60
-    ;(jonathan:write-key-value "category" (rss-item-category obj))
61
-    (jonathan:write-key-value "comments" (coerce (rss-item-comments obj) 'simple-string))
62
-    (jonathan:write-key-value "enclosure" "rss-item-enclosure obj")
63
-    (jonathan:write-key-value "guid" (coerce (rss-item-guid obj) 'simple-string))
64
-    (jonathan:write-key-value "date" (coerce (rss-item-pub-date obj) 'simple-string))
65
-    (jonathan:write-key-value "source" (coerce (rss-item-source obj) 'simple-string))))
66
-
67
-@export
68
-(defgeneric serialize (cls &rest links)
69
-  (:method ((obj list) &rest ignored)
70
-   (declare (ignore ignored))
71
-   (loop for item in obj
72
-         collect (serialize item)))
73
-
74
-  (:method ((obj vector) &rest ignored)
75
-   (declare (ignore ignored))
76
-   (loop for item across obj
77
-         collect (serialize item)))
78
-
79
-  (:method ((obj rss-feed) &rest ignored)
80
-   (declare (ignore ignored))
81
-   (let ((feed (postmodern:make-dao
82
-                 'rss_feed_store
83
-                 :title (rss-feed-title obj) :link (rss-feed-link obj) :description (rss-feed-description obj))))
84
-     (format t "~a~%" (rfs-link feed))
85
-     (loop for item in (rss-feed-items obj)
86
-           collect (serialize item (rfs-id feed)))
87
-     feed))
88
-
89
-  (:method ((obj rss-item) &rest links)
90
-   (let ((feed (car links)))
91
-     (format t "~a~%" feed)
92
-     (postmodern:make-dao 'rss_item_store
93
-                          :title (rss-item-title obj)
94
-                          :link (rss-item-link obj)
95
-                          :description (rss-item-description-raw obj)
96
-                          :guid (rss-item-guid obj) :pub-date (rss-item-pub-date obj)
97
-                          :source (rss-item-source obj)
98
-                          :feed feed))))
37
+(defmethod jonathan:%to-json ((obj rss-feed)) (jonathan:%to-json (serialize obj
38
+                                                                            #'alexandria:alist-hash-table
39
+                                                                            #'%json-pair-transform)))
40
+(defmethod jonathan:%to-json ((obj rss-item)) (jonathan:%to-json (serialize obj
41
+                                                                            #'alexandria:alist-hash-table
42
+                                                                            #'%json-pair-transform)))
43
+
44
+(defun alist-string-hash-table (alist)
45
+  (alexandria:alist-hash-table alist :test #'string=))
46
+
47
+(defun transform-alist (pair-transform alist)
48
+  (iterate (for (k . v) in-sequence alist)
49
+           (collect
50
+             (funcall pair-transform k v))))
51
+
52
+(defun %json-pair-transform (k v)
53
+  (cons (make-keyword k)
54
+        (typecase v
55
+          (string (coerce v 'simple-string))
56
+          (t v))))
57
+
58
+(defun %default-pair-transform (k v)
59
+  (cons (make-keyword (string-upcase k)) v))
60
+
61
+@export
62
+(defgeneric serialize (cls &optional output-transform pair-transform)
63
+  (:method ((obj sequence) &optional (output-transform #'identity) (pair-transform #'%default-pair-transform))
64
+   (iterate (for item in-sequence obj)
65
+            (collect (serialize item output-transform pair-transform))))
66
+
67
+  (:method ((obj rss-feed) &optional (output-transform #'identity) (pair-transform #'%default-pair-transform))
68
+   (funcall output-transform
69
+     (transform-alist pair-transform
70
+                      `(("title" . ,(rss-feed-title obj))
71
+                        ("link" . ,(rss-feed-link obj))
72
+                        ("description" . ,(rss-feed-description obj))
73
+                        ("items" . ,(iterate (for item in-sequence (rss-feed-items obj))
74
+                                           (collect (serialize item output-transform pair-transform))))))))
75
+
76
+  (:method ((obj rss-item) &optional (output-transform #'identity) (pair-transform #'%default-pair-transform))
77
+   (funcall output-transform
78
+     (transform-alist pair-transform
79
+                      `(("title" . ,(rss-item-title obj))
80
+                        ("link" . ,(rss-item-link obj))
81
+                        ("description" . ,(rss-item-description-raw obj))
82
+                        ("guid" . ,(rss-item-guid obj))
83
+                        ("pub-date" . ,(rss-item-pub-date obj))
84
+                        ("source" . ,(rss-item-source obj)))))))
85
+
86
+@export
87
+(defun store-feed-dao (serialized-rss-feed &optional link)
88
+  (declare (ignore link))
89
+  (let* ((items nil)
90
+         (rss_feed (eval `(postmodern:make-dao
91
+                            'rss_feed_store
92
+                            ,@(iterate (for (k . v) in-sequence serialized-rss-feed)
93
+                                       (if (eql k :items)
94
+                                         (setf items v)
95
+                                         (appending (list k v))))))))
96
+    (iterate (for item in items)
97
+             (store-item-dao item (slot-value rss_feed 'id)))
98
+    rss_feed))
99
+
100
+@export
101
+(defun store-item-dao (rss-item link)
102
+ (eval `(postmodern:make-dao
103
+          'rss_item_store
104
+          :feed ,link
105
+          ,@(iterate (for (k . v) in-sequence rss-item)
106
+                     (appending (list k v))))))
107
+
108
+#|
109
+(:documentation
110
+  "Store a serialized rss object into rhe database: the basic idea here is
111
+   that the quasi-quoted expression generates a form that would insert the
112
+   item and then we eval it.")
113
+|#
99 114
 
100 115
 @export
101 116
 (defmacro copy-slots (slots from-v to-v)
... ...
@@ -105,7 +120,6 @@
105 120
                 in (mapcar (lambda (x) (if (symbolp x) (list x x) x)) slots)
106 121
                 collect `(setf (slot-value ,to ',to-slot) (slot-value ,from ',fro-slot))))))
107 122
 
108
-
109 123
 @export
110 124
 (defun deserialize-item (item)
111 125
   (let ((result (make-instance 'rss-item)))
... ...
@@ -197,7 +211,7 @@
197 211
            (description-munged (dehtml description-raw))
198 212
            (category (get-category-names (lquery:$ "category"))))
199 213
            ;(enclosure) --- TODO: implement comment / enclosure handling
200
-           
214
+
201 215
       (xml-text-bind (title link guid pub-date source comments)
202 216
         (make-instance 'rss-item :item item
203 217
                        :title title :link link :description-raw description-raw :description description-munged
... ...
@@ -223,6 +237,7 @@
223 237
            (feedid (anaphora:aif (postmodern:query (:select 'id :from 'rssfeed
224 238
                                                     :where (:= 'link (rss-feed-link rss-feed-))))
225 239
                      (caar anaphora:it) ;; The postmodern query returns a nested list
226
-                     (slot-value (serialize rss-feed-) 'id))))
240
+                     (slot-value (store-feed-dao (serialize rss-feed-)) 'id))))
227 241
       (postmodern:query
228
-        (:insert-into 'subscriptions :set 'uid uid 'feedid feedid)))))
242
+        (:insert-into 'subscriptions :set 'uid uid 'feedid feedid))
243
+      rss-feed-)))