web.lisp 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. (in-package :cl-user)
  2. (restas:define-module #:assboard.web
  3. (:use :cl :hunchentoot :assboard.utils :assboard.engine))
  4. (in-package #:assboard.web)
  5. ;; Parameters
  6. (setf (who:html-mode) :html5)
  7. (restas:define-route static/css ("css/:file" :content-type "text/css")
  8. (let ((css-path (merge-pathnames "css/" +project-path+)))
  9. (merge-pathnames file css-path)))
  10. (defclass renderer () ())
  11. (restas::register-pkgmodule-traits
  12. 'assboard.web
  13. :render-method (lambda () (make-instance 'assboard.web::renderer)))
  14. ;; Rendering
  15. (defgeneric finalize-page (data)
  16. (:documentation "Final rendering step"))
  17. (defgeneric render-route-data (route data)
  18. (:documentation "Process route-specific data"))
  19. (defmethod finalize-page ((data list))
  20. (who:with-html-output-to-string (out nil :prologue t)
  21. (:html
  22. (:head
  23. (:meta :charset "utf-8")
  24. (:meta :http-equiv "X-UA-Compatible" :content "IE=edge")
  25. (:meta :name "viewport" :content "width=device-width, initial-scale=1")
  26. (:title (who:str (getf data :title)))
  27. ;;(:link :rel "stylesheet" :href (restas:genurl 'static/css :file "styles.css"))
  28. )
  29. (:body
  30. (who:str (getf data :menu))
  31. (who:str (getf data :content))))))
  32. (defmethod render-route-data (route (data list))
  33. (list*
  34. :title (getf data :title "AssBoard")
  35. data))
  36. (defmethod restas:render-object ((designer renderer) (object list))
  37. (let ((full-data (render-route-data (restas:route-symbol restas:*route*)
  38. object)))
  39. (finalize-page full-data)))
  40. (defun posts-list (out posts)
  41. (who:with-html-output (out)
  42. (if posts
  43. (who:htm
  44. (:ul
  45. (loop for post in posts
  46. :do (who:htm
  47. (:li
  48. (:a :href (restas:genurl 'posting :hash (data::posting-hash post))
  49. (who:str (data::posting-title post)))
  50. " by "
  51. (:a :href (restas:genurl 'author
  52. :author (data::posting-author post))
  53. (who:str (data::posting-author post))))))))
  54. (who:htm (:ul (:li "No posts yet"))))))
  55. (defmethod render-route-data ((route (eql 'index)) (posts list))
  56. (list
  57. :title "AssBoard"
  58. :content
  59. (who:with-html-output-to-string (out)
  60. (:h1 "AssBoard open postings")
  61. (posts-list out posts)
  62. (:h2 "Add new posting")
  63. (:form :method "POST" :action (restas:genurl 'posting/post)
  64. (:input :type "text" :name "title" :placeholder "Title") (:br) (:br)
  65. (:textarea :name "body" :cols 80 :rows 10 :placeholder "Body") (:br)
  66. (:button :type "submit" "Prepare")))))
  67. (restas:define-route index ("")
  68. (pomo:query-dao
  69. 'data::posting
  70. (:select 'hash 'author 'status 'title 'maintainer 'created-on
  71. :from 'posting)))
  72. (defmethod render-route-data ((route (eql 'posting/post)) (data list))
  73. (list
  74. :title "Add new posting"
  75. :content
  76. (who:with-html-output-to-string (out)
  77. (:h1 "Add new posting")
  78. (if (getf data :error)
  79. (who:htm (:h2 "Error adding post:")
  80. (:p (who:str (getf data :error))))
  81. (who:htm
  82. (:p "To add new posting, clearsign (`gpg --clearsign`) the following text and paste results from '-----BEGIN PGP SIGNED MESSAGE-----' up to '-----END PGP SIGNATURE-----'")
  83. (:textarea :disabled t :cols 80 :rows 10
  84. (who:str (getf data :to-sign)))
  85. (:form :method "POST"
  86. (:textarea :name "clearsigned" :cols 80 :rows 20 :placeholder "clearsigned") (:br)
  87. (:button :type "submit" "Post")))))))
  88. (restas:define-route posting/post ("posting/" :method :post)
  89. (let ((clearsigned (hunchentoot:post-parameter "clearsigned")))
  90. (if clearsigned
  91. (handler-case
  92. (let ((posting (process-command clearsigned)))
  93. (restas:redirect 'posting :hash (data::posting-hash posting)))
  94. (command-error (err)
  95. (log:info "Error processing command ~A" err)
  96. (list :error err
  97. :clearsigned clearsigned)))
  98. (let ((title (hunchentoot:post-parameter "title"))
  99. (body (hunchentoot:post-parameter "body")))
  100. (if (or (equal title "") (equal body ""))
  101. (restas:redirect 'index)
  102. (list
  103. :to-sign (format-new-posting title body)))))))
  104. (defmethod render-route-data ((route (eql 'author)) (data list))
  105. (list
  106. :title (format nil "~A's postings @ AssBoard" (getf data :author))
  107. :content
  108. (who:with-html-output-to-string (out)
  109. (:h1 (who:str (getf data :author)) "'s postings")
  110. (who:str (posts-list out (getf data :posts))))))
  111. (restas:define-route author ("author/:author/")
  112. (let ((posts (pomo:query-dao
  113. 'data::posting
  114. (:select 'hash 'author 'status 'title 'maintainer 'created-on
  115. :from 'posting
  116. :where (:= 'author author)))))
  117. (if posts
  118. (list :author author :posts posts)
  119. hunchentoot:+HTTP-NOT-FOUND+)))
  120. (defmethod restas:render-object ((designer renderer) (object data::posting))
  121. (finalize-page
  122. (list
  123. :title (format nil "~A by ~A @ AssBoard"
  124. (data::posting-title object)
  125. (data::posting-author object))
  126. :content
  127. (who:with-html-output-to-string (out)
  128. (:h1 (who:str (data::posting-title object)))
  129. (:div (cl-markdown:markdown (data::posting-body object) :stream out))))))
  130. (restas:define-route posting ("posting/:hash/")
  131. (let ((post (first (pomo:select-dao 'data::posting (:= 'hash hash)))))
  132. (if post post
  133. hunchentoot:+HTTP-NOT-FOUND+)))