### Setup Project with Docker Source: https://github.com/elixir-waffle/waffle/blob/master/documentation/development.md Use these commands to set up the project environment using Docker. This involves copying the environment file and starting the Docker Compose services. ```sh $ cp example.env .env $ docker-compose up ``` ```sh $ docker-compose exec waffle sh $ > mix deps.get ``` -------------------------------- ### Define an Uploader with Waffle.Definition Source: https://context7.com/elixir-waffle/waffle/llms.txt Example of defining an uploader module using `use Waffle.Definition`, including versions, validation, transformations, storage directory, filename, and default URLs. ```elixir defmodule MyApp.Avatar do use Waffle.Definition # Two versions will be stored: original and a resized thumb @versions [:original, :thumb] # Validate by extension (simpler) or magic bytes (more secure) @extension_allowlist ~w(.jpg .jpeg .gif .png .webp) def validate({file, _scope}) do ext = file.file_name |> Path.extname() |> String.downcase() case Enum.member?(@extension_allowlist, ext) do true -> :ok false -> {:error, "invalid file type: #{ext}"} end end # Transform :thumb version using ImageMagick's convert def transform(:thumb, _) do {:convert, "-strip -thumbnail 100x100^ -gravity center -extent 100x100 -format png", :png} end # Store files in a per-user directory def storage_dir(_version, {_file, user}) do "uploads/avatars/#{user.id}" end # Name versions by their version atom (original.png, thumb.png) def filename(version, _), do: version # Fallback URL when no file is present def default_url(:thumb, _scope), do: "https://placehold.it/100x100" def default_url(_version, _scope), do: nil end ``` -------------------------------- ### Store and Retrieve Files Source: https://github.com/elixir-waffle/waffle/blob/master/documentation/examples/local.md Examples of storing files using the defined Avatar module, both from a file path and directly from Plug.Upload. Also shows how to retrieve the URL for a stored file. ```elixir # Given some current_user record current_user = %{id: 1} # Store any accessible file Avatar.store({"/path/to/my/selfie.png", current_user}) #=> {:ok, "selfie.png"} # ..or store directly from the `params` of a file upload within your controller Avatar.store({%Plug.Upload{}, current_user}) #=> {:ok, "selfie.png"} # and retrieve the url later Avatar.url({"selfie.png", current_user}, :thumb) #=> "uploads/avatars/1/thumb.png" ``` -------------------------------- ### Custom Storage Backend Implementation in Elixir Source: https://context7.com/elixir-waffle/waffle/llms.txt Implement the `Waffle.StorageBehavior` to create custom storage adapters like Google Cloud Storage. This example shows the basic structure for `put`, `url`, and `delete` operations. ```elixir defmodule MyApp.GCSStorage do @behaviour Waffle.StorageBehavior @impl true def put(definition, version, {file, scope}) do bucket = definition.bucket() key = Path.join(definition.storage_dir(version, {file, scope}), file.file_name) # Upload using your GCS client ... case GCSClient.upload(bucket, key, file.path) do :ok -> {:ok, file.file_name} {:error, reason} -> {:error, reason} end end @impl true def url(definition, version, file_and_scope, _options) do key = Path.join(definition.storage_dir(version, file_and_scope), "file.ext") "https://storage.googleapis.com/#{definition.bucket()}/#{key}" end @impl true def delete(definition, version, file_and_scope) do key = Path.join(definition.storage_dir(version, file_and_scope), "file.ext") GCSClient.delete(definition.bucket(), key) :ok end end # Point a definition module to the custom backend defmodule MyApp.DocumentUploader do use Waffle.Definition def __storage, do: MyApp.GCSStorage end ``` -------------------------------- ### Async Processing and Timeout Configuration in Elixir Source: https://context7.com/elixir-waffle/waffle/llms.txt Configure global Waffle settings for version processing timeout and temporary directory, or disable async processing per definition module. This example shows how to increase the timeout and set a custom temp directory globally, and disable async processing in a specific uploader. ```elixir # config/config.exs — increase version processing timeout globally config :waffle, version_timeout: 30_000, # 30 seconds (default: 15_000 ms) tmp_dir: "/mnt/fast-ssd/tmp" # custom temp directory for processing # In a definition module — disable async processing entirely defmodule MyApp.SyncUploader do use Waffle.Definition @async false # versions processed sequentially @versions [:original, :thumb, :large] def transform(:thumb, _), do: {:convert, "-resize 100x100", :png} def transform(:large, _), do: {:convert, "-resize 1200x1200>", :jpg} def transform(:original, _), do: :noaction end ``` -------------------------------- ### Publish Documentation Only Source: https://github.com/elixir-waffle/waffle/blob/master/documentation/development.md Publish only the project documentation to Hex.pm. Set MIX_ENV to 'dev' and use the `mix hex.publish docs` task. ```sh $ MIX_ENV=dev mix hex.publish docs ``` -------------------------------- ### Run Basic Waffle Tests Source: https://github.com/elixir-waffle/waffle/blob/master/README.md Execute the basic test suite for Waffle using `mix test`. This does not require any S3 configuration. ```bash mix test ``` -------------------------------- ### Generate Documentation Source: https://github.com/elixir-waffle/waffle/blob/master/documentation/development.md Generate project documentation using the `mix docs` task. Ensure the environment variable MIX_ENV is set to 'dev'. ```sh $ MIX_ENV=dev mix docs ``` -------------------------------- ### Local Storage Configuration Source: https://context7.com/elixir-waffle/waffle/llms.txt Configure Waffle for local filesystem storage and serve files statically with Plug.Static in Phoenix. ```elixir # config/config.exs config :waffle, storage: Waffle.Storage.Local, storage_dir_prefix: "priv/waffle/public", # physical path prefix (not in URL) asset_host: "http://static.example.com" # or {:system, "ASSET_HOST"} # lib/my_app_web/endpoint.ex — serve uploaded files statically defmodule MyAppWeb.Endpoint do use Phoenix.Endpoint, otp_app: :my_app plug Plug.Static, at: "/uploads", from: Path.expand("./priv/waffle/public/uploads"), gzip: false end ``` -------------------------------- ### Publish Package Source: https://github.com/elixir-waffle/waffle/blob/master/documentation/development.md Publish the package to Hex.pm. This requires setting MIX_ENV to 'dev' and using the `mix hex.publish` task. ```sh $ MIX_ENV=dev mix hex.publish ``` -------------------------------- ### Run Linter Source: https://github.com/elixir-waffle/waffle/blob/master/documentation/development.md Execute the Credo linter to check code quality and style. Use the --strict flag for a more rigorous check. ```sh $ mix credo --strict ``` -------------------------------- ### Run Waffle Tests with S3 Source: https://github.com/elixir-waffle/waffle/blob/master/README.md To test S3 capabilities, ensure your S3 bucket is configured for ACLs and public access. Set the required environment variables and run `mix test --include s3:true`. ```bash mix test --include s3:true ``` -------------------------------- ### Generate a Waffle Definition Module Source: https://context7.com/elixir-waffle/waffle/llms.txt Use the `mix waffle.g` task to scaffold a new uploader definition module. ```bash # In a Phoenix project — creates lib/my_app_web/uploaders/avatar.ex mix waffle.g avatar # In a non-Phoenix project — path must be provided explicitly mix waffle.g avatar uploaders/avatar.ex ``` -------------------------------- ### Store Files with Avatar.store/1 Source: https://context7.com/elixir-waffle/waffle/llms.txt Accepts files in multiple formats and optionally a scope. Returns {:ok, filename} or {:error, reason}. Ensure the file is in a supported format like a local path, URL, Plug.Upload, binary map, or stream map. ```elixir user = Repo.get!(User, 42) # Store a local file path {:ok, name} = MyApp.Avatar.store("/tmp/photo.jpg") # => {:ok, "photo.jpg"} # Store a remote file (downloaded automatically with retry + content-disposition support) {:ok, name} = MyApp.Avatar.store("https://example.com/profile.png") # => {:ok, "profile.png"} ``` ```elixir def create(conn, %{"avatar" => upload}) do user = conn.assigns.current_user case MyApp.Avatar.store({upload, user}) do {:ok, filename} -> User.changeset(user, %{avatar: filename}) |> Repo.update!() send_resp(conn, 200, "uploaded") {:error, reason} -> send_resp(conn, 422, "upload failed: #{inspect(reason)}") end end ``` ```elixir # Store from raw binary (e.g., from Plug.Conn.read_body/1) {:ok, data, _conn} = Plug.Conn.read_body(conn) {:ok, name} = MyApp.Avatar.store(%{filename: "photo.png", binary: data}, user) ``` ```elixir # Store from a stream stream = File.stream!("/path/to/large_video.mp4") {:ok, name} = MyApp.Avatar.store(%{filename: "video.mp4", stream: stream}, user) ``` -------------------------------- ### Add Waffle to Mix Dependencies Source: https://github.com/elixir-waffle/waffle/blob/master/README.md Add Waffle and its required dependencies for ExAws to your `mix.exs` file. Run `mix deps.get` to fetch them. ```elixir defp deps do [ {:waffle, "~> 1.1"}, # If using S3: {:ex_aws, "~> 2.1.2"}, {:ex_aws_s3, "~> 2.0"}, {:hackney, "~> 1.9"}, {:sweet_xml, "~> 0.6"} ] end ``` -------------------------------- ### Store and Retrieve Files with Waffle S3 Source: https://github.com/elixir-waffle/waffle/blob/master/documentation/examples/s3.md Demonstrates how to store files using the defined Waffle module and retrieve their URLs. Supports storing from file paths or Plug.Upload structs. ```elixir # Given some current_user record current_user = %{id: 1} # Store any accessible file Avatar.store({"/path/to/my/selfie.png", current_user}) #=> {:ok, "selfie.png"} # ..or store directly from the `params` of a file upload within your controller Avatar.store({%Plug.Upload{}, current_user}) #=> {:ok, "selfie.png"} # and retrieve the url later Avatar.url({"selfie.png", current_user}, :thumb) #=> "https://s3.amazonaws.com/custom_bucket/uploads/avatars/1/thumb.png" ``` -------------------------------- ### Configure Waffle for S3 Storage Source: https://github.com/elixir-waffle/waffle/blob/master/documentation/examples/s3.md Set up Waffle to use S3 as the storage backend. Ensure you provide your S3 bucket name and optionally an asset host. Also configure ex_aws. ```elixir config :waffle, storage: Waffle.Storage.S3, bucket: "custom_bucket", # or {:system, "AWS_S3_BUCKET"} asset_host: "http://static.example.com" # or {:system, "ASSET_HOST"} config :ex_aws, json_codec: Jason # any configurations provided by https://github.com/ex-aws/ex_aws ``` -------------------------------- ### Generate Avatar Definition Module Source: https://github.com/elixir-waffle/waffle/blob/master/README.md Use the `mix waffle.g` task to generate a definition module for handling avatars. This command creates a file in `lib/[APP_NAME]_web/uploaders/avatar.ex`. ```bash mix waffle.g avatar ``` -------------------------------- ### Define a Waffle Definition Module for Avatars Source: https://github.com/elixir-waffle/waffle/blob/master/documentation/examples/s3.md Create a module that defines how Waffle should handle avatar files, including versioning, validation, transformations, and storage. ```elixir defmodule Avatar do use Waffle.Definition @versions [:original, :thumb] @extension_whitelist ~w(.jpg .jpeg .gif .png) def acl(:thumb, _), do: :public_read def validate({file, _}) do file_extension = file.file_name |> Path.extname |> String.downcase Enum.member?(@extension_whitelist, file_extension) end def transform(:thumb, _) do {:convert, "-thumbnail 100x100^ -gravity center -extent 100x100 -format png", :png} end def filename(version, _) do version end def storage_dir(_, {file, user}) do "uploads/avatars/#{user.id}" end def default_url(:thumb) do "https://placehold.it/100x100" end end ``` -------------------------------- ### Run Tests with S3 Integration Source: https://github.com/elixir-waffle/waffle/blob/master/documentation/development.md Execute tests that integrate with AWS S3. Ensure S3 is configured correctly, including user permissions, bucket settings, and environment variables. ```sh $ mix test ``` -------------------------------- ### Add Waffle to Mix Dependencies Source: https://context7.com/elixir-waffle/waffle/llms.txt Add Waffle and optional S3 dependencies to your project's mix.exs file. ```elixir defp deps do [ {:waffle, "~> 1.1"}, # Required only if using S3: {:ex_aws, "~> 2.1"}, {:ex_aws_s3, "~> 2.1"}, {:hackney, "~> 1.9"}, {:sweet_xml, "~> 0.6"}, # Optional Ecto integration: {:waffle_ecto, "~> 0.0"} ] end ``` -------------------------------- ### Generate URLs with Avatar.url/2,3 and Avatar.urls/1 Source: https://context7.com/elixir-waffle/waffle/llms.txt Generates public or signed URLs for stored files. `url/2` for default, `url/3` for signed S3 URLs with expiry, and `urls/1` for all versions. Handles nil files by returning a default URL. ```elixir user = %{id: 42} # URL for the default (first) version MyApp.Avatar.url({"photo.png", user}) # => "https://my-bucket.s3.amazonaws.com/uploads/avatars/42/original.png" # URL for a specific version MyApp.Avatar.url({"photo.png", user}, :thumb) # => "https://my-bucket.s3.amazonaws.com/uploads/avatars/42/thumb.png" # Signed URL with custom expiry (S3 only) MyApp.Avatar.url({"photo.png", user}, :original, signed: true, expires_in: 3600) # => "https://my-bucket.s3.amazonaws.com/uploads/avatars/42/original.png?AWSAccessKeyId=...&Expires=..." # All version URLs at once MyApp.Avatar.urls({"photo.png", user}) # => %{original: "https://.../original.png", thumb: "https://.../thumb.png"} # Nil file returns default_url MyApp.Avatar.url({nil, user}, :thumb) # => "https://placehold.it/100x100" ``` -------------------------------- ### Avatar.url/2,3 and Avatar.urls/1 Source: https://context7.com/elixir-waffle/waffle/llms.txt Generates public URLs for stored file versions. `url/2` provides the default URL, `url/3` supports signed URLs with expiry for S3, and `urls/1` returns a map of all version URLs. ```APIDOC ## `Avatar.url/2,3` and `Avatar.urls/1` — Generating URLs `url/2` generates a public URL for a stored file version. `url/3` supports signed S3 URLs with expiry. `urls/1` returns a map of all version URLs at once. ```elixir user = %{id: 42} # URL for the default (first) version MyApp.Avatar.url({"photo.png", user}) # => "https://my-bucket.s3.amazonaws.com/uploads/avatars/42/original.png" # URL for a specific version MyApp.Avatar.url({"photo.png", user}, :thumb) # => "https://my-bucket.s3.amazonaws.com/uploads/avatars/42/thumb.png" # Signed URL with custom expiry (S3 only) MyApp.Avatar.url({"photo.png", user}, :original, signed: true, expires_in: 3600) # => "https://my-bucket.s3.amazonaws.com/uploads/avatars/42/original.png?AWSAccessKeyId=...&Expires=..." # All version URLs at once MyApp.Avatar.urls({"photo.png", user}) # => %{original: "https://.../original.png", thumb: "https://.../thumb.png"} # Nil file returns default_url MyApp.Avatar.url({nil, user}, :thumb) # => "https://placehold.it/100x100" ``` ``` -------------------------------- ### Configure Local Storage Source: https://github.com/elixir-waffle/waffle/blob/master/documentation/examples/local.md Configure Waffle to use the local storage provider and set the asset host. This is typically done in your application's configuration. ```elixir config :waffle, storage: Waffle.Storage.Local, asset_host: "http://static.example.com" # or {:system, "ASSET_HOST"} ``` -------------------------------- ### Avatar.store/1 Source: https://context7.com/elixir-waffle/waffle/llms.txt Stores a file from various sources like local paths, remote URLs, Plug.Upload, binary maps, or streams. It can optionally accept a scope tuple for organizing files. Returns {:ok, filename} on success or {:error, reason} on failure. ```APIDOC ## `Avatar.store/1` — Storing Files `store/1` accepts a file in several formats (local path, remote URL, `%Plug.Upload{}`, binary map, stream map) and optionally a scope tuple. Returns `{:ok, filename}` or `{:error, reason}`. ```elixir user = Repo.get!(User, 42) # Store a local file path {:ok, name} = MyApp.Avatar.store("/tmp/photo.jpg") # => {:ok, "photo.jpg"} # Store a remote file (downloaded automatically with retry + content-disposition support) {:ok, name} = MyApp.Avatar.store("https://example.com/profile.png") # => {:ok, "profile.png"} # Store from a Plug.Upload (controller params) def create(conn, %{"avatar" => upload}) do user = conn.assigns.current_user case MyApp.Avatar.store({upload, user}) do {:ok, filename} -> User.changeset(user, %{avatar: filename}) |> Repo.update!() send_resp(conn, 200, "uploaded") {:error, reason} -> send_resp(conn, 422, "upload failed: #{inspect(reason)}") end end # Store from raw binary (e.g., from Plug.Conn.read_body/1) {:ok, data, _conn} = Plug.Conn.read_body(conn) {:ok, name} = MyApp.Avatar.store(%{filename: "photo.png", binary: data}, user) # Store from a stream stream = File.stream!("/path/to/large_video.mp4") {:ok, name} = MyApp.Avatar.store(%{filename: "video.mp4", stream: stream}, user) ``` ``` -------------------------------- ### S3 Storage Configuration Source: https://context7.com/elixir-waffle/waffle/llms.txt Configure Waffle to use Amazon S3 storage with ExAws, using credentials from environment variables or application config. ```elixir # config/config.exs config :waffle, storage: Waffle.Storage.S3, bucket: {:system, "AWS_S3_BUCKET"}, # runtime env var asset_host: {:system, "ASSET_HOST"}, # optional CDN host virtual_host: true # use bucket.s3.amazonaws.com format config :ex_aws, json_codec: Jason, access_key_id: [{:system, "AWS_ACCESS_KEY_ID"}, :instance_role], secret_access_key: [{:system, "AWS_SECRET_ACCESS_KEY"}, :instance_role], region: "eu-central-1", s3: [ scheme: "https://", host: "s3.eu-central-1.amazonaws.com", region: "eu-central-1" ] ``` -------------------------------- ### Define File Transformations with transform/2 Source: https://context7.com/elixir-waffle/waffle/llms.txt Defines how file versions are processed using ImageMagick, FFmpeg, or custom Elixir functions. Can skip transformations or use external tools. Ensure system executables are available. ```elixir defmodule MyApp.MediaUploader do use Waffle.Definition @versions [:original, :thumb, :preview, :html_doc] # No transformation — store original as-is def transform(:original, _), do: :noaction # ImageMagick: resize + convert to PNG def transform(:thumb, _) do {:convert, "-strip -thumbnail 100x100^ -gravity center -extent 100x100 -format png", :png} end # FFmpeg: extract first frame of video as JPEG def transform(:preview, {file, _}) do {:ffmpeg, fn input, output -> "-i #{input} -vframes 1 -f image2 #{output}" end, :jpg} end # Custom Elixir function transformation (e.g., using ExOptimizer) def transform(:optimized, _), do: &optimize_image/2 defp optimize_image(_version, original_file) do tmp_path = Waffle.File.generate_temporary_path(original_file) File.cp!(original_file.path, tmp_path) case ExOptimizer.optimize(tmp_path) do {:ok, _} -> {:ok, %Waffle.File{original_file | path: tmp_path, is_tempfile?: true}} {:error, reason} -> {:error, reason} end end # Skip storing a version conditionally def transform(:html_doc, {%{file_name: name}, _}) do if Path.extname(name) == ".docx", do: {:soffice_wrapper, fn i, o -> [i, o] end, :html}, else: :skip end end ``` -------------------------------- ### Define Asset Definition Module Source: https://github.com/elixir-waffle/waffle/blob/master/documentation/examples/local.md Define a module that inherits from Waffle.Definition to specify validation rules, transformations, filenames, and storage directories for your assets. ```elixir defmodule Avatar do use Waffle.Definition @versions [:original, :thumb] @extensions ~w(.jpg .jpeg .gif .png) def validate({file, _}) do file_extension = file.file_name |> Path.extname |> String.downcase case Enum.member?(@extensions, file_extension) do true -> :ok false -> {:error, "file type is invalid"} end end def transform(:thumb, _) do {:convert, "-thumbnail 100x100^ -gravity center -extent 100x100 -format png", :png} end def filename(version, _) do version end def storage_dir(_, {file, user}) do "uploads/avatars/#{user.id}" end end ``` -------------------------------- ### transform/2 Callback Source: https://context7.com/elixir-waffle/waffle/llms.txt Defines how file versions are processed using ImageMagick, FFmpeg, or custom Elixir functions. This callback is invoked within a definition module to control transformations. ```APIDOC ## `transform/2` — File Transformations (ImageMagick, FFmpeg, Custom) The `transform/2` callback in a definition module controls how each version is processed. It can invoke any system executable, or an arbitrary Elixir function. ```elixir defmodule MyApp.MediaUploader do use Waffle.Definition @versions [:original, :thumb, :preview, :html_doc] # No transformation — store original as-is def transform(:original, _), do: :noaction # ImageMagick: resize + convert to PNG def transform(:thumb, _) do {:convert, "-strip -thumbnail 100x100^ -gravity center -extent 100x100 -format png", :png} end # FFmpeg: extract first frame of video as JPEG def transform(:preview, {file, _}) do {:ffmpeg, fn input, output -> "-i #{input} -vframes 1 -f image2 #{output}" end, :jpg} end # Custom Elixir function transformation (e.g., using ExOptimizer) def transform(:optimized, _), do: &optimize_image/2 defp optimize_image(_version, original_file) do tmp_path = Waffle.File.generate_temporary_path(original_file) File.cp!(original_file.path, tmp_path) case ExOptimizer.optimize(tmp_path) do {:ok, _} -> {:ok, %Waffle.File{original_file | path: tmp_path, is_tempfile?: true}} {:error, reason} -> {:error, reason} end end # Skip storing a version conditionally def transform(:html_doc, {%{file_name: name}, _}) do if Path.extname(name) == ".docx", do: {:soffice_wrapper, fn i, o -> [i, o] end, :html}, else: :skip end end ``` ``` -------------------------------- ### S3 ACL and Object Headers Configuration in Elixir Source: https://context7.com/elixir-waffle/waffle/llms.txt Configure per-version ACL permissions and custom S3 headers like cache control and encryption using `acl/2` and `s3_object_headers/2` overrides. Also shows how to set a different bucket per scope and configure remote headers for authenticated downloads. ```elixir defmodule MyApp.Avatar do use Waffle.Definition @versions [:original, :thumb] # Make only the thumb publicly readable; original remains private (default) def acl(:thumb, _), do: :public_read def acl(:original, _), do: :private # Set content-type, cache headers, and server-side encryption def s3_object_headers(version, {file, _scope}) do [ content_type: MIME.from_path(file.file_name), cache_control: "public, max-age=31536000", encryption: "AES256" ] end # Use a different bucket per scope def bucket({_file, scope}), do: scope.bucket || Application.fetch_env!(:waffle, :bucket) # Remote headers for authenticated file downloads def remote_file_headers(%URI{host: "private.example.com"}) do token = Base.encode64("user:secret") [{"Authorization", "Basic #{token}"}] end def remote_file_headers(_), do: [] end ``` -------------------------------- ### Magic-byte File Validation in Elixir Source: https://context7.com/elixir-waffle/waffle/llms.txt Implement magic-byte validation for secure file type checking. Requires the `magic_bytes` hex package. Handles both direct file paths and streaming uploads. ```elixir defmodule MyApp.SecureUploader do use Waffle.Definition @allowed_types ~w(image/jpeg image/png image/gif image/webp) # Magic-byte validation (requires magic_bytes hex package) def validate({%{path: path}, _scope}) when not is_nil(path) do case MagicBytes.from_path(path) do {:ok, mime} when mime in @allowed_types -> :ok {:ok, other_mime} -> {:error, "file type #{other_mime} not allowed"} {:error, :unknown} -> {:error, "unrecognized file type"} {:error, reason} -> {:error, "could not read file: #{inspect(reason))}"} end end # Stream-based validation for streaming uploads def validate({%{stream: stream}, _scope}) when not is_nil(stream) do case MagicBytes.from_stream(stream) do {:ok, mime} when mime in @allowed_types -> :ok _ -> {:error, "invalid file type"} end end end ``` -------------------------------- ### Delete Files with Avatar.delete/1 Source: https://context7.com/elixir-waffle/waffle/llms.txt Removes all versions of a stored file from the storage backend. Supports deletion with or without a scope for path resolution. Useful for cleaning up files when deleting records. ```elixir user = Repo.get!(User, 42) # Delete without scope :ok = MyApp.Avatar.delete("photo.png") # Delete with scope (resolves per-user storage_dir) :ok = MyApp.Avatar.delete({"photo.png", user}) ``` ```elixir # Typical usage when removing a user record def delete_user(user_id) do user = Repo.get!(User, user_id) :ok = MyApp.Avatar.delete({user.avatar, user}) Repo.delete!(user) end ``` -------------------------------- ### Avatar.delete/1 Source: https://context7.com/elixir-waffle/waffle/llms.txt Removes all versions of a stored file from the configured storage backend. It respects the scope-based path resolution used during upload. Accepts a filename or a tuple of {filename, scope}. ```APIDOC ## `Avatar.delete/1` — Deleting Files `delete/1` removes all versions of a stored file from the configured storage backend, respecting the same scope-based path resolution used at upload time. ```elixir user = Repo.get!(User, 42) # Delete without scope :ok = MyApp.Avatar.delete("photo.png") # Delete with scope (resolves per-user storage_dir) :ok = MyApp.Avatar.delete({"photo.png", user}) # Typical usage when removing a user record def delete_user(user_id) do user = Repo.get!(User, user_id) :ok = MyApp.Avatar.delete({user.avatar, user}) Repo.delete!(user) end ``` ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.