diff of a1e506a502429aa0ae0bdfbe0d51362a7a767fba
a1e506a502429aa0ae0bdfbe0d51362a7a767fba
diff --git a/aggressive-murja.asd b/aggressive-murja.asd
index b27294b..c001d65 100644
--- a/aggressive-murja.asd
+++ b/aggressive-murja.asd
@@ -77,12 +77,14 @@
(:module "view"
:components
- ((:module "components"
- :components ((:file "root")
- (:file "tabs")
- (:file "blogpost")))
- (:file "blog-root")
- (:file "single-post")))
+ ((:module "components"
+ :components ((:file "root")
+ (:file "tabs")
+ (:file "blogpost")))
+ (:module "admin"
+ :components ((:file "post-list")))
+ (:file "blog-root")
+ (:file "single-post")))
(:file "main"))))
:in-order-to ((test-op (test-op "aggressive-murja/tests"))))
diff --git a/src/model/post.lisp b/src/model/post.lisp
index ab56ca3..06edc01 100644
--- a/src/model/post.lisp
+++ b/src/model/post.lisp
@@ -1,7 +1,8 @@
(defpackage murja.model.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 :previouslies
- :id :title :creator :created-at :content)
+ :id :title :creator :created-at :content :admin-get-all-titles
+ :title :month :year :id :tags :hidden :unlisted)
(:import-from :com.inuoe.jzon :parse))
(in-package :murja.model.post)
@@ -24,8 +25,33 @@
(previouslies :initform nil :ghost t :initarg ghostlies :accessor previouslies :col-type string))
(:metaclass postmodern:dao-class)
(:keys id)
+ (:documentation "A blogpost")
(:table-name "blog.Post"))
+
+(defclass Title ()
+ ((title :initarg :title :accessor post-title :initform (error "Title required"))
+ (month :initarg :month :accessor month :initform (error "Month required"))
+ (year :initarg :year :accessor year :initform (error "Year required"))
+ (id :initarg :id :accessor id :initform (error "Id required"))
+ (tags :initarg :tags :accessor tags :initform nil)
+ (hidden :initarg :hidden :accessor hidden :initform nil)
+ (unlisted :initarg :unlisted :accessor unlisted :initform nil))
+
+ (:documentation "A subset of the data in the class Post. This is for use in the sidebar's tree view and admin's postmanager"))
+
+(defmethod print-object ((p Title) output)
+ (with-slots (id title month year tags hidden unlisted) p
+ (format output "#<TITLE: ~{~{~a: ~s~}~^,~%~t ~}>" (list
+ (list :id id)
+ (list :title title)
+ (list :tags tags)
+ (list :month month)
+ (list :year year)
+ (list :hidden hidden)
+ (list :unlisted unlisted)))))
+
+
(defmethod print-object ((p Post) output)
(with-slots (creator id title content creator-id tags created-at hidden unlisted previous next previouslies) p
(format output "#<POST: ~{~{~a: ~s~}~^,~%~t ~}>" (list
@@ -46,6 +72,29 @@
(list :next next)
(list :previouslies previouslies)))))
+(defun admin-get-all-titles ()
+ (map 'list
+ (lambda (row)
+ ;; could this bs be macrofied?
+ (destructuring-bind (title month year id tags hidden unlisted) row
+ (make-instance 'Title
+ :title title
+ :month month
+ :year year
+ :id id
+ :tags (coerce (parse tags) 'list)
+ :hidden hidden
+ :unlisted unlisted)))
+ (postmodern:query "
+SELECT p.Title AS \"Title\",
+ EXTRACT(MONTH FROM p.created_at) AS \"Month\",
+ EXTRACT(YEAR FROM p.created_at) AS \"Year\",
+ p.id as \"Id\",
+ p.Tags as \"Tags\", p.hidden, p.unlisted
+FROM blog.Post p
+ORDER BY p.created_at DESC" :lists)))
+
+
;; (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>
diff --git a/src/view/admin/post-list.lisp b/src/view/admin/post-list.lisp
new file mode 100644
index 0000000..84e387a
--- /dev/null
+++ b/src/view/admin/post-list.lisp
@@ -0,0 +1,56 @@
+(defpackage murja.view.admin.post-list
+ (:use :cl :binding-arrows :spinneret
+ :easy-routes
+ :murja.settings :cl-hash-util
+ :murja.view.components.blogpost)
+ (:import-from :murja.view.components.tabs :deftab)
+ (:import-from :murja.model.post :admin-get-all-titles :title :month :year :id :tags :hidden :unlisted))
+
+(in-package :murja.view.admin.post-list)
+
+(defun admin-post-row (title-to-print)
+ (with-slots (title month year id tags hidden unlisted) title-to-print
+ (with-html
+ (:div.title-flex-container
+ (:span.post-admin-title (format nil "~a - ~a ~d" title month year))
+ (:a :href (format nil "/blog/post/edit/~d" id) "Edit")
+ (:div.post-admin-title
+ (:h* "Tags")
+ (:ul
+ (dolist (tag tags)
+ (:li
+ (:a :href (format nil "/blog/tags/~a" tag) tag)))))))))
+
+(defun tagcloud ()
+ (let* ((all-tags
+ (postmodern:query "select distinct (replace((elements->0)::text, '\"', ''))
+from blog.post as orig_post,
+ jsonb_array_elements(tags) as elements;" :column))
+ ;; there is probably a way to do this with a single query, but I couldn't codegolf it
+ (counted
+ (map 'list (lambda (tag)
+ (cons tag (caar (postmodern:query "SELECT count(*) from blog.post where tags ? $1" tag))))
+ all-tags)))
+ ;; (("lol" . 2) ("hehe" . 2) ("ilpo" . 1) ("säätää" . 1) ("tag1" . 1) ("tag2" . 1))
+
+
+
+ (with-html
+ (:section
+ (:h* ("Tags in the system (~d)" (length counted)))
+ (:ul
+ (dolist (c counted)
+ (destructuring-bind (tag . count) c
+ (:li (:a :href (format nil "/blog/tags/~a" tag)
+ (format nil "~a (~d)" tag count))))))))))
+
+
+(deftab blog/postadmin (:url "/blog/postadmin"
+ :title "Manage posts"
+ :require-login t
+ :needed-abilities ("create-post" "delete-post" "edit-post"))
+ (setf murja.view.components.root:*inject-to-sidebar* #'tagcloud)
+ (let ((titles (admin-get-all-titles)))
+ (:div.vertical-flex-container
+ (dolist (title titles)
+ (admin-post-row title)))))
diff --git a/src/view/components/root.lisp b/src/view/components/root.lisp
index c0db5f2..f31673f 100644
--- a/src/view/components/root.lisp
+++ b/src/view/components/root.lisp
@@ -4,7 +4,7 @@
:murja.middleware.auth
:murja.model.user)
- (:export :*inject-to-head* :root-component)
+ (:export :*inject-to-head* :*inject-to-sidebar* :root-component)
(:import-from :murja.posts.post-db :get-titles-by-year))
(in-package :murja.view.components.root)
@@ -48,25 +48,31 @@
(defvar *inject-to-head* nil)
+(defvar *inject-to-sidebar* nil)
(defmacro root-component (inner-component)
(assert inner-component)
"Returns the root html element of murja with `inner-component` embedded inside it"
`(format nil "<!DOCTYPE html>~%~a"
- (with-html-string
- (:html
- (:head
- (:link :href "/resources/murja.css" :rel "stylesheet" :type "text/css")
- (:script :src "/resources/newui.js")
- (:meta :charset "UTF-8")
- (dolist (head-element *inject-to-head*)
- (funcall head-element)))
-
- (:body
- (:header
- (:a :href "/" (gethash "blog-title" *settings* )))
+ ;; let's make *inject-to-sidebar* request-scoped
+ (let ((*inject-to-sidebar* nil))
+ (with-html-string
+ (:html
+ (:head
+ (:link :href "/resources/murja.css" :rel "stylesheet" :type "text/css")
+ (:meta :charset "UTF-8")
+ (dolist (head-element *inject-to-head*)
+ (funcall head-element)))
+
+ (:body
+ (:header
+ (:a :href "/" (gethash "blog-title" *settings* )))
- (:div :class "sidebar-flex"
- ,inner-component
- (:div :id "sidebar"
- (loginform/user-widget)
- (sidebar-tree))))))))
+ (:div :class "sidebar-flex"
+ ,inner-component
+ (:div :id "sidebar"
+ (loginform/user-widget)
+ (sidebar-tree)
+ (if *inject-to-sidebar*
+ (progn (format t "*inject-to-sidebar* is something~%")
+ (funcall *inject-to-sidebar*))
+ (format t "*inject-to-sidebar* is nothing~%"))))))))))