9d7c4d5c |
(declaim (optimize (debug 3) (speed 0) (safety 3)))
(ql:quickload :alexandria)
(ql:quickload :ironclad)
(ql:quickload :uuid)
(ql:quickload :bordeaux-threads)
(ql:quickload :manardb)
(ql:quickload :ningle)
(ql:quickload :anaphora)
(ql:quickload :ubiquitous)
(ql:quickload :spinneret)
(ql:quickload :clack)
(ql:quickload :clack-errors)
|
ef839cbf |
(ql:quickload :cl-markdown)
|
9d7c4d5c |
(defpackage :blogerate
|
ef839cbf |
(:use :cl :alexandria :ningle :anaphora :ningle :spinneret))
|
9d7c4d5c |
(in-package :blogerate)
(ubiquitous:restore 'blogerate)
(manardb:use-mmap-dir "/tmp/manardb-blogerate")
(manardb:defmmclass blog ()
|
ef839cbf |
((id :initarg :id :accessor blog-id)
(posts :initarg :posts :accessor blog-posts :initform nil)
(tags :initform nil :accessor blog-tags)
(authors :initform nil :accessor blog-authors)
(sets :initform nil :accessor blog-sets)
(seed :initarg :seed
:initform (make-array 10
:element-type '(unsigned-byte 8)
:initial-contents (loop for x from 1 to 10 collect (random 256)))
:accessor blog-seed)))
(manardb:defmmclass post-set ()
((id :initarg :id :accessor set-id :initform nil)
(name :initarg :name :accessor set-name)
(posts :initarg :posts :accessor set-posts :initform nil)
(seed :initarg :seed
:initform (make-array 10
:element-type '(unsigned-byte 8)
:initial-contents (loop for x from 1 to 10 collect (random 256)))
:accessor set-seed)))
|
9d7c4d5c |
(manardb:defmmclass post ()
((id :initarg :id :accessor post-id :initform (with-output-to-string (s)
|
ef839cbf |
(uuid:print-bytes s (uuid:make-v4-uuid))))
|
9d7c4d5c |
(title :initarg :title :accessor post-title :initform "")
(text :initarg :text :accessor post-text :initform "")
(author :initarg :author :accessor post-author :initform "")
(post-set :initarg :set :accessor post-set :initform "")
(tags :initarg :tags :initform '() :accessor post-tags)))
(defun make-post (blog title text author set &key (tags nil tags-p))
|
ef839cbf |
(let ((result (make-instance 'post :title title :text text :author author :set set :tags tags))
(set-obj (get-set :name set :author author)))
|
9d7c4d5c |
(push result (blog-posts blog))
(when tags-p
(mapcar (lambda (x) (push (cons x result) (blog-tags blog)))
(post-tags result)))
|
ef839cbf |
(push result (set-posts set-obj))
(unless (member set (blog-sets blog) :key #'car :test #'string=)
(push (cons set set-obj) (blog-sets blog)))
|
9d7c4d5c |
(push (cons (post-author result) result) (blog-authors blog))
result))
|
ef839cbf |
(defparameter *blog* (get-blog "my-blog9"))
(defvar *app* (make-instance 'ningle:<app>))
(defun get-set-by-id (id &optional (blog *blog*))
(awhen (blog-sets blog)
(awhen (assoc id it :test #'string=)
(cdr it))))
(defun get-set-by-name-and-author (name author)
(flet ((new-set ()
(let ((new-set (make-instance 'post-set :name name)))
(setf (set-id new-set) (get-set-id (set-seed new-set) name author))
new-set)))
(aif (manardb:retrieve-all-instances 'post-set)
(aif (car (remove-if-not (lambda (x) (equal (get-set-id (set-seed x) name author) (set-id x)))
it))
it
(new-set))
(new-set))))
(defun get-set (&key id name author)
(if id
(get-set-by-id id)
(get-set-by-name-and-author name author)))
|
9d7c4d5c |
(defun get-blog (id)
(flet ((new-blog () (make-instance 'blog :id id)))
(aif (manardb:retrieve-all-instances 'blog)
(aif (car (remove-if-not (lambda (x) (equal id (blog-id x))) it))
it
(new-blog))
(new-blog))))
(defmacro with-page ((&key title) &body body)
`(spinneret:with-html-string
(:doctype)
(:html
(:head
(:link :rel "stylesheet" :href "/static/main.css")
(:title ,title))
|
ef839cbf |
(:body
(:main
,@body)))))
(defmacro format-post (item)
(alexandria:once-only (item)
`(spinneret:with-html
(:section :class "post-show"
(:h2
(post-title ,item))
(:article
(:raw
(nth-value 1 (cl-markdown:markdown (post-text ,item) :stream nil))))
(:span :class "author"
(post-author ,item))
(:span :class "set"
(post-set ,item))
(:ul.tags
(loop for tag in (post-tags ,item)
collect (:li tag)))))))
(defun get-set-posts (set-name)
(car
(loop for (set . post) in (blog-sets *blog*)
if (string= set set-name)
collect post)))
(defun get-tag (tag-name)
(mapcar #'cdr
(remove-if-not (lambda (x) (string= x tag-name))
(blog-tags *blog*)
:key #'car)))
(setf (route *app* "/posts/:set" :method :GET)
(lambda (params)
(declare (ignorable params))
(let* ((set-name (cdr (assoc :set params)))
(set-title (format nil "Set ~a" set-name))
(set (get-set :id set-name)))
(format nil "~s" (get-set :id set-name))
(with-page (:title set-title)
(:header
(:h1 (set-name set)))
(loop for post in (set-posts set)
collect (format nil "~s" (format-post post)))))))
|
9d7c4d5c |
|
ef839cbf |
(setf (route *app* "/posts/:set/:tag" :method :GET)
(lambda (params)
(let* ((set-id (cdr (assoc :set params)))
(tag-name (cdr (assoc :tag params)))
(set (get-set :id set-id))
(set-posts (set-posts set))
(tag-posts (get-tag tag-name))
(display-posts (intersection set-posts tag-posts
:key #'post-id
:test #'string=)))
(with-page (:title (format nil "Set: ~a Tag: ~a" (set-name set) tag-name))
(:header (:h1 (format nil "Set: ~a Tag: ~a" (set-name set) tag-name)))
(mapcar (lambda (post) (format nil "~s" (format-post post)))
display-posts)))))
(spinneret:deftag form-input (body attrs &key name label (type "text"))
(declare (ignore body))
(alexandria:once-only (name)
`(:div.input-group :class ,name ,@attrs
;(:label :for ,name ,label)
(:input :placeholder ,label :type ,type :name ,name))))
|
9d7c4d5c |
(setf (route *app* "/post/new" :method :GET)
(lambda (params)
(declare (ignorable params))
|
ef839cbf |
(with-page (:title "new post")
(:header
(:h1 "New Post:"))
(:section :class "post-new"
(:form :action "/post/new" :method "POST"
(form-input :name "title" :label "Title")
(form-input :name "author" :label "Author")
(:div.input-group.post
;(:label :for "post" "Post Content")
(:textarea :placeholder "Content" :name "post"))
(form-input :name "set" :label "Set")
(form-input :name "tags" :label "Comma-separated Tags")
(form-input :type "submit"))))))
|
9d7c4d5c |
(defun str-assoc-cdr (key alist)
(cdr (assoc key alist :test #'equal)))
(defun split-tags (str)
|
ef839cbf |
(mapcar (lambda (x) (string-trim " " x))
(split-sequence:split-sequence #\, str)))
(defun get-set-id (blog-seed set-name author-name)
(let* ((o nil)
(digester (ironclad:make-digest :tiger))
(digest (progn
(ironclad:update-digest digester blog-seed)
(ironclad:update-digest digester (ironclad:ascii-string-to-byte-array set-name))
(ironclad:update-digest digester (ironclad:ascii-string-to-byte-array author-name))
(ironclad:byte-array-to-hex-string
(ironclad:produce-digest digester))))
(digest-length (length digest)))
(dotimes (x (ceiling (/ digest-length 5)))
(push (subseq digest
(* x 5)
(min digest-length (* (1+ x) 5)))
o))
(format nil "~{~a~^-~}" o)))
|
9d7c4d5c |
(setf (route *app* "/post/new" :method :POST)
(lambda (params)
(format nil "~s" params)
|
ef839cbf |
(let* ((author (str-assoc-cdr "author" params))
(set (str-assoc-cdr "set" params))
(new-post
|
9d7c4d5c |
(make-post *blog*
(str-assoc-cdr "title" params)
(str-assoc-cdr "post" params)
|
ef839cbf |
author
set
|
9d7c4d5c |
:tags (split-tags (str-assoc-cdr "tags" params)))))
(with-page (:title (post-title new-post))
(:header
|
ef839cbf |
(:h1 "What a Piece of Blog is This?"))
(format-post new-post)))))
|
9d7c4d5c |
(setf (route *app* "/" :method :GET)
(lambda (params)
|
ef839cbf |
(declare (ignore params))
(with-page (:title "What a Piece of Blog is This?")
|
9d7c4d5c |
(:header
|
ef839cbf |
(:h1 "What a Piece of Blog is This?"))
|
9d7c4d5c |
(loop for new-post in (blog-posts *blog*)
collect
|
ef839cbf |
(format-post new-post)))))
|
9d7c4d5c |
(defparameter *handler*
(clack:clackup
(lack.builder:builder
:session
;:csrf
;clack-errors:*clack-error-middleware*
(:static :path "/static/" :root #p"./static/")
|
ef839cbf |
*app*) :port 5050))
|