Backblaze and Phoenix LiveView Uploads

Here’s a minimal but complete working example for you.

# Utility module to deal with S3 operations
defmodule MyAppWeb.S3 do
  def presigned_put(opts) do
    key = Keyword.fetch!(opts, :key)
    max_file_size = opts[:max_file_size] || 10_000_000
    expires_in = opts[:expires_in] || 7200

    uri = "https://s3.us-east-005.backblazeb2.com/bucket-name/#{key}"

    url =
      :aws_signature.sign_v4_query_params(
        "secret",
        "secret2",
        "us-east-005",
        "S3",
        :calendar.universal_time(),
        "PUT",
        uri,
        ttl: expires_in,
        uri_encode_path: false,
        body_digest: "UNSIGNED-PAYLOAD"
      )

    {:ok, url}
  end
end
# Phoenix live upload
defmodule MyAppWeb.UploadLive do
  use MyAppWeb, :live_view

  @impl Phoenix.LiveView
  def mount(_params, _session, socket) do
    {:ok,
     socket
     |> assign(:uploaded_files, [])
     |> allow_upload(:avatar,
       max_file_size: 50_000_000,
       accept: ~w(.jpg .jpeg .png .gif .svg .webp),
       max_entries: 1,
       external: &presign_upload/2
     )}
  end

  defp presign_upload(entry, socket) do
    uploads = socket.assigns.uploads
    key = "public/#{URI.encode(entry.client_name)}"

    {:ok, presigned_url} = MyAppWeb.S3.presigned_put(key: key)

    meta = %{uploader: "S3", key: key, url: presigned_url}
    {:ok, meta, socket}
  end

end
// assets/js/app.js
...
...

const Uploaders = {}

Uploaders.S3 = function(entries, onViewError){
  entries.forEach(entry => {
    let {url} = entry.meta
    let xhr = new XMLHttpRequest()

    onViewError(() => xhr.abort())
    xhr.onload = () => {
      xhr.status >= 200 && xhr.status < 300 ? entry.progress(100) : entry.error()
    }
    xhr.onerror = () => entry.error()
    xhr.upload.addEventListener("progress", (event) => {
      if(event.lengthComputable){
        let percent = Math.round((event.loaded / event.total) * 100)
        if(percent < 100){ entry.progress(percent) }
      }
    })

    xhr.open("PUT", url, true)
    xhr.send(entry.file)
  })
}
...
...
let liveSocket = new LiveSocket("/live", Socket, {
  uploaders: Uploaders,
  params: {_csrf_token: csrfToken}
})
...
4 Likes