From bc06ba5a1b7804c02e199abe96af18b569a30491 Mon Sep 17 00:00:00 2001 From: Akira Tempaku <paku@skyizwhite.dev> Date: Sat, 19 Apr 2025 00:14:19 +0900 Subject: [PATCH] init --- .gitignore | 1 + README.md | 53 ++++++++++++++++++++++++ microcms.asd | 8 ++++ qlfile | 5 +++ qlfile.lock | 24 +++++++++++ src/main.lisp | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 201 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 microcms.asd create mode 100644 qlfile create mode 100644 qlfile.lock create mode 100644 src/main.lisp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95b52b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.qlot diff --git a/README.md b/README.md new file mode 100644 index 0000000..42a1504 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# microcms-lisp-sdk + +microcms-lisp-sdk is a Common Lisp SDK for interacting with [microCMS](https://microcms.io) via its REST API. It provides macros to define client functions for both list and object type endpoints. + +## ⚙️ Configuration + +Before making API requests, set your API key and service domain: + +```lisp +(setf microcms:*api-key* "your-api-key") +(setf microcms:*service-domain* "your-service-domain") ; e.g., "example" for example.microcms.io +``` + +## 🚀 Usage + +### List Type Endpoint + +Use `define-list-client` macro to define functions for list-type content. + +```lisp +(microcms:define-list-client article) +``` +This will generate the following functions: + +| Function Name | Arguments | Description | +|---------------|-----------|-------------| +| `get-article-list` | (&optional `query`) | Get a list of articles. | +| `get-article-list-detail` | (`id`, &optional `query`) | Get details of a specific article by ID. | +| `create-article` | (`content`, &optional `query`) | Create a new article with the given content. | +| `update-article` | (`id`, `content`) | Update an existing article by its ID with new content. | +| `delete-article` | (`id`) | Delete an article by its ID. | + +Note: query arguments should be provided as a property list (plist), where keys use kebab-case (e.g., `:draft-key`). + +### Object Type Endpoint + +Use `define-object-client` macro to define functions for object-type content. + +```lisp +(microcms:define-object-client profile) +``` + +This will generate the following functions: + +| Function Name | Arguments | Description | +|---------------|-----------|-------------| +| `get-profile-object` | () | Retrieve the profile object. | +| `update-profile` | (`content`) | Update the content of the profile object. | + +### 📄 License + +MIT License +© 2025 Akira Tempaku diff --git a/microcms.asd b/microcms.asd new file mode 100644 index 0000000..ba0929f --- /dev/null +++ b/microcms.asd @@ -0,0 +1,8 @@ +(defsystem "microcms" + :version "0.1.0" + :description "microCMS Common Lisp SDK." + :author "Akira Tempaku" + :license "MIT" + :class :package-inferred-system + :pathname "src" + :depends-on ("microcms/main")) diff --git a/qlfile b/qlfile new file mode 100644 index 0000000..a7de216 --- /dev/null +++ b/qlfile @@ -0,0 +1,5 @@ +ql alexandria +ql dexador +ql jonathan +ql quri +ql kebab diff --git a/qlfile.lock b/qlfile.lock new file mode 100644 index 0000000..26a1d71 --- /dev/null +++ b/qlfile.lock @@ -0,0 +1,24 @@ +("quicklisp" . + (:class qlot/source/dist:source-dist + :initargs (:distribution "https://beta.quicklisp.org/dist/quicklisp.txt" :%version :latest) + :version "2024-10-12")) +("alexandria" . + (:class qlot/source/ql:source-ql + :initargs (:%version :latest) + :version "ql-2024-10-12")) +("dexador" . + (:class qlot/source/ql:source-ql + :initargs (:%version :latest) + :version "ql-2024-10-12")) +("jonathan" . + (:class qlot/source/ql:source-ql + :initargs (:%version :latest) + :version "ql-2020-09-25")) +("quri" . + (:class qlot/source/ql:source-ql + :initargs (:%version :latest) + :version "ql-2024-10-12")) +("kebab" . + (:class qlot/source/ql:source-ql + :initargs (:%version :latest) + :version "ql-2015-06-08")) diff --git a/src/main.lisp b/src/main.lisp new file mode 100644 index 0000000..6ab8019 --- /dev/null +++ b/src/main.lisp @@ -0,0 +1,110 @@ +(defpackage #:microcms + (:nicknames #:microcms/main) + (:use #:cl) + (:import-from #:alexandria + #:alist-plist + #:assoc-value + #:remove-from-plist + #:symbolicate) + (:import-from #:jonathan + #:to-json + #:parse) + (:import-from #:dexador + #:request + #:http-request-failed) + (:import-from #:quri + #:make-uri + #:render-uri) + (:import-from #:kebab + #:to-camel-case) + (:export #:*api-key* + #:*service-domain* + #:define-list-client + #:define-object-client)) +(in-package #:microcms) + +(defparameter *api-key* nil) +(defparameter *service-domain* nil) + +(defun %get-list (endpoint &optional query) + (%request :get endpoint "" query)) + +(defun %get-list-detail (endpoint id &optional (query nil)) + (%request :get endpoint id query)) + +(defun %create (endpoint content &optional query) + (let ((id (getf content :|id|)) + (pure-content (remove-from-plist content :|id|))) + (if id + (%put endpoint id pure-content) + (%post endpoint pure-content query)))) + +(defun %put (endpoint id content &optional query) + (%request :put endpoint id query content)) + +(defun %post (endpoint content &optional query) + (%request :post endpoint "" query content)) + +(defun %update (endpoint id content) + (%request :patch endpoint id nil content)) + +(defun %delete (endpoint id) + (%request :delete endpoint id)) + +(defun %get-object (endpoint) + (%request :get endpoint)) + +(defun %request (method endpoint &optional (path "") (query nil) (body nil)) + (let* ((url (%build-uri endpoint path query)) + (headers `(("X-MICROCMS-API-KEY" . ,*api-key*) + ("Content-Type" . "application/json")))) + (format t "API request url: ~a~%" url) + (handler-case + (multiple-value-bind (resp-body status resp-headers) + (request url + :method method + :headers headers + :content (and body (to-json body)) + :force-binary nil) + (format t "API response status: ~a~%" status) + (when (and (stringp resp-body) + (search "application/json" (gethash "content-type" resp-headers))) + (parse resp-body))) + (http-request-failed () + '(:|error| "API request failed"))))) + +(defun %build-uri (endpoint &optional (path "") (query nil)) + (let ((uri (make-uri + :scheme "https" + :host (format nil "~A.microcms.io" *service-domain*) + :path (format nil "/api/v1/~A/~A" endpoint path) + :query (%build-query query)))) + (render-uri uri))) + +(defun %build-query (query) + (loop :for (key val) :on query :by #'cddr + :collect (cons (to-camel-case (symbol-name key)) val))) + +(defmacro define-list-client (endpoint) + (let ((str-endpoint (string-downcase (string endpoint)))) + `(progn + (defun ,(symbolicate 'get- endpoint '-list) (&optional query) + (%get-list ,str-endpoint query)) + (defun ,(symbolicate 'get- endpoint '-list-detail) (id &optional query) + (%get-list-detail ,str-endpoint id query)) + (defun ,(symbolicate 'create- endpoint) (content &optional query) + (%create ,str-endpoint content query)) + (defun ,(symbolicate 'update- endpoint) (id content) + (%update ,str-endpoint id content)) + (defun ,(symbolicate 'delete- endpoint) (id) + (%delete ,str-endpoint id)) + nil))) + +(defmacro define-object-client (endpoint) + (let ((str-endpoint (string-downcase (string endpoint)))) + `(progn + (defun ,(symbolicate 'get- endpoint '-object) () + (%get-object ,str-endpoint)) + (defun ,(symbolicate 'update- endpoint) (content) + (%update ,str-endpoint nil content)) + nil)))