228 lines
7.5 KiB
Common Lisp
228 lines
7.5 KiB
Common Lisp
;; clog -- Supposedly simplistic blogging in Common Lisp
|
|
;; Copyright (C) 2013 Tom Willemse
|
|
|
|
;; clog is free software: you can redistribute it and/or modify
|
|
;; it under the terms of the GNU Affero General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; clog is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU Affero General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU Affero General Public License
|
|
;; along with clog. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
(in-package :clog)
|
|
|
|
(defclass clog (<app>) ())
|
|
|
|
(defvar *app* (make-instance 'clog))
|
|
|
|
(defmethod call ((this clog) env)
|
|
(prepare-tables)
|
|
(call-next-method))
|
|
|
|
(defun make-tpl-parameters (&rest args)
|
|
(append (list :prefix (script-name *request*)) args))
|
|
|
|
(defun post-tags (post)
|
|
(query (:select 'tag.* :from 'tag
|
|
:inner-join 'post-tag
|
|
:on (:and (:= 'id 'tag_id)
|
|
(:= 'post_id (post-id post))))
|
|
(:dao tag)))
|
|
|
|
(defun post-comments (post)
|
|
(query (:order-by
|
|
(:select :* :from 'post
|
|
:where (:and (:= 'type "comment")
|
|
(:= 'parent (post-id post))))
|
|
'timestamp)
|
|
(:dao post)))
|
|
|
|
(defun tag-posts (tag)
|
|
(query (:order-by
|
|
(:select 'post.* :from 'post
|
|
:inner-join 'post-tag
|
|
:on (:and (:= 'id 'post_id)
|
|
(:= 'tag_id (tag-id tag))))
|
|
'timestamp)
|
|
(:dao post)))
|
|
|
|
(defun page-posts (page)
|
|
(query (:order-by
|
|
(:select :* :from 'post
|
|
:where (:and (:= 'parent (post-id page))
|
|
(:= 'type "blog")))
|
|
'timestamp)
|
|
(:dao post)))
|
|
|
|
(defun comment-comments (comment)
|
|
(query (:order-by
|
|
(:select :* :from 'post
|
|
:where (:and (:= 'parent (post-id comment))
|
|
(:= 'type "comment")))
|
|
'timestamp)
|
|
(:dao post)))
|
|
|
|
(defun timestamp->string (ts)
|
|
(multiple-value-bind (year month day hour minute second)
|
|
(decode-timestamp ts)
|
|
(format nil "~2,'0d-~2,'0d-~d ~2,'0d:~2,'0d:~2,'0d"
|
|
day month year hour minute second)))
|
|
|
|
(defun post-params (post)
|
|
(list :title (post-title post)
|
|
:timestamp (timestamp->string (post-time post))
|
|
:slug (post-slug post)))
|
|
|
|
(defun render-post (post &key (lvl 2) (content-render 'post-content))
|
|
(clog-templates:post
|
|
(append (post-params post)
|
|
(make-tpl-parameters
|
|
:content (funcall content-render post)
|
|
:tags (mapcar #'render-tag (post-tags post))
|
|
:lvl lvl
|
|
:type "posts"
|
|
:author (post-author-name post)))))
|
|
|
|
(defun render-tag (tag)
|
|
(clog-templates:tag
|
|
(make-tpl-parameters :name (tag-name tag) :slug (tag-slug tag))))
|
|
|
|
(defun render-page (page)
|
|
(clog-templates:post
|
|
(append (post-params page)
|
|
(make-tpl-parameters
|
|
:content (apply 'concatenate 'string (post-content page)
|
|
(mapcar #'(lambda (post)
|
|
(render-post post :lvl 3))
|
|
(page-posts page)))
|
|
:tags (mapcar #'render-tag (post-tags page))
|
|
:lvl 2
|
|
:type "pages"))))
|
|
|
|
(defun render-comment (comment)
|
|
(clog-templates:comment
|
|
(make-tpl-parameters
|
|
:comment (post-content comment)
|
|
:timestamp (timestamp->string (post-time comment))
|
|
:author (post-author-name comment)
|
|
:md5 (post-author-md5 comment)
|
|
:comments (mapcar #'render-comment
|
|
(comment-comments comment)))))
|
|
|
|
(defun get-posts (&key (type "blog") (parent-id :NULL))
|
|
(query (:order-by
|
|
(:select :* :from 'post
|
|
:where (:and (:= 'type type)
|
|
(:or (:and (:is-null parent-id)
|
|
(:is-null 'parent))
|
|
(:= 'parent parent-id))))
|
|
(:desc 'timestamp))
|
|
(:dao post)))
|
|
|
|
(defun get-post (slug &key (type "blog"))
|
|
(query (:select :* :from 'post
|
|
:where (:and (:= 'slug slug)
|
|
(:= 'type type)))
|
|
(:dao post :single)))
|
|
|
|
(defun get-tag (slug)
|
|
(query (:select :* :from 'tag :where (:= 'slug slug))
|
|
(:dao tag :single)))
|
|
|
|
(defun post-list-page ()
|
|
(clog-templates:main
|
|
(make-tpl-parameters
|
|
:content (clog-templates:list
|
|
(make-tpl-parameters
|
|
:posts (mapcar #'render-post (get-posts)))))))
|
|
|
|
(defun post-page (slug)
|
|
(let ((post (get-post slug)))
|
|
(list (if post 200 404)
|
|
'(:content-type "text/html")
|
|
(clog-templates:main
|
|
(make-tpl-parameters
|
|
:content (if post
|
|
(clog-templates:single
|
|
(make-tpl-parameters
|
|
:post (render-post post)
|
|
:comments (mapcar #'render-comment
|
|
(post-comments post))))
|
|
"No such post"))))))
|
|
|
|
(defun tag-page (slug)
|
|
(let ((tag (get-tag slug)))
|
|
(list (if tag 200 404)
|
|
'(:content-type "text/html")
|
|
(clog-templates:main
|
|
(make-tpl-parameters
|
|
:content
|
|
(if tag
|
|
(clog-templates:list
|
|
(make-tpl-parameters
|
|
:posts (mapcar #'render-post (tag-posts tag))))
|
|
"No such tag"))))))
|
|
|
|
(defun page-page (slug)
|
|
(let ((page (get-post slug :type "page")))
|
|
(list (if page 200 404)
|
|
'(:content-type "text/html")
|
|
(clog-templates:main
|
|
(make-tpl-parameters
|
|
:content
|
|
(if page
|
|
(clog-templates:single
|
|
(make-tpl-parameters
|
|
:post (render-page page)
|
|
:comments (mapcar #'render-comment
|
|
(post-comments page))))
|
|
"No such page"))))))
|
|
|
|
(setf (route *app* "/") (lambda (params) (post-list-page)))
|
|
(setf (route *app* "/posts/:slug")
|
|
(lambda (params) (post-page (getf params :slug))))
|
|
(setf (route *app* "/tags/:slug")
|
|
(lambda (params) (tag-page (getf params :slug))))
|
|
(setf (route *app* "/pages/:slug")
|
|
(lambda (params) (page-page (getf params :slug))))
|
|
|
|
(setf (route *app* "/login" :method :POST)
|
|
#'(lambda (params)
|
|
(if (authorize (getf params :|username|)
|
|
(getf params :|password|))
|
|
"Authorized!"
|
|
"Failed...Try again.")))
|
|
|
|
(setf (route *app* "/hello/:name")
|
|
#'(lambda (params)
|
|
(format nil "Hello, ~A" (getf params :name))))
|
|
|
|
(setf (route *app* "/say/*/to/*")
|
|
#'(lambda (params)
|
|
;; matches /say/hello/to/world
|
|
(format nil "~S" (getf params :splat))))
|
|
|
|
(setf (route *app* "/download/*.*")
|
|
#'(lambda (params)
|
|
;; matches /download/path/to/file.xml
|
|
(format nil "~S" (getf params :splat))))
|
|
|
|
(defun get-app ()
|
|
(clack.builder:builder
|
|
(<clack-middleware-static> :path "/static/" :root "static/")
|
|
(<clack-middleware-postmodern>
|
|
:database *dbname* :user *dbuser* :password *dbpass* :host *dbhost*)
|
|
clack.middleware.session:<clack-middleware-session>
|
|
*app*))
|
|
|
|
;; (defparameter *urlmap* (make-instance '<clack-app-urlmap>))
|
|
|
|
;; (mount *urlmap* "/clog/" (get-app))
|
|
|
|
;; (defvar *handler*
|
|
;; (clackup *urlmap* :port 5004))
|