{"id":13757604,"url":"https://github.com/stavro/arc","last_synced_at":"2025-04-09T02:16:07.036Z","repository":{"id":33269984,"uuid":"36914552","full_name":"stavro/arc","owner":"stavro","description":":paperclip: Flexible file upload and attachment library for Elixir","archived":false,"fork":false,"pushed_at":"2024-07-12T12:41:24.000Z","size":266,"stargazers_count":1167,"open_issues_count":83,"forks_count":210,"subscribers_count":26,"default_branch":"master","last_synced_at":"2025-04-02T01:14:01.635Z","etag":null,"topics":["attachment","elixir","s3","s3-storage","storage"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"roller-project/roller","license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stavro.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-06-05T05:52:31.000Z","updated_at":"2025-02-28T06:06:34.000Z","dependencies_parsed_at":"2024-06-18T11:21:33.544Z","dependency_job_id":"c6de92b4-4429-42f8-9c91-6f90d6aaffd3","html_url":"https://github.com/stavro/arc","commit_stats":{"total_commits":154,"total_committers":58,"mean_commits":"2.6551724137931036","dds":0.6168831168831168,"last_synced_commit":"b090340a7a42da680d73d7aa031aa0bc3e1bd132"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stavro%2Farc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stavro%2Farc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stavro%2Farc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stavro%2Farc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stavro","download_url":"https://codeload.github.com/stavro/arc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247962604,"owners_count":21024871,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["attachment","elixir","s3","s3-storage","storage"],"created_at":"2024-08-03T12:00:41.825Z","updated_at":"2025-04-09T02:16:07.021Z","avatar_url":"https://github.com/stavro.png","language":"Elixir","readme":"## This Project is Deprecated\n\n**NOTE**: This project is being deprecated in favor of\n[Waffle](https://github.com/elixir-waffle/waffle). The new project is based off\nthis project meaning all of the same configuration and APIs will continue to\nwork as expected (at the time of writing this).\n\n### Migrating to Waffle\n\n#### Update your dependency\n\nIf you are using Mix, simply change your dependency from `arc` to `waffle`.\nWaffle uses the same version tags, so there is no need to modify the version\nin your mix file.\n\nBecause Waffle is still Arc under the hood, any Arc-based storage providers,\nsuch as `arc_gcs`, will continue to work without any additional changes.\n\n#### Update your configs\n\nReplace any instances of `config :arc` to `config :waffle`. All configuration\noptions remain the same (as of version `0.11.0`) and do not need to be updated.\n\n#### Update your code\n\nChange any instances of `Arc` to `Waffle` **except** where it concerns the\nthird-party storage providers. For example, `Arc.Storage.GCS` would continue\nto use `Arc` as the root namespace because that's defined outside the core\nlibrary. If you wish to standardize the libraries, you can use an alias in your\nfiles to prepare for an eventual namespace conversion, e.g.\n\n```elixir\n# \"Renames\" the Arc namespace to Waffle\nalias Arc, as: Waffle\n\n# Use the new library name as your normally would\nWaffle.Storage.GCS\n```\n\nArc\n===\n\nArc is a flexible file upload library for Elixir with straightforward integrations for Amazon S3 and ImageMagick.\n\nBrowse the readme below, or jump to [a full example](#full-example).\n\n## Content\n\n- [Installation](#installation)\n  - [Configuration](#configuration)\n  - [Storage Providers](#storage-providers)\n  - [Usage with Ecto](#usage-with-ecto)\n- [Getting Started](#getting-started-defining-your-upload)\n  - [Basics](#basics)\n  - [Transformations](#transformations)\n    - [ImageMagick Transformations](#imagemagick-transformations)\n    - [FFmpeg Transformations](#ffmpeg-transformations)\n    - [Complex Transformations](#complex-transformations)\n  - [Asynchronous File Uploading](#asynchronous-file-uploading)\n  - [Storage of Files](#storage-of-files)\n    - [Local Configuration](#local-configuration)\n    - [S3 Configuration](#s3-configuration)\n    - [Storage Directory](#storage-directory)\n    - [Specify multiple buckets](#specify-multiple-buckets)\n    - [Specify multiple asset hosts](#specify-multiple-asset-hosts)\n    - [Access Control Permissions](#access-control-permissions)\n    - [S3 Object Headers](#s3-object-headers)\n    - [File Validation](#file-validation)\n    - [File Names](#file-names)\n  - [Object Deletion](#object-deletion)\n  - [URL Generation](#url-generation)\n    - [Alternate S3 configuration example](#alternate-s3-configuration-example)\n- [Full example](#full-example)\n\n## Installation\n\nAdd the latest stable release to your `mix.exs` file, along with the required dependencies for `ExAws` if appropriate:\n\n```elixir\ndefp deps do\n  [\n    arc: \"~\u003e 0.11.0\",\n\n    # If using Amazon S3:\n    ex_aws: \"~\u003e 2.0\",\n    ex_aws_s3: \"~\u003e 2.0\",\n    hackney: \"~\u003e 1.6\",\n    poison: \"~\u003e 3.1\",\n    sweet_xml: \"~\u003e 0.6\"\n  ]\nend\n```\n\nThen run `mix deps.get` in your shell to fetch the dependencies.\n\n### Configuration\n\nArc expects certain properties to be configured at the application level:\n\n```elixir\nconfig :arc,\n  storage: Arc.Storage.S3, # or Arc.Storage.Local\n  bucket: {:system, \"AWS_S3_BUCKET\"} # if using Amazon S3\n```\n\nAlong with any configuration necessary for ExAws.\n\n### Storage Providers\nArc ships with integrations for Local Storage and S3.  Alternative storage providers may be supported by the community:\n\n* **Rackspace** - https://github.com/lokalebasen/arc_rackspace\n* **Manta** - https://github.com/onyxrev/arc_manta\n* **OVH** - https://github.com/stephenmoloney/arc_ovh\n* **Google Cloud Storage** - https://github.com/martide/arc_gcs\n* **Microsoft Azure Storage** - https://github.com/phil-a/arc_azure\n\n### Usage with Ecto\n\nArc comes with a companion package for use with Ecto.  If you intend to use Arc with Ecto, it is highly recommended you also add the [`arc_ecto`](https://github.com/stavro/arc_ecto) dependency.  Benefits include:\n\n  * Changeset integration\n  * Versioned urls for cache busting (`.../thumb.png?v=63601457477`)\n\n# Getting Started: Defining your Upload\n\nArc requires a **definition module** which contains the relevant configuration to store and retrieve your files.\n\nThis definition module contains relevant functions to determine:\n  * Optional transformations of the uploaded file\n  * Where to put your files (the storage directory)\n  * What to name your files\n  * How to secure your files (private? Or publicly accessible?)\n  * Default placeholders\n\nTo start off, generate an attachment definition:\n\n```bash\nmix arc.g avatar\n```\n\nThis should give you a basic file in:\n\n```\nweb/uploaders/avatar.ex\n```\n\nCheck this file for descriptions of configurable options.\n\n## Basics\n\nThere are two supported use-cases of Arc currently:\n\n  1. As a general file store, or\n  2. As an attachment to another model (the attached model is referred to as a `scope`)\n\nThe upload definition file responds to `Avatar.store/1` which accepts either:\n\n  * A path to a local file\n  * A path to a remote `http` or `https` file\n  * A map with a filename and path keys (eg, a `%Plug.Upload{}`)\n  * A map with a filename and binary keys (eg, `%{filename: \"image.png\", binary: \u003c\u003c255,255,255,...\u003e\u003e}`)\n  * A two-tuple consisting of one of the above file formats as well as a scope object.\n\nExample usage as general file store:\n\n```elixir\n# Store any locally accessible file\nAvatar.store(\"/path/to/my/file.png\") #=\u003e {:ok, \"file.png\"}\n\n# Store any remotely accessible file\nAvatar.store(\"http://example.com/file.png\") #=\u003e {:ok, \"file.png\"}\n\n# Store a file directly from a `%Plug.Upload{}`\nAvatar.store(%Plug.Upload{filename: \"file.png\", path: \"/a/b/c\"}) #=\u003e {:ok, \"file.png\"}\n\n# Store a file from a connection body\n{:ok, data, _conn} = Plug.Conn.read_body(conn)\nAvatar.store(%{filename: \"file.png\", binary: data})\n```\n\nExample usage as a file attached to a `scope`:\n\n```elixir\nscope = Repo.get(User, 1)\nAvatar.store({%Plug.Upload{}, scope}) #=\u003e {:ok, \"file.png\"}\n```\n\nThis scope will be available throughout the definition module to be used as an input to the storage parameters (eg, store files in `/uploads/#{scope.id}`).\n\n## Transformations\n\nArc can be used to facilitate transformations of uploaded files via any system executable.  Some common operations you may want to take on uploaded files include resizing an uploaded avatar with ImageMagick or extracting a still image from a video with FFmpeg.\n\nTo transform an image, the definition module must define a `transform/2` function which accepts a version atom and a tuple consisting of the uploaded file and corresponding scope.\n\nThis transform handler accepts the version atom, as well as the file/scope argument, and is responsible for returning one of the following:\n  * `:noaction` - The original file will be stored as-is.\n  * `:skip` - Nothing will be stored for the provided version.\n  * `{executable, args}` - The `executable` will be called with `System.cmd` with the format `#{original_file_path} #{args} #{transformed_file_path}`.\n  * `{executable, fn(input, output) -\u003e args end}` - If your executable expects arguments in a format other than the above, you may supply a function to the conversion tuple which will be invoked to generate the arguments. The arguments can be returned as a string (e.g. – `\" #{input} -strip -thumbnail 10x10 #{output}\"`) or a list (e.g. – `[input, \"-strip\", \"-thumbnail\", \"10x10\", output]`) for even more control.\n  * `{executable, args, output_extension}` - If your transformation changes the file extension (eg, converting to `png`), then the new file extension must be explicit.\n\n### ImageMagick transformations\n\nAs images are one of the most commonly uploaded filetypes, Arc has a recommended integration with ImageMagick's `convert` tool for manipulation of images.  Each upload definition may specify as many versions as desired, along with the corresponding transformation for each version.\n\nThe expected return value of a `transform` function call must either be `:noaction`, in which case the original file will be stored as-is, `:skip`, in which case nothing will be stored, or `{:convert, transformation}` in which the original file will be processed via ImageMagick's `convert` tool with the corresponding transformation parameters.\n\nThe following example stores the original file, as well as a squared 100x100 thumbnail version which is stripped of comments (eg, GPS coordinates):\n\n```elixir\ndefmodule Avatar do\n  use Arc.Definition\n\n  @versions [:original, :thumb]\n\n  def transform(:thumb, _) do\n    {:convert, \"-strip -thumbnail 100x100^ -gravity center -extent 100x100\"}\n  end\nend\n```\n\nOther examples:\n\n```elixir\n# Change the file extension through ImageMagick's `format` parameter:\n{:convert, \"-strip -thumbnail 100x100^ -gravity center -extent 100x100 -format png\", :png}\n\n# Take the first frame of a gif and process it into a square jpg:\n{:convert, fn(input, output) -\u003e \"#{input}[0] -strip -thumbnail 100x100^ -gravity center -extent 100x100 -format jpg #{output}\", :jpg}\n```\n\nFor more information on defining your transformation, please consult [ImageMagick's convert documentation](http://www.imagemagick.org/script/convert.php).\n\n\u003e **Note**: Keep this transformation function simple and deterministic based on the version, file name, and scope object. The `transform` function is subsequently called during URL generation, and the transformation is scanned for the output file format.  As such, if you conditionally format the image as a `png` or `jpg` depending on the time of day, you will be displeased with the result of Arc's URL generation.\n\n\u003e **System Resources**: If you are accepting arbitrary uploads on a public site, it may be prudent to add system resource limits to prevent overloading your system resources from malicious or nefarious files.  Since all processing is done directly in ImageMagick, you may pass in system resource restrictions through the [-limit](http://www.imagemagick.org/script/command-line-options.php#limit) flag.  One such example might be: `-limit area 10MB -limit disk 100MB`.\n\n### FFmpeg transformations\n\nCommon transformations of uploaded videos can be also defined through your definition module:\n\n```elixir\n# To take a thumbnail from a video:\n{:ffmpeg, fn(input, output) -\u003e \"-i #{input} -f jpg #{output}\" end, :jpg}\n\n# To convert a video to an animated gif\n{:ffmpeg, fn(input, output) -\u003e \"-i #{input} -f gif #{output}\" end, :gif}\n```\n\n### Complex Transformations\n`Arc` requires the output of your transformation to be located at a predetermined path.  However, the transformation may be done completely outside of `Arc`. For fine-grained transformations, you should create an executable wrapper in your $PATH (eg. bash script) which takes these proper arguments, runs your transformation, and then moves the file into the correct location.\n\nFor example, to use `soffice` to convert a doc to an html file, you should place the following bash script in your $PATH:\n\n```bash\n#!/usr/bin/env sh\n\n# `soffice` doesn't allow for output file path option, and arc can't find the\n# temporary file to process and copy. This script has a similar argument list as\n# what arc expects. See https://github.com/stavro/arc/issues/77.\n\nset -e\nset -o pipefail\n\nfunction convert {\n    soffice \\\n        --headless \\\n        --convert-to html \\\n        --outdir $TMPDIR \\\n        \"$1\"\n}\n\nfunction filter_new_file_name {\n    awk -F$TMPDIR '{print $2}' \\\n    | awk -F\" \" '{print $1}' \\\n    | awk -F/ '{print $2}'\n}\n\nconverted_file_name=$(convert \"$1\" | filter_new_file_name)\n\ncp $TMPDIR/$converted_file_name \"$2\"\nrm $TMPDIR/$converted_file_name\n```\n\nAnd perform the transformation as such:\n\n\n```elixir\ndef transform(:html, _) do\n  {:soffice_wrapper, fn(input, output) -\u003e [input, output] end, :html}\nend\n```\n\n## Asynchronous File Uploading\n\nIf you specify multiple versions in your definition module, each version is processed and stored concurrently as independent Tasks.  To prevent an overconsumption of system resources, each Task is given a specified timeout to wait, after which the process will fail.  By default this is `15 seconds`.\n\nIf you wish to change the time allocated to version transformation and storage, you may add a configuration parameter:\n\n```elixir\nconfig :arc,\n  :version_timeout, 15_000 # milliseconds\n```\n\nTo disable asynchronous processing, add `@async false` to your upload definition.\n\n## Storage of files\n\nArc currently supports Amazon S3 and local destinations for file uploads.\n\n### Local Configuration\n\nTo store your attachments locally, override the `__storage` function in your definition module to `Arc.Storage.Local`. You may wish to optionally override the storage directory as well, as outlined below.\n\n```elixir\ndefmodule Avatar do\n  use Arc.Definition\n  def __storage, do: Arc.Storage.Local # Add this\nend\n```\n\n### S3 Configuration\n\n[ExAws](https://github.com/CargoSense/ex_aws) is used to support Amazon S3.\n\nTo store your attachments in Amazon S3, you'll need to provide a bucket destination in your application config:\n\n```elixir\nconfig :arc,\n  bucket: \"uploads\"\n```\n\nYou may also set the bucket from an environment variable:\n\n```elixir\nconfig :arc,\n  bucket: {:system, \"S3_BUCKET\"}\n```\n\nIn addition, ExAws must be configured with the appropriate Amazon S3 credentials.\n\nExAws has by default the following configuration (which you may override if you wish):\n\n```elixir\nconfig :ex_aws,\n  access_key_id: [{:system, \"AWS_ACCESS_KEY_ID\"}, :instance_role],\n  secret_access_key: [{:system, \"AWS_SECRET_ACCESS_KEY\"}, :instance_role]\n```\n\nThis means it will first look for the AWS standard AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables, and fall back using instance meta-data if those don't exist. You should set those environment variables to your credentials, or configure an instance that this library runs on to have an iam role.\n\n### Storage Directory\n\n**Configuration Option**\n\n* `arc[:storage_dir]` - The storage directory to place files. Defaults to `uploads`, but can be overwritten via configuration options `:storage_dir`\n\n```elixir\nconfig :arc,\n  storage_dir: \"my/dir\"\n```\n\nThe storage dir can also be overwritten on an individual basis, in each separate definition. A common pattern for user profile pictures is to store each user's uploaded images in a separate subdirectory based on their primary key:\n\n```elixir\ndef storage_dir(version, {file, scope}) do\n  \"uploads/users/avatars/#{scope.id}\"\nend\n```\n\n\n\u003e **Note**: If you are \"attaching\" a file to a record on creation (eg, while inserting the record at the same time), then you cannot use the model's `id` as a path component.  You must either (1) use a different storage path format, such as UUIDs, or (2) attach and update the model after an id has been given.\n\n\u003e **Note**: The storage directory is used for both local filestorage (as the relative or absolute directory), and S3 storage, as the path name (not including the bucket).\n\n### Specify multiple buckets\n\nArc lets you specify a bucket on a per definition basis. In case you want to use\nmultiple buckets, you can specify a bucket in the uploader definition file\nlike this:\n\n```elixir\ndef bucket, do: :some_custom_bucket_name\n```\n\n### Specify multiple asset hosts\n\nArc lets you specify an asset host on a per definition basis. In case you want to use\nmultiple hosts, you can specify an asset_host in the uploader definition file\nlike this:\n\n```elixir\ndef asset_host, do: \"https://example.com\"\n```\n\n### Access Control Permissions\n\nArc defaults all uploads to `private`.  In cases where it is desired to have your uploads public, you may set the ACL at the module level (which applies to all versions):\n\n```elixir\n@acl :public_read\n```\n\nOr you may have more granular control over each version.  As an example, you may wish to explicitly only make public a thumbnail version of the file:\n\n```elixir\ndef acl(:thumb, _), do: :public_read\n```\n\nSupported access control lists for Amazon S3 are:\n\n| ACL                          | Permissions Added to ACL                                                                                                                |\n|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|\n| `:private`                   | Owner gets `FULL_CONTROL`. No one else has access rights (default).                                                                     |\n| `:public_read`               | Owner gets `FULL_CONTROL`. The `AllUsers` group gets READ access.                                                                       |\n| `:public_read_write`         | Owner gets `FULL_CONTROL`. The `AllUsers` group gets `READ` and `WRITE` access. Granting this on a bucket is generally not recommended. |\n| `:authenticated_read`        | Owner gets `FULL_CONTROL`. The `AuthenticatedUsers` group gets `READ` access.                                                           |\n| `:bucket_owner_read`         | Object owner gets `FULL_CONTROL`. Bucket owner gets `READ` access.                                                                      |\n| `:bucket_owner_full_control` | Both the object owner and the bucket owner get `FULL_CONTROL` over the object.                                                          |\n\nFor more information on the behavior of each of these, please consult Amazon's documentation for [Access Control List (ACL) Overview](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html).\n\n### S3 Object Headers\n\nThe definition module may specify custom headers to pass through to S3 during object creation.  The available custom headers include:\n  *  :cache_control\n  *  :content_disposition\n  *  :content_encoding\n  *  :content_length\n  *  :content_type\n  *  :expect\n  *  :expires\n  *  :storage_class\n  *  :website_redirect_location\n  *  :encryption (set to \"AES256\" for encryption at rest)\n\n\nAs an example, to explicitly specify the content-type of an object, you may define a `s3_object_headers/2` function in your definition, which returns a Keyword list, or Map of desired headers.\n\n```elixir\ndef s3_object_headers(version, {file, scope}) do\n  [content_type: MIME.from_path(file.file_name)] # for \"image.png\", would produce: \"image/png\"\nend\n```\n\n### File Validation\n\nWhile storing files on S3 (rather than your harddrive) eliminates some malicious attack vectors, it is strongly encouraged to validate the extensions of uploaded files as well.\n\nArc delegates validation to a `validate/1` function with a tuple of the file and scope.  As an example, to validate that an uploaded file conforms to popular image formats, you may use:\n\n```elixir\ndefmodule Avatar do\n  use Arc.Definition\n  @extension_whitelist ~w(.jpg .jpeg .gif .png)\n\n  def validate({file, _}) do\n    file_extension = file.file_name |\u003e Path.extname() |\u003e String.downcase()\n    Enum.member?(@extension_whitelist, file_extension)\n  end\nend\n```\n\nAny uploaded file failing validation will return `{:error, :invalid_file}` when passed through to `Avatar.store`.\n\n### File Names\n\nIt may be undesirable to retain original filenames (eg, it may contain personally identifiable information, vulgarity, vulnerabilities with Unicode characters, etc).\n\nYou may specify the destination filename for uploaded versions through your definition module.\n\nA common pattern is to combine directories scoped to a particular model's primary key, along with static filenames. (eg: `user_avatars/1/thumb.png`)\n\nExamples:\n\n```elixir\n# To retain the original filename, but prefix the version and user id:\ndef filename(version, {file, scope}) do\n  file_name = Path.basename(file.file_name, Path.extname(file.file_name))\n  \"#{scope.id}_#{version}_#{file_name}\"\nend\n\n# To make the destination file the same as the version:\ndef filename(version, _), do: version\n```\n\n## Object Deletion\n\nAfter an object is stored through Arc, you may optionally remove it.  To remove a stored object, pass the same path identifier and scope from which you stored the object.\n\nExample:\n\n```elixir\n# Without a scope:\n{:ok, original_filename} = Avatar.store(\"/Images/me.png\")\n:ok = Avatar.delete(original_filename)\n\n# With a scope:\nuser = Repo.get! User, 1\n{:ok, original_filename} = Avatar.store({\"/Images/me.png\", user})\n:ok = Avatar.delete({original_filename, user})\n# or\nuser = Repo.get!(User, 1)\n{:ok, original_filename} = Avatar.store({\"/Images/me.png\", user})\nuser = Repo.get!(User, 1)\n:ok = Avatar.delete({user.avatar, user})\n```\n\n## Url Generation\n\nSaving your files is only the first half of any decent storage solution.  Straightforward access to your uploaded files is equally as important as storing them in the first place.\n\nOften times you will want to regain access to the stored files.  As such, `Arc` facilitates the generation of urls.\n\n```elixir\n# Given some user record\nuser = %{id: 1}\n\nAvatar.store({%Plug.Upload{}, user}) #=\u003e {:ok, \"selfie.png\"}\n\n# To generate a regular, unsigned url (defaults to the first version):\nAvatar.url({\"selfie.png\", user}) #=\u003e \"https://bucket.s3.amazonaws.com/uploads/1/original.png\"\n\n# To specify the version of the upload:\nAvatar.url({\"selfie.png\", user}, :thumb) #=\u003e \"https://bucket.s3.amazonaws.com/uploads/1/thumb.png\"\n\n# To generate a signed url:\nAvatar.url({\"selfie.png\", user}, :thumb, signed: true) #=\u003e \"https://bucket.s3.amazonaws.com/uploads/1/thumb.png?AWSAccessKeyId=AKAAIPDF14AAX7XQ\u0026Signature=5PzIbSgD1V2vPLj%2B4WLRSFQ5M%3D\u0026Expires=1434395458\"\n\n# To generate urls for all versions:\nAvatar.urls({\"selfie.png\", user}) #=\u003e %{original: \"https://.../original.png\", thumb: \"https://.../thumb.png\"}\n```\n\n**Default url**\n\nIn cases where a placeholder image is desired when an uploaded file is not present, Arc allows the definition of a default image to be returned gracefully when requested with a `nil` file.\n\n```elixir\ndef default_url(version) do\n  MyApp.Endpoint.url \u003c\u003e \"/images/placeholders/profile_image.png\"\nend\n\nAvatar.url(nil) #=\u003e \"http://example.com/images/placeholders/profile_image.png\"\nAvatar.url({nil, scope}) #=\u003e \"http://example.com/images/placeholders/profile_image.png\"\n```\n\n**Virtual Host**\n\nTo support AWS regions other than US Standard, it may be required to generate urls in the [`virtual_host`](http://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html) style.  This will generate urls in the style: `https://#{bucket}.s3.amazonaws.com` instead of `https://s3.amazonaws.com/#{bucket}`.\n\nTo use this style of url generation, your bucket name must be DNS compliant.\n\nThis can be enabled with:\n\n```elixir\nconfig :arc,\n  virtual_host: true\n```\n\n\u003e When using virtual hosted–style buckets with SSL, the SSL wild card certificate only matches buckets that do not contain periods. To work around this, use HTTP or write your own certificate verification logic.\n\n\n**Asset Host**\n\nYou may optionally specify an asset host rather than using the default `bucket.s3.amazonaws.com` format.\n\nIn your application configuration, you'll need to provide an `asset_host` value:\n\n```elixir\nconfig :arc,\n  asset_host: \"https://d3gav2egqolk5.cloudfront.net\", # For a value known during compilation\n  asset_host: {:system, \"ASSET_HOST\"} # For a value not known until runtime\n```\n\n### Alternate S3 configuration example\nIf you are using a region other than US-Standard, it is necessary to specify the correct configuration for `ex_aws`.  A full example configuration for both arc and ex_aws is as follows:\n\n```\nconfig :arc,\n  bucket: \"my-frankfurt-bucket\"\n\nconfig :ex_aws,\n  access_key_id: \"my_access_key_id\",\n  secret_access_key: \"my_secret_access_key\",\n  region: \"eu-central-1\",\n  s3: [\n    scheme: \"https://\",\n    host: \"s3.eu-central-1.amazonaws.com\",\n    region: \"eu-central-1\"\n  ]\n```\n\n\u003e For your host configuration, please examine the approved [AWS Hostnames](http://docs.aws.amazon.com/general/latest/gr/rande.html).  There are often multiple hostname formats for AWS regions, and it will not work unless you specify the correct one.\n\n\n# Full Example\n\n```elixir\ndefmodule Avatar do\n  use Arc.Definition\n\n  @versions [:original, :thumb]\n  @extension_whitelist ~w(.jpg .jpeg .gif .png)\n\n  def acl(:thumb, _), do: :public_read\n\n  def validate({file, _}) do\n    file_extension = file.file_name |\u003e Path.extname |\u003e String.downcase\n    Enum.member?(@extension_whitelist, file_extension)\n  end\n\n  def transform(:thumb, _) do\n    {:convert, \"-thumbnail 100x100^ -gravity center -extent 100x100 -format png\", :png}\n  end\n\n  def filename(version, _) do\n    version\n  end\n\n  def storage_dir(_, {file, user}) do\n    \"uploads/avatars/#{user.id}\"\n  end\n\n  def default_url(:thumb) do\n    \"https://placehold.it/100x100\"\n  end\nend\n\n# Given some current_user record\ncurrent_user = %{id: 1}\n\n# Store any accessible file\nAvatar.store({\"/path/to/my/selfie.png\", current_user}) #=\u003e {:ok, \"selfie.png\"}\n\n# ..or store directly from the `params` of a file upload within your controller\nAvatar.store({%Plug.Upload{}, current_user}) #=\u003e {:ok, \"selfie.png\"}\n\n# and retrieve the url later\nAvatar.url({\"selfie.png\", current_user}, :thumb) #=\u003e \"https://s3.amazonaws.com/bucket/uploads/avatars/1/thumb.png\"\n```\n\n## Roadmap\n\nContributions are welcome.  Here is my current roadmap:\n\n  * Ease migration for version (or acl) changes\n  * Alternative storage destinations (eg, Filesystem)\n  * Solidify public API\n\n## Contribution\n\nOpen source contributions are welcome.  All pull requests must have corresponding unit tests.\n\nTo execute all tests locally, make sure the following system environment variables are set prior to running tests (if you wish to test `s3_test.exs`)\n\n  * `ARC_TEST_BUCKET`\n  * `ARC_TEST_S3_KEY`\n  * `ARC_TEST_S3_SECRET`\n\nThen execute `mix test`.\n\n## License\n\nCopyright 2015 Sean Stavropoulos\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n      http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n","funding_links":[],"categories":["Files and Directories"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstavro%2Farc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstavro%2Farc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstavro%2Farc/lists"}