Implemented Plug.Upload Struct, Phoenix v1.7.7

this is the form

<.simple_form :let={f} for={@changeset} action={@action} multipart>
  <.error :if={@changeset.action}>
    Oops, something went wrong! Please check the errors below.
  <.input field={f[:title]} type="text" label="Title" />
   <.input field={f[:file]} type="file" label="Photo" />
  <.input field={f[:description]} type="text" label="Description" />
    <.button>Save Photo</.button>

this is the controller

  @max_file_size_bytes 2_000_000

  def create(conn, %{"photo" => photo_params}) do
    case Photos.create_photo(photo_params) do
      {:ok, photo} ->
        case validate_image_file(photo_params["file"]) do
          {:ok, %{filename: uploaded_file_name, path: file_path}} ->
            photo_with_file_name = Map.put(photo, :file_name, uploaded_file_name)

            File.cp(file_path, "/assets/img/#{uploaded_file_name}")

            |> assign(:uploaded_file_name, uploaded_file_name)
            |> put_flash(:info, "Photo created successfully.")
            |> redirect(to: ~s"/photos/#{photo}")

          {:flash_error, reason} ->
            |> put_flash(:error, reason)
            |> redirect(to: "/photos/new")

          {:error, reason} ->
            |> put_flash(:error, reason)
            |> redirect(to: "/photos/new")

        {:error, %Ecto.Changeset{} = changeset} ->
          render(conn, :new, changeset: changeset)

  defp validate_image_file(%Plug.Upload{filename: filename, path: file_path}) do
    # Check the file size before processing
    file_size_bytes = File.read_info!(file_path).size

    if file_size_bytes > @max_file_size_bytes do
      {:flash_error, "File size exceeds the maximum limit (2 MB)."}
      case, 4) do
        <<137, 80, 78, 71>> -> {:ok, %{filename: filename, path: file_path}} # PNG signature
        <<255, 216, 255>> -> {:ok, %{filename: filename, path: file_path}}  # JPEG signature
        <<71, 73, 70, 56>> -> {:ok, %{filename: filename, path: file_path}} # GIF87a signature
         _ -> {:error, "Invalid image file format."}
    _ -> {:error, "Error while processing the file."}

  defp validate_image_file(_), do: {:error, "No file uploaded."}

This is the request i’ve been try

[info] POST /photos
[debug] Processing with SandboxFeatutre.PhotoController.create/2
Parameters: %{"_csrf_token" => "bzUUVGBFED8JAwBIWyNTGSdJEjR9Q04f7EU895vzQP71nLbCRxJmEtxo", "photo" => %{"description" => "dsdsd", "file" => %Plug.Upload{path: "/tmp/plug-1690/multipart-1690891031-43570755477-2", content_type: "image/jpeg", filename: "sandboximage.jpeg"}, "title" => "tester"}}
Pipelines: [:browser]

output :

[info] Sent 500 in 23ms
[error] #PID<0.5594.0> running Phoenix.Endpoint.SyncCodeReloadPlug (connection #PID<0.5511.0>, stream id 8) terminated
Server: localhost:4000 (http)
Request: POST /photos
** (exit) an exception was raised:
    ** (Protocol.UndefinedError) protocol Phoenix.HTML.Safe not implemented for %Plug.Upload{path: "/tmp/plug-1690/multipart-1690891031-43570755477-2", content_type: "image/jpeg", filename: "index.jpeg"} of type Plug.Upload (a struct). This protocol is implemented for the following type(s): Atom, BitString, Date, DateTime, Decimal, Float, Integer, List, NaiveDateTime, Phoenix.HTML.Form, Phoenix.LiveComponent.CID, Phoenix.LiveView.Component, Phoenix.LiveView.Comprehension, Phoenix.LiveView.JS, Phoenix.LiveView.Rendered, Time, Tuple, URI

im not familiar with current version, is that any references about this error?

The new CoreComponents in 1.7 don’t have a specific clause for file inputs. You’d want to add one, which skips trying to set the value of the input. An file input value cannot be “set” via the html.

then what if I want to add a file upload feature, but save the file name in the database? and move the file to a folder like assets/img, please reference it so it works

Instead of using the .input component from the default core components, use .live_file_input component from Phoenix.Component instead.

How to use live_file_input in this case? Could you please share?

I know the way to use live_file_input with LiveView, but don’t know how to use it as change for .input component as codeanpeace said above.

I am reading the document: File Uploads — Phoenix v1.7.7. And I face the same error as Gelato faced.
I am following your suggestion to find out the way to add more file inputs for CoreComponent, but suddenly the error disappeared without any change, the document work and I don’t know why :smiley:

<.input field={f[:photo]} type=“file” label=“Photo” /> work without Protocol error and no need to update core_component.

Ahh, live_file_input is meant for LiveView only.

That said, you can render a LiveView within a “DeadView” aka a non-live regular controller view using live_render/3.

1 Like