I am using waffle and waffle ecto, but I also rolled my own library to manage file upload.
You don’t need a controller if the path of the file is served by plug static, but if not You can use a controller. That has the advantage to allow check access, and to store files wherever You want.
Something like…
def get_thumbnail(conn, %{"page_id" => id} = params) do
version =
case params["version"] do
nil ->
:original
version when version in ["large", "thumb", "mini"] ->
String.to_atom(version)
_ ->
:original
end
case Core.get_event_by_permalink(id) do
nil ->
conn
|> put_flash(:error, gettext("Event not found."))
|> redirect(to: root_path(conn))
%Event{} = event ->
local_path = ThumbnailsUploader.local_path(event, version)
check_and_send(conn, event, local_path, "image/png")
end
end
check_and_send is a custom function, a simpler example would be…
def get_animated_gif(conn, %{"id" => id}) do
event = Core.get_event!(id)
if event.animated_gif do
conn
|> put_resp_content_type("image/gif")
|> send_file(200, event.animated_gif)
else
send_resp(conn, 404, "")
end
end
As long as You know the path to the real file… and the content_type.
I use FileInfo and ExImageInfo to check file validity, and extract some metadata.
Using waffle allows transformation of data, like resizing, or any ffmpeg transformation, You just have to be careful about timeout (and increase the value if You do complex transformation).
You also need to adapt if You use liveview, and build a fake Plug.Upload when consuming entries. It works.
Waffle ecto allows to integrate this with Ecto, including custom validations. It works well…