diff --git a/.env.example b/.env.example index 5166f7b..742b580 100644 --- a/.env.example +++ b/.env.example @@ -2,5 +2,6 @@ WEBSITE_ENV= WEBSITE_URL= MICROCMS_SERVICE_DOMAIN= MICROCMS_API_KEY= +MICROCMS_WEBHOOK_KEY= CLOUDFLARE_ZONE_ID= CLOUDFLARE_API_KEY= diff --git a/qlfile b/qlfile index 2f0ba9c..b4862b2 100644 --- a/qlfile +++ b/qlfile @@ -9,3 +9,6 @@ ql clack-errors git microcms https://github.com/skyizwhite/microcms-lisp-sdk ql local-time ql function-cache +ql jonathan +ql access +ql flexi-streams diff --git a/qlfile.lock b/qlfile.lock index f41858f..6421bac 100644 --- a/qlfile.lock +++ b/qlfile.lock @@ -46,3 +46,15 @@ (:class qlot/source/ql:source-ql :initargs (:%version :latest) :version "ql-2023-10-21")) +("jonathan" . + (:class qlot/source/ql:source-ql + :initargs (:%version :latest) + :version "ql-2020-09-25")) +("access" . + (:class qlot/source/ql:source-ql + :initargs (:%version :latest) + :version "ql-2024-10-12")) +("flexi-streams" . + (:class qlot/source/ql:source-ql + :initargs (:%version :latest) + :version "ql-2024-10-12")) diff --git a/src/helper.lisp b/src/helper.lisp new file mode 100644 index 0000000..c208395 --- /dev/null +++ b/src/helper.lisp @@ -0,0 +1,26 @@ +(uiop:define-package #:website/helper + (:use #:cl + #:jingle) + (:import-from #:flexi-streams + #:make-flexi-stream) + (:import-from #:jonathan + #:parse) + (:export #:api-request-p + #:get-request-body-plist)) +(in-package #:website/helper) + +(defun starts-with-p (prefix string) + (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 + (let ((text-stream (make-flexi-stream (request-raw-body *request*) + :external-format :utf-8))) + (with-output-to-string (out) + (loop :for char := (read-char text-stream nil) + :while char + :do (write-char char out)))))) diff --git a/src/lib/cms.lisp b/src/lib/cms.lisp index e61aa08..7f75c80 100644 --- a/src/lib/cms.lisp +++ b/src/lib/cms.lisp @@ -17,19 +17,19 @@ (setf microcms:*service-domain* (microcms-service-domain)) (setf microcms:*api-key* (microcms-api-key)) -(defmacro memorize (name timeout) +(defmacro memorize (name) (let ((origin (gensym))) `(progn (setf (fdefinition ',origin) (fdefinition ',name)) - (defcached (,name :timeout ,timeout) (&key query) + (defcached ,name (&key query) (,origin :query query))))) (define-object-client about) -(memorize get-about 60) +(memorize get-about) (define-object-client work) -(memorize get-work 60) +(memorize get-work) (define-list-client blog) -(memorize get-blog-list 60) -(memorize get-blog-detail 60) +(memorize get-blog-list) +(memorize get-blog-detail) diff --git a/src/lib/env.lisp b/src/lib/env.lisp index a6e1ffb..f0a4bb1 100644 --- a/src/lib/env.lisp +++ b/src/lib/env.lisp @@ -18,3 +18,4 @@ (env-var website-url "WEBSITE_URL") (env-var microcms-service-domain "MICROCMS_SERVICE_DOMAIN") (env-var microcms-api-key "MICROCMS_API_KEY") +(env-var microcms-webhook-key "MICROCMS_WEBHOOK_KEY") diff --git a/src/renderer.lisp b/src/renderer.lisp index 80fd48b..7863e38 100644 --- a/src/renderer.lisp +++ b/src/renderer.lisp @@ -2,8 +2,10 @@ (:use #:cl #:hsx #:jingle) - (:import-from #:hsx/element - #:element) + (:import-from #:jonathan + #:to-json) + (:import-from #:website/helper + #:api-request-p) (:import-from #:website/components/metadata #:~metadata) (:import-from #:website/components/scripts @@ -13,17 +15,21 @@ (in-package #:website/renderer) (defmethod jingle:process-response :around ((app jingle:app) result) - (set-response-header :content-type "text/html; charset=utf-8") (when (eq (request-method *request*) :get) (set-response-header :cache-control "public, max-age=60")) - (call-next-method app - (render-to-string - (hsx (html :lang "ja" - (head - (~metadata :metadata (context :metadata)) - (~scripts)) - (body - :hx-ext "head-support, response-targets, preload" - :hx-boost "true" :hx-swap "transition:true" - :hx-target-404 "body" :hx-target-5* "body" - (~layout result))))))) + (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 "ja" + (head + (~metadata :metadata (context :metadata)) + (~scripts)) + (body + :hx-ext "head-support, response-targets, preload" + :hx-boost "true" :hx-swap "transition:true" + :hx-target-404 "body" :hx-target-5* "body" + (~layout result))))))))) diff --git a/src/routes/about.lisp b/src/routes/about.lisp index e8dcc44..1fd330c 100644 --- a/src/routes/about.lisp +++ b/src/routes/about.lisp @@ -1,7 +1,8 @@ (defpackage #:website/routes/about (:use #:cl #:hsx - #:jingle) + #:jingle + #:access) (:import-from #:website/lib/cms #:get-about) (:import-from #:website/lib/time @@ -25,7 +26,7 @@ (div :class "flex justify-center" (figure :class "flex flex-col items-center" (img - :src (getf (getf about :avatar) :url) + :src (accesses about :avatar :url) :alt "avatar" :class "size-40 rounded-xl shadow-sm avatar") (figcaption (getf about :avatar-caption)))) (raw! (getf about :content)) diff --git a/src/routes/api/revalidate.lisp b/src/routes/api/revalidate.lisp new file mode 100644 index 0000000..7c0457e --- /dev/null +++ b/src/routes/api/revalidate.lisp @@ -0,0 +1,24 @@ +(defpackage #:website/routes/api/revalidate + (:use #:cl + #:jingle) + (:import-from #:function-cache + #:clear-cache) + (:import-from #:website/lib/env + #:microcms-webhook-key) + (:import-from #:website/helper + #:get-request-body-plist) + (:import-from #:website/lib/cms + #:get-about) + (:export #:handle-post)) +(in-package #:website/routes/api/revalidate) + +(defun handle-post (params) + (declare (ignore params)) + (unless (string= (car (get-request-header "X-MICROCMS-WEBHOOK-KEY")) + (microcms-webhook-key)) + (set-response-status :unauthorized) + (return-from handle-post '(:|message| "Invalid token"))) + (let* ((body (get-request-body-plist)) + (api (getf body :|api|))) + (cond ((string= api "about") (clear-cache 'get-about))) + '(:|message| "ok"))) diff --git a/src/routes/index.lisp b/src/routes/index.lisp index 8797b29..398abbc 100644 --- a/src/routes/index.lisp +++ b/src/routes/index.lisp @@ -1,6 +1,7 @@ (defpackage #:website/routes/index (:use #:cl - #:hsx) + #:hsx + #:access) (:import-from #:website/lib/cms #:get-about) (:export #:handle-get @@ -30,7 +31,7 @@ (hsx (div :class "flex flex-col items-center justify-center h-full" (img - :src (getf (getf about :avatar) :url) + :src (accesses about :avatar :url) :alt "avatar" :class "size-40 rounded-xl shadow-sm avatar") (div :class "flex flex-col items-center gap-2 py-6" (h1 :class "font-bold text-2xl text-center" diff --git a/src/routes/not-found.lisp b/src/routes/not-found.lisp index b2caaad..163ff70 100644 --- a/src/routes/not-found.lisp +++ b/src/routes/not-found.lisp @@ -2,6 +2,8 @@ (:use #:cl #:hsx #:jingle) + (:import-from #:website/helper + #:api-request-p) (:export #:handle-not-found)) (in-package #:website/routes/not-found) @@ -12,9 +14,11 @@ (defun handle-not-found () (setf (context :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")))) + (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")))))