Last active
May 17, 2023 17:31
-
-
Save Wigny/471acb1049b3d5da02fb2df87803d632 to your computer and use it in GitHub Desktop.
Phoenix external upload to GCS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Application.put_env(:uploader, Uploader.Endpoint, | |
http: [ip: {127, 0, 0, 1}, port: 4001], | |
server: true, | |
render_errors: [ | |
formats: [html: Uploader.ErrorHTML], | |
layout: false | |
], | |
live_view: [signing_salt: "aaaaaaaa"], | |
secret_key_base: String.duplicate("a", 64) | |
) | |
Mix.install([ | |
{:plug_cowboy, "~> 2.6"}, | |
{:jason, "~> 1.0"}, | |
{:phoenix, "1.7.2"}, | |
{:phoenix_live_view, "0.18.18"}, | |
{:gcs_signed_url, "~> 0.4"} | |
]) | |
defmodule Uploader.ErrorHTML do | |
def render(template, _assigns) do | |
Phoenix.Controller.status_message_from_template(template) | |
end | |
end | |
defmodule Uploader.Layouts do | |
use Phoenix.Component | |
def render("root.html", assigns) do | |
~H""" | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |
<meta name="csrf-token" content={Phoenix.Controller.get_csrf_token()} /> | |
<.live_title>Uploader</.live_title> | |
<script src="https://unpkg.com/phoenix@1.7.2/priv/static/phoenix.min.js"></script> | |
<script src="https://unpkg.com/phoenix_live_view@0.18.18/priv/static/phoenix_live_view.min.js"></script> | |
<script type="module"> | |
import { createUpload } from "https://unpkg.com/@mux/upchunk@3.1.0/dist/upchunk.mjs"; | |
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content"); | |
const liveSocket = new LiveView.LiveSocket("/live", Phoenix.Socket, { | |
params: { _csrf_token: csrfToken }, | |
uploaders: { | |
UpChunk: (entries, onViewError) => { | |
entries.forEach(entry => { | |
const { file, meta: { endpoint, headers } } = entry; | |
const upload = createUpload({ endpoint, headers, file }); | |
onViewError(() => upload.pause()); | |
upload.on("error", (e) => { | |
entry.error(e.detail.message); | |
}); | |
upload.on("progress", (e) => { | |
if (e.detail < 100) { entry.progress(e.detail); } | |
}); | |
upload.on("success", () => { | |
entry.progress(100); | |
}); | |
}); | |
} | |
} | |
}); | |
liveSocket.connect(); | |
</script> | |
</head> | |
<body> | |
<%= @inner_content %> | |
</body> | |
</html> | |
""" | |
end | |
end | |
defmodule Uploader.HomeLive do | |
use Phoenix.LiveView | |
@google_application_credentials System.fetch_env!("GOOGLE_APPLICATION_CREDENTIALS") | |
def mount(_params, _session, socket) do | |
{ | |
:ok, | |
allow_upload(socket, :images, | |
accept: ~w(image/*), | |
max_entries: 1, | |
auto_upload: true, | |
external: &presign_upload/2, | |
progress: &handle_progress/3 | |
) | |
} | |
end | |
def render(assigns) do | |
~H""" | |
<div id="info"><%= Phoenix.Flash.get(@flash, :info) %></div> | |
<form phx-submit="save" phx-change="validate"> | |
<.live_file_input upload={@uploads.images} /> | |
</form> | |
""" | |
end | |
def handle_event("validate", _params, socket) do | |
{:noreply, socket} | |
end | |
def handle_event("save", _params, socket) do | |
{:noreply, socket} | |
end | |
defp presign_upload(entry, socket) do | |
client = GcsSignedUrl.Client.load_from_file(@google_application_credentials) | |
bucket = "bucket" | |
headers = ["Content-Type": entry.client_type] | |
sign_opts = [verb: "PUT", expires: 1800, headers: headers] | |
endpoint = GcsSignedUrl.generate_v4(client, bucket, "test/#{entry.client_name}", sign_opts) | |
{:ok, %{uploader: "UpChunk", endpoint: endpoint, headers: Map.new(headers)}, socket} | |
end | |
defp handle_progress(:images, %{done?: true} = entry, socket) do | |
{:noreply, put_flash(socket, :info, "File `#{entry.client_name}` uploaded!")} | |
end | |
defp handle_progress(:images, _entry, socket) do | |
{:noreply, socket} | |
end | |
end | |
defmodule Uploader.Router do | |
use Phoenix.Router | |
import Phoenix.LiveView.Router | |
pipeline :browser do | |
plug(:accepts, ["html"]) | |
plug(:fetch_session) | |
plug(:fetch_live_flash) | |
plug(:put_root_layout, {Uploader.Layouts, :root}) | |
plug(:protect_from_forgery) | |
end | |
scope "/", Uploader do | |
pipe_through(:browser) | |
live("/", HomeLive, :index) | |
end | |
end | |
defmodule Uploader.Endpoint do | |
use Phoenix.Endpoint, otp_app: :uploader | |
@session_options [ | |
store: :cookie, | |
key: "_uploader_key", | |
signing_salt: "aaaaaaaa", | |
same_site: "Lax" | |
] | |
socket("/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]) | |
plug(Plug.Session, @session_options) | |
plug(Uploader.Router) | |
end | |
{:ok, _} = Supervisor.start_link([Uploader.Endpoint], strategy: :one_for_one) | |
Process.sleep(:infinity) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment