UPDATE: Biff now includes a com.biffweb/s3-request function that can be used with the examples below, with some slight modifications.
Add the provided s3.clj
file to your project, and set the following keys in config.edn
and secrets.env
:
;; config.edn
:s3/origin "https://nyc3.digitaloceanspaces.com"
:s3/edge "https://example.nyc3.cdn.digitaloceanspaces.com"
:s3/bucket "your-bucket"
:s3/access-key "..."
:s3/secret-key "S3_SECRET_KEY"
# secrets.env
S3_SECRET_KEY=...
Upload a publicly accessible object:
(s3/request ctx
{:bucket "your-bucket"
:method "PUT"
:key "some-key"
:body "some-body"
:headers {"x-amz-acl" "public-read"
"content-type" "text/plain"}})
:bucket
is optional; if unset,(:s3/bucket ctx)
will be used.:key
will be coerced to a string.:body
can be a string or a file.- The public url will be
(str (:s3/edge ctx) "/some-key")
. - To make the object private, set
"x-amz-acl"
to"private"
.
Retrieve an object (mainly useful for private objects):
(:body (s3/request ctx
{:bucket "your-bucket"
:method "GET"
:key "..."}))
Add a file input to your UI. It will trigger a request as soon as the user
selects a file. Don't forget :hx-encoding
:
[:input {:type "file"
:name "image"
:accept "image/apng, image/avif, image/gif, image/jpeg, image/png, image/svg+xml, image/webp"
:hx-encoding "multipart/form-data"
:hx-post "/upload"
:hx-swap "outerHTML"}]
(You may want to use hx-indicator while the file uploads.)
Add a backend route that receives the image and uploads it to S3:
(defn upload-image [{:keys [s3/edge multipart-params] :as ctx}]
(let [image-id (random-uuid)
{:keys [tempfile content-type]} (get multipart-params "image")
url (str edge "/" image-id)]
(s3/request ctx {:method "PUT"
:key image-id
:body tempfile
:headers {"x-amz-acl" "public-read"
"content-type" content-type}})
[:img {:src url}]))
(def plugin
{:routes [["/upload" {:post upload-image}]]})
After the upload completes, the input
element will be replaced with an img
element.
Note: It is possible for the backend to give a signed URL to the client which allows them to upload the image directly to S3. I haven't bothered figuring out how to do that.