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~%"))))))))))