cd3f5f95 |
(in-package :cl-user)
|
b5a8e489 |
|
4ed5f067 |
(declaim (optimize (speed 0) (safety 3) (debug 3)))
|
cd3f5f95 |
|
703fc701 |
(define-modify-macro aconsf (key value)
(lambda (place k v)
(acons k v place)))
(aconsf drakma:*text-content-types*
"application" "rdf+xml")
(aconsf drakma:*text-content-types*
"application" "rss+xml")
(aconsf drakma:*text-content-types*
"text" "rss+xml")
|
4ed5f067 |
(aconsf drakma:*text-content-types*
"application" "xml")
|
cd3f5f95 |
|
b99dd5be |
;(load "utils.lisp")
|
89bed873 |
(in-package plump-dom)
|
cd3f5f95 |
|
89bed873 |
(defmethod serialize-object :around ((node element))
(when (string= (tag-name node) "iframe")
(make-text-node node))
(call-next-method))
|
cd3f5f95 |
|
89bed873 |
(in-package :whitespace)
(ubiquitous:restore :whitespace)
(defparameter *app* (make-instance 'ningle:<app>))
(handler-bind ((warning #'sb-ext::muffle-warning))
(let* ((feed-urls (ubiquitous:value 'feed 'urls))
(plump-parser:*tag-dispatchers* plump:*xml-tags*)
(docs (map 'vector (lambda (x) (plump:parse (drakma:http-request x))) feed-urls)))
(defparameter *feeds* (map 'vector (lambda (x) (make-rss-feed x)) docs))))
|
cd3f5f95 |
|
c5c94d7d |
(defparameter *db-connection-info* (ubiquitous:value 'db))
|
b99dd5be |
(defmacro with-whitespace-db (&body body)
`(postmodern:with-connection *db-connection-info*
,@body))
|
c5c94d7d |
|
b99dd5be |
(defmacro wc (&body body)
|
c5c94d7d |
"Utility function for the REPL"
|
b99dd5be |
`(with-whitespace-db ,@body))
(defmacro with-xml-tags (&body body)
`(let ((plump:*tag-dispatchers* plump:*xml-tags*))
,@body))
|
cd3f5f95 |
|
89bed873 |
(defmacro defun-from-value (name value)
|
4ed5f067 |
`(eval-when (:compile-toplevel :load-toplevel :execute)
(setf (symbol-function ',name) ,value)))
|
89bed873 |
(defun-from-value jsonapi-encoder
(jonathan.helper:compile-encoder () (success result)
(list :|success| success
:|result| result)))
; ; ; Ultimately, this will only serialize the feed if the client
(cl-oid-connect:def-route ("/feeds/add" (params) :method :post :app *app*)
|
b5a8e489 |
(ningle.context:with-context-variables (session)
|
67a3d329 |
(let ((user-info (gethash :app-user session))
|
3ef36f73 |
(result '(302 (:location "/")))
|
b5a8e489 |
(api (string= (cl-oid-connect:assoc-cdr "api" params 'string=) "yes"))
(url (cl-oid-connect:assoc-cdr "url" params 'string=))
|
3ef36f73 |
(plump-parser:*tag-dispatchers* plump-parser:*xml-tags*))
(cl-oid-connect:require-login
|
89bed873 |
(when (neither-null params user-info)
(handler-case
|
3ef36f73 |
(let* ((doc (plump:parse (drakma:http-request url)))
|
67a3d329 |
(uid (slot-value user-info 'id)))
|
b5a8e489 |
(multiple-value-bind (added-feed dao-feed) (store-feed doc)
|
67a3d329 |
(subscribe-to-feed uid (slot-value dao-feed 'id))
(when api
|
703fc701 |
(setf result `(200 (:Content-Type "application/json")
(,(jsonapi-encoder t added-feed)))))))
|
3ef36f73 |
(cl-postgres-error:unique-violation
()
(when api
(setf result
|
703fc701 |
`(400 () (,(jsonapi-encoder nil "Feed already saved")))))))))
|
3ef36f73 |
result)))
|
89bed873 |
;;; TODO: add needs to return the new content, so that angular can append it
|
0b3c568e |
(defparameter *userasdfs* nil)
#|(cl-oid-connect:require-login
))|#
|
e09b98ca |
;;; this will be bound by calls to with-palette
;;; probably should be refactored out
(defparameter *palette* nil)
(defparameter *colorscheme* (make-instance 'colors:colorscheme))
(defun get-theme-css ()
(colors:with-palette (*palette*)
(flet ((combine-unit-q (quant unit) (format nil "~d~a" quant unit)))
(let* ((header-height 9)
|
89bed873 |
(main-right-margin (* 0.618 (- 100 header-height)))
|
e09b98ca |
(height-units "vh")
(ss (lass:compile-and-write
`(* :color ,(colors:colorscheme-fg *colorscheme*))
|
89bed873 |
`(body
:background-color ,(colors:colorscheme-bg *colorscheme*))
|
e09b98ca |
`((:or h1 h2 h3)
:color ,(colors:colorscheme-fg-highlight *colorscheme*))
`(.feed-header
:background-color ,(colors:colorscheme-bg-highlight *colorscheme*))
`((:or h4 h5 h6) :color ,(colors:colorscheme-fg-highlight *colorscheme*))
`(header
:border-bottom "thin" "solid" ,(colors:colorscheme-accent *colorscheme*)
:height ,(combine-unit-q header-height height-units)
:font-size ,(combine-unit-q (* 0.75 header-height) height-units)
:line-height ,(combine-unit-q header-height height-units)
(.flip-button
:background-color ,(colors:colorscheme-fg *colorscheme*)
:color ,(colors:colorscheme-bg *colorscheme*))
((:and .flip-button :focus)
:outline none)
((:and .flip-button :hover)
|
89bed873 |
:font-size ,(combine-unit-q (* 0.25 header-height) height-units)))
|
e09b98ca |
`(main
|
67a3d329 |
:border-left medium solid ,(colors:colorscheme-accent *colorscheme*)
|
89bed873 |
:height ,(combine-unit-q (- 100 header-height) height-units)
|
67a3d329 |
("#add-form"
:box-shadow "0em" "0em" "0.2em" "0.2em" ,(colors:colorscheme-accent *colorscheme*)
|
89bed873 |
((:or input button)
|
67a3d329 |
:background-color ,(colors:colorscheme-bg *colorscheme*)
:color ,(colors:colorscheme-fg *colorscheme*))
|
89bed873 |
)
)
|
e09b98ca |
`((:or a (:and a :visited) (:and a :active) code.url)
:color ,(colors:colorscheme-fg-highlight *colorscheme*))
`(section#sidebar
|
89bed873 |
:transition opacity "0.5s" ease
|
e09b98ca |
(ul.menu
((li + li)
:border-top "thin" "solid" ,(colors:colorscheme-fg-highlight *colorscheme*))
((:and li :hover)
:background-color ,(colors:colorscheme-hover-highlight *colorscheme*)
:color ,(colors:colorscheme-fg-highlight *colorscheme*))))
`(.feed
:border-bottom thick solid ,(colors:colorscheme-accent *colorscheme*)
:border-left none)
`(.link-header :background-color ,(colors:colorscheme-bg-highlight *colorscheme*))
`(.link
:border-top thin solid ,(colors:colorscheme-fg *colorscheme*)
:border-bottom none
(.link-info
:color ,(colors:colorscheme-fg-deemph *colorscheme*)
:border-bottom "thin" "solid" ,(colors:colorscheme-fg *colorscheme*)
((:or a span)
:color inherit)
((:and a :hover)
:color ,(colors:colorscheme-fg *colorscheme*))
))
|
89bed873 |
|
e09b98ca |
`((:and .feed-header :hover)
:background-color ,(colors:colorscheme-hover-highlight *colorscheme*))
`((.link.closed .link-header)
:background-color ,(colors:colorscheme-bg *colorscheme*))
`((:or (:and .link-header :hover) (.link.closed (:and .link-header :hover)))
:background-color ,(colors:colorscheme-hover-highlight *colorscheme*)))))
|
b99dd5be |
(declare (ignorable main-right-margin)) ; TODO: use this!!!
|
c5c94d7d |
`(200 (:content-type "text/css") (,ss))))))
|
e09b98ca |
|
703fc701 |
(define-view json-feed (the-feeds)
`(200 (:content-type "application/json" :cache-control "private, max-age=300")
(,(jsonapi-encoder t the-feeds))))
(define-controller json-feed (params)
(ningle.context:with-context-variables (session)
(let* ((user-info (gethash :app-user session))
(*feeds* (if user-info (deserialize user-info) *feeds*)))
(setf *userasdfs* user-info)
*feeds*)) )
(cl-oid-connect:def-route ("/demo" (params) :app *app*)
(base-template-f t))
|
c5c94d7d |
(defroutes *app*
|
703fc701 |
(("/feeds/json") (araneus:as-route 'json-feed))
|
c5c94d7d |
(("/theme/:scheme.css") (araneus::compose-route (css) css))
(("/userinfo.json") (araneus::compose-route (id) userinfo))
(("/login") (araneus::compose-route (id) login))
|
703fc701 |
(("/logout") (araneus::compose-route (logout) redirect))
|
c5c94d7d |
(("/") (araneus:as-route 'root)))
|
b5a8e489 |
(defun assoc-cdr-alternatives (alist alt1 alt2 &optional (test #'eql))
(aif (cl-oid-connect:assoc-cdr alt1 alist test)
it
(cl-oid-connect:assoc-cdr alt2 alist test)))
(cl-oid-connect::setup-oid-connect *app* (userinfo &rest args)
(declare (ignore args) (optimize (speed 0) (safety 3) (debug 3)))
(flet ((get-received-id (userinfo) (assoc-cdr-alternatives userinfo :id :sub))
|
c5c94d7d |
(get-db-user (received-id) (car (postmodern:select-dao 'reader_user (:= :foreign-id received-id))))
|
b5a8e489 |
(get-first-name (userinfo) (assoc-cdr-alternatives userinfo :first--name :given--name))
(get-last-name (userinfo) (assoc-cdr-alternatives userinfo :last--name :family--name))
(get-link (userinfo) (assoc-cdr-alternatives userinfo :link :profile)))
(postmodern:with-transaction ()
(let ((received-id (get-received-id userinfo)))
(aif (get-db-user received-id) it
(postmodern:make-dao
'reader_user
:foreign-id received-id
:first-name (get-first-name userinfo)
:last-name (get-last-name userinfo)
:name (cl-oid-connect:assoc-cdr :name userinfo)
:email (cl-oid-connect:assoc-cdr :email userinfo)
:gender (cl-oid-connect:assoc-cdr :gender userinfo)
:link (get-link userinfo)
:locale (cl-oid-connect:assoc-cdr :locale userinfo)))))))
|
89bed873 |
|
b99dd5be |
(defun update-feed (url)
|
7f1ede8b |
(with-whitespace-db
|
b99dd5be |
(postmodern:with-transaction ()
(upsert-feed (make-rss-feed (with-xml-tags (plump:parse (drakma:http-request url))))))))
|
7f1ede8b |
(defmacro amapcar-with-body (list &body forms)
(alexandria:once-only (list)
`(mapcar (lambda (it) ,@forms)
,list)))
|
b99dd5be |
(defun update-all-feeds ()
|
7f1ede8b |
(with-whitespace-db
|
b99dd5be |
(let ((urls (postmodern:query (:select 'fetch-url :from 'rss-feed-store))))
|
7f1ede8b |
(amapcar-with-body urls
(restart-case
(apply #'update-feed it)
(continue-updates () (warn (format nil "Skipping feed with fetch-url: ~s" it)))
(use-value (v) (update-feed v)))))))
|
b99dd5be |
(defun minutes (minutes) (* minutes 60))
|
7f1ede8b |
(defun continue-updates (e)
(declare (ignore e))
(let ((restart (find-restart 'continue-updates)))
(when restart
(format t "continuing")
(invoke-restart restart))))
|
b99dd5be |
(let (update-thread stop)
(defun start-update-thread ()
(setf update-thread
(bordeaux-threads:make-thread
(lambda ()
(loop
|
7f1ede8b |
(handler-bind ((drakma:parameter-error #'continue-updates))
(update-all-feeds))
|
b99dd5be |
(sleep (ubiquitous:value 'update-frequency))
(when stop
(return-from nil nil))))
:name "Whitespace Update Thread")))
(defun stop-update-thread ()
(setf stop t)
(setf update-thread nil)))
|
c06fa69a |
(let ((handler nil))
(defun stop () (clack:stop (pop handler)))
(defun start (&optional tmp)
|
b5a8e489 |
(cl-oid-connect:initialize-oid-connect
(ubiquitous:value 'facebook 'secrets)
(ubiquitous:value 'google 'secrets))
|
c06fa69a |
(let ((server (if (> (length tmp) 1)
(intern (string-upcase (elt tmp 1)) 'keyword)
:hunchentoot)))
(push (clack:clackup
(lack.builder:builder
:backtrace
:session
;:csrf
(lambda (app) (lambda (env)
(postmodern:with-connection *db-connection-info*
(funcall app env))))
(:static :path "/static/" :root #p"./static/")
*app*) :port 9090 :server server)
handler)))
(defun restart-clack ()
(do () ((null handler)) (stop))
(start)))
|
b5a8e489 |
|
c06fa69a |
|