Waffle

repository·master·Indexed 21 days ago

https://github.com/elixir-waffle/waffle

A flexible file upload library for Elixir providing integrations for Amazon S3 and local storage. It supports image transformations via ImageMagick, definition modules for handling upload logic (transformations, storage directories, and filename generation), and optional Ecto integration via the waffle_ecto package.

Tokens
11.1K
Snippets
38
Records
45
Agent score
70%

What's inside Waffle

  1. Configure AWS S3 for integration testing

    master

    To run tests that include S3 integration, follow these steps:

    1. Create a new AWS user with FullS3Access permissions.
    2. Retrieve the key_id and secret for this user.
    3. Create a new S3 bucket with public access enabled.
    4. In test/test_helper.exs, uncomment the s3 exclusion line.
    5. Update your .env file with the new AWS credentials and bucket information.

    After configuration, run the tests using mix test.

    $ mix test
  2. Configure Waffle for S3 storage

    master

    To use Amazon S3 as your storage provider, configure the :waffle application with the Waffle.Storage.S3 module. You must specify a bucket and an asset_host. You can use literal strings or use {:system, "ENV_VAR_NAME"} to pull values from environment variables. Additionally, ensure ex_aws is configured (e.g., setting the json_codec).

    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
  3. Run common development tasks

    master

    The following commands are used for standard development workflows including linting, documentation generation, and publishing to Hex.

    # Run the linter
    $ mix credo --strict
    
    # Generate documentation
    $ MIX_ENV=dev mix docs
    
    # Publish the package to Hex
    $ MIX_ENV=dev mix hex.publish
    
    # Publish only the documentation to Hex
    $ MIX_ENV=dev mix hex.publish docs
  4. Install Waffle

    master

    Add waffle to your mix.exs dependencies. If you plan to use Amazon S3 for storage, you must also include the ExAws dependencies.

    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
  5. Define a Waffle Definition Module

    master

    A Waffle definition module is created by using the Waffle.Definition module. This module defines how files are validated, transformed (e.g., resizing images), named, and where they are stored.

    Key callbacks to implement:

    • @versions: A list of atoms representing the different versions of the file (e.g., [:original, :thumb]).
    • @extensions: A list of allowed file extensions.
    • validate/1: Validates the file (e.g., checking extensions). Returns :ok or {:error, reason}.
    • transform/2: Defines image processing instructions for a specific version. Returns {:convert, command, extension}.
    • filename/2: Determines the filename for a specific version.
    • storage_dir/2: Determines the directory path where the file should be stored. This function can accept additional context (like a user record) passed during the store call.
    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
  6. Configure Waffle for Local Storage

    master

    To use local file storage, configure the :waffle application in your configuration files. You must specify the storage provider as Waffle.Storage.Local and provide an asset_host which is the base URL used for generating file URLs. The asset_host can be a hardcoded string or a system environment variable using the {:system, "VAR_NAME"} syntax.

    config :waffle,
      storage: Waffle.Storage.Local,
      asset_host: "http://static.example.com" # or {:system, "ASSET_HOST"}
  7. Create a definition module

    master

    A definition module is a required component in Waffle that contains the logic for handling specific types of uploads. It defines:

    • Transformations (e.g., resizing images)
    • Storage directory
    • Filename generation
    • Security settings (public vs private)
    • Default placeholders

    You can create one manually or use the Waffle generator CLI to scaffold a module.

    # Generate a definition module for 'avatar'
    mix waffle.g avatar
  8. Set up the local development environment

    master

    To set up the project for local development, use Docker to manage the environment. First, copy the example environment file to .env, then start the Docker containers. Once running, enter the container and fetch the project dependencies using mix deps.get.

    # Copy environment file and start containers
    $ cp example.env .env
    $ docker-compose up
    
    # Enter the container and get dependencies
    $ docker-compose exec waffle sh
    $ > mix deps.get
  9. Configure the HTTP client

    master

    Waffle uses :hackney by default to download remote files. You can explicitly set the HTTP client in your configuration, or implement your own by adopting the Waffle.HTTPClient behaviour.

    config :waffle, :http_client, Waffle.HTTPClient.Hackney
  10. Configure a storage provider

    master

    Waffle requires a storage provider to be configured in your application configuration.

    Local Storage

    Use Waffle.Storage.Local for storing files on the local filesystem.

    S3 Storage

    Use Waffle.Storage.S3 for Amazon S3. This requires ex_aws configuration as well.

    # Local Storage Example
    config :waffle,
      storage: Waffle.Storage.Local,
      asset_host: "http://static.example.com" # or {:system, "ASSET_HOST"}
    
    # S3 Storage Example
    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
  11. Implement file transformations in Waffle

    master

    To transform uploaded files, you must define a transform/2 function in your Waffle definition module. This function accepts a version atom and a tuple containing the file and its scope: {file, scope}.

    The transform/2 function must return one of the following types to control how the file is processed:

    • :noaction - The original file is stored as-is.
    • :skip - No file is stored for this version.
    • {executable, args} - Runs System.cmd with the format: #{original_file_path} #{args} #{transformed_file_path}.
    • {executable, fn(input, output) -> args end} - Uses a function to generate arguments. The function can return a string or a list of arguments.
    • {executable, args, output_extension} - Used when the transformation changes the file extension (e.g., converting to .png). You must explicitly provide the new extension.
    • fn version, file -> {:ok, file} end - A custom transformation implemented entirely as an Elixir function.
    • {&transform/2, fn version, file -> :png end} - A custom transformation that maps a version to a specific file extension.
    defmodule Avatar do
      use Waffle.Definition
    
      @versions [:original, :thumb]
    
      def transform(:thumb, _) do
        {:convert, "-strip -thumbnail 100x100^ -gravity center -extent 100x100"}
      end
    end