(in-package :cl-user) (defpackage :my-blog (:use :cl :elephant) (:export :start-blog)) (in-package :my-blog) (defpclass blog-post () ((title :initarg :title :accessor title) (body :initarg :body :accessor body) (timestamp :initarg :timestamp :accessor timestamp :initform (get-universal-time) :index t) (url-part :initarg :url-part :accessor url-part :initform nil :index t))) (defmethod initialize-instance :after ((obj blog-post) &key) "If :url-part wasn't non-nil when making the instance, generate it automatically." (cond ((eq nil (url-part obj)) (setf (url-part obj) (make-url-part (title obj)))))) (defun make-url-part (title) "Generate a url-part from a title. A url-part should only have alphanumeric characters or dashes (in place of spaces)." (string-downcase (delete-if #'(lambda (x) (not (or (alphanumericp x) (char= #\- x)))) (substitute #\- #\Space title)))) (defun generate-index-page () "Generate the index page showing all the blog posts." (with-output-to-string (stream) (let ((html-template:*string-modifier* #'identity)) (html-template:fill-and-print-template #P"index.tmpl" (list :blog-posts (loop for blog-post in (nreverse (get-instances-by-range 'blog-post 'timestamp nil nil)) collect (list :title (title blog-post) :body (body blog-post) :url-part (url-part blog-post)))) :stream stream)))) (defun generate-blog-post-page (template) "Generate a page using blog post data." (let ((url-part (hunchentoot:query-string))) (with-output-to-string (stream) ; Create a stream that will give us a sting (let ((blog-post (get-instance-by-value 'blog-post 'url-part url-part)) ; Get the right blog post (html-template:*string-modifier* #'identity)) (html-template:fill-and-print-template template (cond ((eq nil blog-post) (list :title "" :body "" :url-part "")) (t (list :title (title blog-post) :body (body blog-post) :url-part (url-part blog-post)))) :stream stream))))) (defun view-blog-post-page () "Generate a page for viewing a blog post." (generate-blog-post-page #P"post.tmpl")) (defmacro with-http-authentication (&rest body) `(multiple-value-bind (username password) (hunchentoot:authorization) (cond ((and (string= username *username*) (string= password *password*)) ,@body) (t (hunchentoot:require-authorization "my-blog"))))) (defun edit-blog-post () (with-http-authentication (cond ((eq (hunchentoot:request-method) :GET) (generate-blog-post-page #P"post-form.tmpl")) ((eq (hunchentoot:request-method) :POST) (update-blog-post))))) (defun create-blog-post () (with-http-authentication (cond ((eq (hunchentoot:request-method) :GET) (with-output-to-string (stream) (html-template:fill-and-print-template #P"post-form.tmpl" nil :stream stream))) ((eq (hunchentoot:request-method) :POST) (save-new-blog-post))))) (defun save-new-blog-post () (let ((blog-post (make-instance 'blog-post :title (hunchentoot:post-parameter "title") :body (hunchentoot:post-parameter "body")))) (hunchentoot:redirect (url-part blog-post)))) (defun update-blog-post () "Read POST data and modify blog post." (let ((blog-post (get-instance-by-value 'blog-post 'url-part (hunchentoot:query-string)))) (setf (title blog-post) (hunchentoot:post-parameter "title")) (setf (body blog-post) (hunchentoot:post-parameter "body")) (setf (url-part blog-post) (make-url-part (title blog-post))) (hunchentoot:redirect (url-part blog-post)))) ; Set the web server dispatch table (setq hunchentoot:*dispatch-table* (list (hunchentoot:create-regex-dispatcher "^/$" 'generate-index-page) (hunchentoot:create-regex-dispatcher "^/view/$" 'view-blog-post-page) (hunchentoot:create-regex-dispatcher "^/edit/$" 'edit-blog-post) (hunchentoot:create-regex-dispatcher "^/create/$" 'create-blog-post))) ; Make sure html-template looks for files in the right directory (setq html-template:*default-template-pathname* #P"/Users/vetler/Documents/devel/cl-webapp-intro-part3/source/") (defun start-blog () "Open the Elephant store and start the web server." ; Open the store where our data is stored (defvar *elephant-store* (open-store '(:clsql (:sqlite3 "/tmp/blog.db")))) ; Start the web server (defvar *ht-server* (hunchentoot:start-server :port 8080))) (defun stop-blog () "Close the Elephant store and stop the web server." (close-store *elephant-store*) (hunchentoot:stop-server *ht-server*)) ; Username and password for admin access (defvar *username* "admin") (defvar *password* "secret")