Add listing and getting a storage bucket, process API response

Processing the API response currently only works when receiving JSON
data, otherwise the content other than the HTTP headers will be
ignored.
This commit is contained in:
Lucien Cartier-Tilet 2022-07-13 15:34:50 +02:00
parent df44f7d979
commit e91c6a80aa
Signed by: phundrak
GPG Key ID: BD7789E705CB8DCA
3 changed files with 275 additions and 30 deletions

View File

@ -6,8 +6,9 @@
~appwrite.el~ is a server SDK for [[https://appwrite.io/][Appwrite]], a self-hosted
backend-as-a-service platform.
* Table of Contents :TOC_2_gh:
* Table of contents :TOC_2_gh:
- [[#introduction][Introduction]]
- [[#how-complete-is-this-sdk-anyways][How complete is this SDK anyways?]]
- [[#installation][Installation]]
- [[#manual-installation][Manual Installation]]
- [[#straight--use-package][Straight + use-package]]
@ -15,8 +16,13 @@ backend-as-a-service platform.
- [[#contributing][Contributing]]
- [[#license][License]]
* Installation
* How complete is this SDK anyways?
~appwrite.el~ is currently in its infancy, so dont expect yet most of
the functionalities to be available. If you want to see a detailed
view of the parity between this library and the server API, check out
the [[file:TODOs.org][TODOs]] file in this repository.
* Installation
** Manual Installation
To manually install ~appwrite.el~, clone the repository wherever you
want, then put the path to the repository in your ~load-path~. You can

150
TODOs.org Normal file
View File

@ -0,0 +1,150 @@
* TODO Implement API [0/8]
** TODO Accounts [0/20]
*** TODO Get Account
*** TODO Get Account Preferences
*** TODO Get Account Sessions
*** TODO Get Account Logs
*** TODO Get Session By ID
*** TODO Update Account Name
*** TODO Update Account Password
*** TODO Update Account Email
*** TODO Update Account Phone
*** TODO Update Account Preferences
*** TODO Update Account Status
*** TODO Delete Account Session
*** TODO Update Session (Refresh Tokens)
*** TODO Delete All Account Sessions
*** TODO Create Password Recovery
*** TODO Create Password Recovery (confirmation)
*** TODO Create Email Verification
*** TODO Create Email Verification (confirmation)
*** TODO Create Phone Verification
*** TODO Create Phone Verification (confirmation)
** TODO Users [0/18]
*** TODO Create User
*** TODO List Users
*** TODO Get User
*** TODO Get User Preferences
*** TODO Get User Sessions
*** TODO Get User Memberships
*** TODO Get User Logs
*** TODO Update User Status
*** TODO Update Email Verification
*** TODO Update Phone Verification
*** TODO Update Name
*** TODO Update Password
*** TODO Update Email
*** TODO Update Phone
*** TODO Update User Preferences
*** TODO Delete User Session
*** TODO Delete User Sessions
*** TODO Delete User
** TODO Teams [0/11]
*** TODO Create Team
*** TODO List Teams
*** TODO Get Team
*** TODO Update Team
*** TODO Delete Team
*** TODO Create Team Membership
*** TODO Get Team Memberships
*** TODO Get Team Membership
*** TODO Update Membership Roles
*** TODO Update Team Membership Status
*** TODO Delete Team Membership
** TODO Databases [0/30]
*** TODO Create Database
*** TODO List Databases
*** TODO Get Database
*** TODO Update Database
*** TODO Delete Database
*** TODO Create Collection
*** TODO List Collections
*** TODO Get Collection
*** TODO Update Collection
*** TODO Delete Collection
*** TODO Create String Attribute
*** TODO Create Email Attribute
*** TODO Create Enum Attribute
*** TODO Create IP Address Attribute
*** TODO Create URL Attribute
*** TODO Create Integer Attribute
*** TODO Create Float Attribute
*** TODO Create Boolean Attribute
*** TODO List Attributes
*** TODO Get Attribute
*** TODO Delete Attribute
*** TODO Create Index
*** TODO List Indexes
*** TODO Get Index
*** TODO Delete Index
*** TODO Create Document
*** TODO List Documents
*** TODO Get Document
*** TODO Update Document
*** TODO Delete Document
** TODO Storage [4/13]
*** DONE Create bucket
CLOSED: [2022-07-13 Wed 14:28]
*** DONE List buckets
CLOSED: [2022-07-13 Wed 15:33]
*** DONE Get Bucket
CLOSED: [2022-07-13 Wed 15:34]
*** TODO Update Bucket
*** DONE Delete Bucket
CLOSED: [2022-07-13 Wed 14:28]
*** TODO Create File
*** TODO List Files
*** TODO Get File
*** TODO Get File Preview
*** TODO Get File for Download
*** TODO Get File for View
*** TODO Update File
*** TODO Delete File
** TODO Functions [0/15]
*** TODO Create Function
*** TODO List Functions
*** TODO List runtimes
*** TODO Get Function
*** TODO Update Function
*** TODO Update Function Deployment
*** TODO Delete Function
*** TODO Create Deployment
*** TODO List Deployments
*** TODO Get Deployment
*** TODO Delete Deployment
*** TODO Create Execution
*** TODO List Executions
*** TODO Get Execution
*** TODO Retry Build
** TODO Avatars [0/7]
*** TODO Get Credit Card Icon
*** TODO Get Browser Icon
*** TODO Get Country Flag
*** TODO Get Image from URL
*** TODO Get Favicon
*** TODO Get QR Code
*** TODO Get User Initials
** TODO Health [0/10]
*** TODO Get HTTP
*** TODO Get DB
*** TODO Get Cache
*** TODO Get Time
*** TODO Get Webhooks Queue
*** TODO Get Logs Queue
*** TODO Get Certificates Queue
*** TODO Get Functions Queue
*** TODO Get Local Storage
*** TODO Get Antivirus
* TODO Write a payload builder
Writing basically the same line over and over when building JSON
payloads is inefficient, see ~appwrite-storage-create-bucket~. There
must be a way to make a macro to make the creation of payloads
simpler.

View File

@ -83,44 +83,82 @@ If it does not contain an API version, prefix \"/v1\" by default."
"/")
api)))
(defun appwrite--message-failure (message status json-message)
"Display MESSAGE followed by JSON-MESSAGE.
This will show a message in the modeline in this format:
[status STATUS] MESSAGE: JSON-MESSAGE"
(message "[status %d] %s: %s" status message json-message))
(defun appwrite--process-response (message success-status response)
"In case of failure when calling the Appwrite API, display MESSAGE.
The function considers a call to the API a failure in case the
HTTP status code in RESPONSE differs from SUCCESS-STATUS, the
HTTP status code hoped for. If thats the case, warn the user,
see `appwrite--message-failure'. Else, return the JSON returned
by the API."
(let ((status (car response))
(json (cdr response)))
(if (= status success-status)
json
(appwrite--message-failure message status (gethash "message" json)))))
(cl-defun appwrite--query-api (&key (method "GET")
api
data
payload
(content-type "application/json")
data-alist-p
payload-alist-p
asyncp
callback)
"Perform a method METHOD to API with DATA as its payload.
"Perform a method METHOD to API with PAYLOAD as its payload.
CONTENT-TYPE is whichever miime-type is being used.
If CONTENT-TYPE is \"application/json\", DATA is subject ot
If CONTENT-TYPE is \"application/json\", PAYLOAD is subject ot
automatic conversion depending on its type.
- If DATA passes `plistp', it will be converted to JSON as a
- If PAYLOAD passes `plistp', it will be converted to JSON as a
plist.
- If DATA passes `hash-table-p', it will be converted to JSON as
a hash table.
- If DATA-ALIST-P is t, DATA will be converted to JSON as an
- If PAYLOAD passes `hash-table-p', it will be converted to JSON
as a hash table.
- If PAYLOAD-ALIST-P is t, PAYLOAD will be converted to JSON as an
associative table.
- Else, DATA will be passed a string containing JSON.
- Else, PAYLOAD will be passed a string containing JSON.
If ASYNCP is t, `appwrite--post-api' will be asynchronous.
CALLBACK must then be set as it will be called once the request
finishes. See `url-retrieve'."
finishes. See `url-retrieve'.
The function returns a pair composed of the HTTP status code as
its car. The cdr is a hash table from the response answer if
Content-Type in the headers is \"application/json\"."
(let* ((url (appwrite--get-full-url api))
(url-request-method method)
(url-request-extra-headers `(("X-Appwrite-key" . ,appwrite-api-key)
("X-Appwrite-Project" . ,appwrite-project)
("Content-type" . ,content-type)))
(url-request-data (cond ((not (string= content-type "application/json"))
data)
((plistp data) (json-encode-plist data))
((hash-table-p data) (json-encode data))
(data-alist-p (json-encode-alist data))
(t data))))
payload)
((plistp payload) (json-encode-plist payload))
((hash-table-p payload) (json-encode payload))
(payload-alist-p (json-encode-alist payload))
(t payload))))
(if asyncp
(url-retrieve url callback)
(with-current-buffer (url-retrieve-synchronously url)
(buffer-string)))))
(let (http-code json)
(message ";;;;;;;;;;;;")
(message "%s" (buffer-string))
(save-match-data
(goto-char (point-min))
(re-search-forward (rx bol "HTTP" (+ (not space)) " " (group (+ digit))))
(setq http-code (string-to-number
(buffer-substring-no-properties (match-beginning 1)
(match-end 1)))))
(when (re-search-forward "^Content-Type: application/json" nil t)
(goto-char (point-min))
(re-search-forward "^$")
(delete-region (point) (point-min))
(setq json (json-parse-buffer)))
`(,http-code . ,json))))))
;;; Account
@ -170,25 +208,76 @@ larger than 20MB are skipped. t by default.
If ANTIVIRUS is t, enable antivirus for the bucket. Files larger
than 20MB are skipped. t by default."
(let ((data `(bucketId ,id name ,name permission ,permission)))
(let ((payload `(bucketId ,id name ,name permission ,permission)))
(when read
(setq data (append data `(read ,read))))
(setq payload (append payload `(read ,read))))
(when write
(setq data (append data `(write ,write))))
(setq payload (append payload `(write ,write))))
(when maximum-file-size
(setq data (append data `(maximumFileSize ,maximum-file-size))))
(setq payload (append payload `(maximumFileSize ,maximum-file-size))))
(when allowed-file-extensions
(setq data (append data `(allowedFileExtensions ,allowed-file-extensions))))
(setq data (append data `(enabled ,(if enabled t :json-false))))
(setq data (append data `(encryption ,(if encryption t :json-false))))
(setq data (append data `(antivirus ,(if antivirus t :json-false))))
;; (json-encode-plist data)
(appwrite--query-api :method "POST" :api "storage/buckets" :data (json-encode-plist data))))
(setq payload (append payload `(allowedFileExtensions ,allowed-file-extensions))))
(setq payload (append payload `(enabled ,(if enabled t :json-false))))
(setq payload (append payload `(encryption ,(if encryption t :json-false))))
(setq payload (append payload `(antivirus ,(if antivirus t :json-false))))
;; (json-encode-plist payload)
(let ((response (appwrite--query-api :method "POST"
:api "/v1/storage/buckets"
:payload (json-encode-plist payload))))
(appwrite--process-response (concat "Failed to create bucket " id)
201
response))))
(cl-defun appwrite-storage-list-buckets (&key search (limit 25) offset cursor cursor-direction order-type)
"List of all storage buckets.
SEARCH is a string to filter the list results when non-nil. Max
length of 256 chars.
LIMIT is the maximum amount of buckets returned by the
query (default value: 25).
OFFSET is the results offset with which the user can manage the
pagination of the results when non-nil.
CURSOR is the id of the bucket used as the starting point of the
query, excluding the bucket itself.
CURSOR-DIRECTION can be either \\='after or \\='before.
ORDER-TYPE can be either \\='ascending or \\='descending.
If the query is successful, return a hash table made from the
acquired JSON. Otherwise, return nil and warn the user."
(let (payload)
(when search (setq payload (append payload `(search ,search))))
(setq payload (append payload `(limit ,limit)))
(when offset (setq payload (append payload `(offset ,offset))))
(when cursor (setq payload (append payload `(cursor ,cursor))))
(when cursor-direction (setq payload (append payload `(cursor-direction ,cursor-direction))))
(when order-type (setq payload (append payload `(order-type ,order-type))))
(let* ((response (appwrite--query-api :api "/v1/storage/buckets"
:payload (json-encode-plist payload)))
(status (car response))
(json (cdr response)))
(if (eq 200 status)
json
(appwrite--message-failure "Failed to list buckets" 200 (gethash "message" json))))))
(defun appwrite-storage-get-bucket (id)
"Get bucket with id ID."
(let ((response (appwrite--query-api :api (concat "/v1/storage/buckets/" id))))
(appwrite--process-response (concat "Failed to get bucket " id)
200
response)))
(defun appwrite-storage-delete-bucket (id)
"Delete bucket with id ID."
(appwrite--query-api :method "DELETE"
:api (concat "storage/buckets/" id)))
(let ((response (appwrite--query-api :method "DELETE"
:api (concat "/v1/storage/buckets/" id))))
(appwrite--process-response (concat "Failed to delete bucket " id)
204
response)))
;;; Functions