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"}}):bucketis optional; if unset,(:s3/bucket ctx)will be used.:keywill be coerced to a string.:bodycan 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.