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
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-))) |