src/models/post.lisp

DOWNLOAD
(defpackage murja.models.post
  (:use :cl)
  (:export :Post :get-page :get-post :post-id :post-title :article :creator :tags :created-at :post-hidden? :post-unlisted? :next-post-id :previous-post-id)
  (:import-from :com.inuoe.jzon :parse))

(in-package :murja.models.post)

;; fuck it we're moving from hashmaps to clos now 
(defclass Post ()
  ;; slots are copied from the table blog.Post, accessors are what I might call these were I designing the db nowadays 
  ((id :initarg :id :accessor post-id :col-type integer)
   (title :initarg :title :accessor post-title :col-type string)
   (content :initarg :content :accessor article :col-type string)
   (creator :initarg :creator :initform nil :accessor creator)
   (creator-id :initarg :creator-id :col-type string :reader creator-id :col-references (murja.models.user:user 'murja.models.user::id))
   (tags :initarg :tags :accessor tags :col-type string)
   (created-at :initarg :created-at :accessor created-at :col-type simple-date:timestamp)
   (hidden :initarg :hidden? :accessor post-hidden? :col-type boolean)
   (unlisted :initarg :unlisted? :accessor post-unlisted? :col-type boolean)
   (previous :ghost t :initarg :previous :accessor previous-post-id :col-type integer :initform -1)
   (next :initform -1 :ghost t :initarg :next :accessor next-post-id :col-type integer))
  (:metaclass postmodern:dao-class)
  (:keys id)
  (:table-name "blog.Post"))

(defmethod print-object ((p Post) output)
  (with-slots (creator id title content creator-id tags created-at hidden unlisted previous next) p
    (format output "#<POST: ~{~{~a: ~s~}~^,~%~t ~}>" (list
						(list :id id)
						(list :title title)
						(list :content
						      (if (> (length content) 50)
							  (format nil "~a..."
								  (str:substring 0 50 content))
							  content))
						(list :creator-id creator-id)
						(list :creator creator)
						(list :tags tags)
						(list :created-at created-at)
						(list :hidden hidden)
						(list :unlisted unlisted)
						(list :previous previous)
						(list :next next)))))

;; (postmodern:query "SELECT * FROM blog.Post limit 1" (:dao post :single))
;; (postmodern:get-dao 'Post 349)
;; #<POST: ID: 349, TITLE: "Lisp webapps ", CONTENT: "<p>So, after <a href=\"https://feuerx.net/blog/post...", CREATOR-ID: 1, TAGS: "[\"postgresql\", \"hunchentoot\", \"lisp\", \"programming\"]", CREATED-AT: #<SIMPLE-DATE:TIMESTAMP 22-02-2025T09:31:40,016>, HIDDEN: NIL, UNLISTED: NIL>

(defun fix-post (p) 
  (setf (tags p) (parse (tags p)))
  (setf (creator p) (postmodern:get-dao 'murja.models.user:user (creator-id p)))

  ;; next-id and previous-id might point to unlisted posts. They should not do that, but I don't know how to wrangle *post-query* sql to support that, so they have to be fixed in lisp.
  (unless (or (equalp (previous-post-id p) -1)
	      (not (caar (postmodern:query "SELECT unlisted FROM blog.Post WHERE id = $1" (previous-post-id p)))))
    (setf (previous-post-id p)
	  (or (caar (postmodern:query "SELECT id FROM blog.Post WHERE ID < $1 AND NOT hidden AND NOT unlisted" (previous-post-id p)))
	      -1)))

  (unless (or (equalp (next-post-id p) -1)
	      (not (caar (postmodern:query "SELECT unlisted FROM blog.Post WHERE id = $1" (next-post-id p)))))
    (setf (next-post-id p)
	  (or (caar (postmodern:query "SELECT id FROM blog.Post WHERE ID > $1 AND NOT hidden AND NOT unlisted" (next-post-id p)))
	      -1)))  
  p)

(defparameter *post-query*
  "
SELECT * FROM
(SELECT p.*, LAG(id, 1, -1) OVER (ORDER BY created_at) as previous, LEAD(id, 1, -1) OVER (ORDER BY created_at) as next FROM blog.Post p where (NOT p.hidden OR (p.hidden AND $2)))
where id = $1
")
  
(defun get-post (id &key allow-hidden?)
  (let ((p (postmodern:query *post-query* id allow-hidden? (:dao Post :single))))
    (when p 
      (fix-post p))
    p))

(defun get-page (page page-size &key allow-hidden? modified-since)
  (let* ((page (if (< page 1)
		   1
		   page)))
    (map 'list
	 #'fix-post 
	 (postmodern:query
	  "SELECT p.*
FROM blog.Post p
WHERE ((NOT p.unlisted) OR $3)
  AND ((NOT p.hidden) OR $3)
  AND (($4::timestamp IS NULL) OR p.created_at > $4::timestamp)
ORDER BY p.created_at DESC
LIMIT $2
OFFSET $1"
	  (* (1- page) page-size)
	  page-size
	  allow-hidden?
	  (if modified-since
	      modified-since
	      :null)
	  (:dao Post)))))