Improve routing, add helpers

This commit is contained in:
Akira Tempaku 2025-06-21 11:55:20 +09:00
commit 96e7dadfdf
Signed by: paku
GPG key ID: 5B4E8402BCC50607
14 changed files with 135 additions and 123 deletions

View file

@ -21,7 +21,7 @@
("ningle-fbr" . ("ningle-fbr" .
(:class qlot/source/git:source-git (:class qlot/source/git:source-git
:initargs (:remote-url "https://github.com/skyizwhite/ningle-fbr.git") :initargs (:remote-url "https://github.com/skyizwhite/ningle-fbr.git")
:version "git-3c83e74a84e57f8ee2a9e98c4045d9b3e7a937f5")) :version "git-19aae06f7ff17f16f008133a3bf234b89ab7de1e"))
("lack-mw" . ("lack-mw" .
(:class qlot/source/git:source-git (:class qlot/source/git:source-git
:initargs (:remote-url "https://github.com/skyizwhite/lack-mw.git") :initargs (:remote-url "https://github.com/skyizwhite/lack-mw.git")

9
src/api/not-found.lisp Normal file
View file

@ -0,0 +1,9 @@
(defpackage #:website/api/not-found
(:use #:cl
#:jingle)
(:export #:handle-not-found))
(in-package #:website/api/not-found)
(defun handle-not-found ()
(set-response-status :not-found)
'(:|message| "Not found"))

View file

@ -1,17 +1,17 @@
(defpackage #:website/routes/api/revalidate (defpackage #:website/api/revalidate
(:use #:cl (:use #:cl
#:jingle #:jingle
#:access) #:access)
(:import-from #:website/lib/env (:import-from #:website/lib/env
#:microcms-webhook-key) #:microcms-webhook-key)
(:import-from #:website/helper (:import-from #:website/helper
#:get-request-body-plist) #:request-body-json->plist)
(:import-from #:website/lib/cms (:import-from #:website/lib/cms
#:clear-about-cache #:clear-about-cache
#:clear-works-cache #:clear-works-cache
#:clear-blog-cache) #:clear-blog-cache)
(:export #:handle-post)) (:export #:handle-post))
(in-package #:website/routes/api/revalidate) (in-package #:website/api/revalidate)
(defun handle-post (params) (defun handle-post (params)
(declare (ignore params)) (declare (ignore params))
@ -19,7 +19,7 @@
(microcms-webhook-key)) (microcms-webhook-key))
(set-response-status :unauthorized) (set-response-status :unauthorized)
(return-from handle-post '(:|message| "Invalid token"))) (return-from handle-post '(:|message| "Invalid token")))
(let* ((body (get-request-body-plist)) (let* ((body (request-body-json->plist))
(api (getf body :|api|)) (api (getf body :|api|))
(id (getf body :|id|)) (id (getf body :|id|))
(old-draft-key (accesses body :|contents| :|old| :|draftKey|)) (old-draft-key (accesses body :|contents| :|old| :|draftKey|))

View file

@ -1,27 +1,71 @@
(defpackage #:website/app (defpackage #:website/app
(:use #:cl (:use #:cl
#:jingle) #:jingle
#:hsx)
(:import-from #:jonathan
#:to-json)
(:import-from #:ningle-fbr (:import-from #:ningle-fbr
#:set-routes) #:set-routes)
(:import-from #:lack/middleware/mount
#:*lack-middleware-mount*)
(:import-from #:lack-mw (:import-from #:lack-mw
#:*trim-trailing-slash*) #:*trim-trailing-slash*)
(:import-from #:clack-errors (:import-from #:clack-errors
#:*clack-error-middleware*) #:*clack-error-middleware*)
(:import-from #:website/components/metadata
#:~metadata)
(:import-from #:website/components/scripts
#:~scripts)
(:import-from #:website/components/layout
#:~layout)
(:import-from #:website/lib/env (:import-from #:website/lib/env
#:dev-mode-p) #:dev-mode-p)
(:import-from #:website/renderer)
(:export #:*app*)) (:export #:*app*))
(in-package #:website/app) (in-package #:website/app)
(defparameter *page-app* (make-app))
(set-routes *page-app* :system :website :target-dir-path "pages")
(defmethod jingle:process-response :around ((app (eql *page-app*)) result)
(set-response-header :content-type "text/html; charset=utf-8")
(when (eq (request-method *request*) :get)
(let ((strategy (context :cache)))
(cond ((dev-mode-p)
(set-response-header :cache-control "private, no-store, must-revalidate"))
((eq strategy :ssr)
(set-response-header :cache-control "public, max-age=0, must-revalidate"))
((eq strategy :isr)
(set-response-header :cache-control "public, max-age=0, s-maxage=60, stale-while-revalidate=60"))
((eq strategy :sg)
(set-response-header :cache-control "public, max-age=0, s-maxage=31536000, must-revalidate")))))
(call-next-method app
(render-to-string
(hsx (html :lang "en"
(head
(~metadata :metadata (context :metadata))
(~scripts))
(body
(~layout result)))))))
(defparameter *api-app* (make-app))
(set-routes *api-app* :system :website :target-dir-path "api")
(defmethod jingle:process-response :around ((app (eql *api-app*)) result)
(set-response-header :content-type "application/json; charset=utf-8")
(call-next-method app (to-json result)))
(defun with-args (middleware &rest args)
(lambda (app)
(apply middleware app args)))
(defparameter *app* (defparameter *app*
(let ((app (make-app))) (progn
(set-routes app :system :website :target-dir-path "routes") (install-middleware *page-app*
(install-middleware app (lambda (app) (with-args *lack-middleware-mount* "/api" *api-app*))
(funcall *clack-error-middleware* (install-middleware *page-app*
app (with-args *clack-error-middleware* :debug (dev-mode-p)))
:debug (dev-mode-p)))) (install-middleware *page-app* *trim-trailing-slash*)
(install-middleware app *trim-trailing-slash*) (static-path *page-app* "/assets/" "assets/")
(static-path app "/assets/" "assets/") (configure *page-app*)))
(configure app)))
*app* *app*

View file

@ -5,18 +5,12 @@
#:make-flexi-stream) #:make-flexi-stream)
(:import-from #:jonathan (:import-from #:jonathan
#:parse) #:parse)
(:export #:api-request-p (:export #:request-body-json->plist
#:get-request-body-plist)) #:set-metadata
#:set-cache))
(in-package #:website/helper) (in-package #:website/helper)
(defun starts-with-p (prefix string) (defun request-body-json->plist ()
(let ((pos (search prefix string :start1 0 :end1 (length prefix) :start2 0)))
(and pos (= pos 0))))
(defun api-request-p ()
(starts-with-p "/api/" (request-uri *request*)))
(defun get-request-body-plist ()
(parse (parse
(let ((text-stream (make-flexi-stream (request-raw-body *request*) (let ((text-stream (make-flexi-stream (request-raw-body *request*)
:external-format :utf-8))) :external-format :utf-8)))
@ -24,3 +18,9 @@
(loop :for char := (read-char text-stream nil) (loop :for char := (read-char text-stream nil)
:while char :while char
:do (write-char char out)))))) :do (write-char char out))))))
(defun set-metadata (metadata)
(setf (context :metadata) metadata))
(defun set-cache (strategy)
(setf (context :cache) strategy))

View file

@ -1,21 +1,22 @@
(defpackage #:website/routes/about (defpackage #:website/pages/about
(:use #:cl (:use #:cl
#:hsx #:hsx
#:jingle) #:jingle
#:website/helper)
(:import-from #:website/lib/cms (:import-from #:website/lib/cms
#:fetch-about) #:fetch-about)
(:import-from #:website/components/article (:import-from #:website/components/article
#:~article) #:~article)
(:export #:handle-get)) (:export #:handle-get))
(in-package #:website/routes/about) (in-package #:website/pages/about)
(defparameter *metadata* (defparameter *metadata*
(list :title "about")) (list :title "about"))
(defun handle-get (params) (defun handle-get (params)
(setf (context :metadata) *metadata*) (set-metadata *metadata*)
(with-request-params ((draft-key "draft-key" nil)) params (with-request-params ((draft-key "draft-key" nil)) params
(setf (context :cache) (if draft-key :ssr :isr)) (set-cache (if draft-key :ssr :isr))
(let ((about (fetch-about :draft-key draft-key))) (let ((about (fetch-about :draft-key draft-key)))
(~article (~article
:title "About" :title "About"

View file

@ -1,15 +1,16 @@
(defpackage #:website/routes/blog/<id> (defpackage #:website/pages/blog/<id>
(:use #:cl (:use #:cl
#:hsx #:hsx
#:jingle) #:jingle
#:website/helper)
(:import-from #:website/lib/cms (:import-from #:website/lib/cms
#:fetch-blog-detail) #:fetch-blog-detail)
(:import-from #:website/routes/not-found (:import-from #:website/pages/not-found
#:handle-not-found) #:handle-not-found)
(:import-from #:website/components/article (:import-from #:website/components/article
#:~article) #:~article)
(:export #:handle-get)) (:export #:handle-get))
(in-package #:website/routes/blog/<id>) (in-package #:website/pages/blog/<id>)
(defun handle-get (params) (defun handle-get (params)
(with-request-params ((id :id nil) (with-request-params ((id :id nil)
@ -17,10 +18,10 @@
(let ((blog (fetch-blog-detail id :draft-key draft-key))) (let ((blog (fetch-blog-detail id :draft-key draft-key)))
(unless blog (unless blog
(return-from handle-get (handle-not-found))) (return-from handle-get (handle-not-found)))
(setf (context :cache) (if draft-key :ssr :isr)) (set-cache (if draft-key :ssr :isr))
(setf (context :metadata) (list :title (getf blog :title) (set-metadata (list :title (getf blog :title)
:description (getf blog :description) :description (getf blog :description)
:type "article")) :type "article"))
(hsx (hsx
(~article (~article
:title (getf blog :title) :title (getf blog :title)

View file

@ -1,21 +1,22 @@
(defpackage #:website/routes/blog/index (defpackage #:website/pages/blog/index
(:use #:cl (:use #:cl
#:hsx #:hsx
#:jingle) #:jingle
#:website/helper)
(:import-from #:website/lib/cms (:import-from #:website/lib/cms
#:fetch-blog-list) #:fetch-blog-list)
(:import-from #:website/lib/time (:import-from #:website/lib/time
#:asctime) #:asctime)
(:export #:handle-get)) (:export #:handle-get))
(in-package #:website/routes/blog/index) (in-package #:website/pages/blog/index)
(defparameter *metadata* (defparameter *metadata*
(list :title "blog")) (list :title "blog"))
(defun handle-get (params) (defun handle-get (params)
(declare (ignore params)) (declare (ignore params))
(setf (context :cache) :isr) (set-cache :isr)
(setf (context :metadata) *metadata*) (set-metadata *metadata*)
(let ((blogs (fetch-blog-list :page 1))) (let ((blogs (fetch-blog-list :page 1)))
(hsx (hsx
(section (section

View file

@ -1,13 +1,14 @@
(defpackage #:website/routes/index (defpackage #:website/pages/index
(:use #:cl (:use #:cl
#:hsx #:hsx
#:access #:access
#:jingle) #:jingle
#:website/helper)
(:import-from #:website/lib/cms (:import-from #:website/lib/cms
#:get-about) #:get-about)
(:export #:handle-get (:export #:handle-get
#:handle-head)) #:handle-head))
(in-package #:website/routes/index) (in-package #:website/pages/index)
(defparameter *links* (defparameter *links*
'(("Keyoxide" '(("Keyoxide"
@ -28,7 +29,7 @@
(defun handle-get (params) (defun handle-get (params)
(declare (ignore params)) (declare (ignore params))
(setf (context :cache) :sg) (set-cache :sg)
(hsx (hsx
(div :class "flex flex-col items-center justify-center h-full" (div :class "flex flex-col items-center justify-center h-full"
(img (img

23
src/pages/not-found.lisp Normal file
View file

@ -0,0 +1,23 @@
(defpackage #:website/pages/not-found
(:use #:cl
#:hsx
#:jingle
#:website/helper)
(:export #:handle-not-found))
(in-package #:website/pages/not-found)
(defparameter *metadata*
'(:title "404 Not Found"
:description "The page you are looking for may have been deleted or the URL might be incorrect."
:error t))
(defun handle-not-found ()
(set-response-status :not-found)
(set-cache :ssr)
(set-metadata *metadata*)
(hsx
(div :class "flex flex-col h-full items-center justify-center gap-y-6"
(h1 :class "font-bold text-2xl"
"404 Not Found")
(a :href "/" :class "text-lg text-pink-500 hover:underline"
"Back to TOP"))))

View file

@ -1,21 +1,22 @@
(defpackage #:website/routes/works (defpackage #:website/pages/works
(:use #:cl (:use #:cl
#:hsx #:hsx
#:jingle) #:jingle
#:website/helper)
(:import-from #:website/lib/cms (:import-from #:website/lib/cms
#:fetch-works) #:fetch-works)
(:import-from #:website/components/article (:import-from #:website/components/article
#:~article) #:~article)
(:export #:handle-get)) (:export #:handle-get))
(in-package #:website/routes/works) (in-package #:website/pages/works)
(defparameter *metadata* (defparameter *metadata*
(list :title "works")) (list :title "works"))
(defun handle-get (params) (defun handle-get (params)
(setf (context :metadata) *metadata*) (set-metadata *metadata*)
(with-request-params ((draft-key "draft-key" nil)) params (with-request-params ((draft-key "draft-key" nil)) params
(setf (context :cache) (if draft-key :ssr :isr)) (set-cache (if draft-key :ssr :isr))
(let ((works (fetch-works :draft-key draft-key))) (let ((works (fetch-works :draft-key draft-key)))
(~article (~article
:title "Works" :title "Works"

View file

@ -1,42 +0,0 @@
(defpackage #:website/renderer
(:use #:cl
#:hsx
#:jingle)
(:import-from #:jonathan
#:to-json)
(:import-from #:website/lib/env
#:dev-mode-p)
(:import-from #:website/helper
#:api-request-p)
(:import-from #:website/components/metadata
#:~metadata)
(:import-from #:website/components/scripts
#:~scripts)
(:import-from #:website/components/layout
#:~layout))
(in-package #:website/renderer)
(defmethod jingle:process-response :around ((app jingle:app) result)
(when (eq (request-method *request*) :get)
(let ((strategy (context :cache)))
(cond ((dev-mode-p)
(set-response-header :cache-control "private, no-store, must-revalidate"))
((eq strategy :ssr)
(set-response-header :cache-control "public, max-age=0, must-revalidate"))
((eq strategy :isr)
(set-response-header :cache-control "public, max-age=0, s-maxage=60, stale-while-revalidate=60"))
((eq strategy :sg)
(set-response-header :cache-control "public, max-age=0, s-maxage=31536000, must-revalidate")))))
(cond ((api-request-p)
(set-response-header :content-type "application/json; charset=utf-8")
(call-next-method app (to-json result)))
(t
(set-response-header :content-type "text/html; charset=utf-8")
(call-next-method app
(render-to-string
(hsx (html :lang "en"
(head
(~metadata :metadata (context :metadata))
(~scripts))
(body
(~layout result)))))))))

View file

@ -1,26 +0,0 @@
(defpackage #:website/routes/not-found
(:use #:cl
#:hsx
#:jingle)
(:import-from #:website/helper
#:api-request-p)
(:export #:handle-not-found))
(in-package #:website/routes/not-found)
(defparameter *metadata*
'(:title "404 Not Found"
:description "The page you are looking for may have been deleted or the URL might be incorrect."
:error t))
(defun handle-not-found ()
(set-response-status :not-found)
(setf (context :cache) :ssr)
(setf (context :metadata) *metadata*)
(if (api-request-p)
'(:|message| "404 Not Found")
(hsx
(div :class "flex flex-col h-full items-center justify-center gap-y-6"
(h1 :class "font-bold text-2xl"
"404 Not Found")
(a :href "/" :class "text-lg text-pink-500 hover:underline"
"Back to TOP")))))

View file

@ -1,8 +1,7 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
content: [ content: [
"./src/renderer.lisp", "./src/pages/**/*.lisp",
"./src/routes/**/*.lisp",
"./src/components/**/*.lisp", "./src/components/**/*.lisp",
], ],
theme: { theme: {