# `Storehouse.S3`
[🔗](https://github.com/hattoriai/storehouse/blob/v0.2.0/lib/storehouse/s3.ex#L1)

A `Storehouse.Adapter` for Amazon S3, and the stores that speak its API:
Cloudflare R2, MinIO, Tigris, Backblaze B2, DigitalOcean Spaces, Google
Cloud Storage's interoperability. With no dependency: it signs its
requests itself (AWS Signature Version 4).

    # config/runtime.exs
    config :storehouse, adapter: Storehouse.S3

    config :storehouse, Storehouse.S3,
      bucket: "my-app-uploads",
      region: "eu-west-1",
      access_key_id: System.fetch_env!("AWS_ACCESS_KEY_ID"),
      secret_access_key: System.fetch_env!("AWS_SECRET_ACCESS_KEY")

For another store, its `endpoint` (whose URLs name the bucket in the
path, `path_style`, unless told otherwise):

    config :storehouse, Storehouse.S3,
      bucket: "uploads",
      region: "auto",
      endpoint: "https://<account>.r2.cloudflarestorage.com",
      access_key_id: System.fetch_env!("R2_ACCESS_KEY_ID"),
      secret_access_key: System.fetch_env!("R2_SECRET_ACCESS_KEY"),
      public_url: "https://files.example.com"

## Options

  * `:bucket`, `:region`, `:access_key_id`, `:secret_access_key`
    (required), and `:session_token` for temporary credentials.
  * `:endpoint` - the store's URL. The default is AWS's,
    `https://s3.<region>.amazonaws.com`.
  * `:path_style` - whether URLs name the bucket in their path
    (`https://endpoint/bucket/key`) rather than their host
    (`https://bucket.endpoint/key`). The default is `false` for AWS,
    `true` with an `:endpoint`.
  * `:public_url` - the base URL of the files for `url/1`: a CDN, a
    custom domain. The default is the bucket's URL. A bucket that is not
    public serves its files at signed URLs (`Storehouse.signed_url/2`).
  * `:cache_control` - the `Cache-Control` the bucket serves each file
    with, kept with it when it is stored, uploaded from the browser,
    streamed in or copied in: for a CDN in front. Keys are never reused,
    so a file never changes: `"public, max-age=31536000, immutable"`.
    The default is none, the bucket's own. A call's `:cache_control`
    takes its place (`Storehouse.store/2`).
  * `:if_none_match` - whether an upload straight to the bucket is
    signed with `If-None-Match: *`, so the bucket takes it only where no
    file is: its URL cannot replace a file once it was checked. The
    default is `true`; AWS S3, R2 and MinIO take it. Set it to `false`
    for a store that refuses the header.

Each file is stored with its checked content type, and with
`Content-Disposition: attachment` for every type a browser should not
show (`Storehouse.Check.inline?/1`): the store never serves an
uploaded page as a page.

## Uploads straight to the bucket

`presign_upload/3` signs a `PUT` of one file, of its type and its size
exactly, which the browser sends to the bucket without going through
the app (`Storehouse.Live.allow/3`). The bucket must allow it with CORS:

    [{"AllowedOrigins": ["https://my-app.example.com"],
      "AllowedMethods": ["PUT"],
      "AllowedHeaders": ["content-type", "content-disposition", "if-none-match"],
      "MaxAgeSeconds": 3600}]

The URL writes only where no file is (`If-None-Match: *`): once the
file was checked (`Storehouse.verify_upload/2`), the same URL cannot
put other bytes in its place.

An upload presigned with its checksum (`:checksum`) is signed with
`x-amz-checksum-sha256` too, and the bucket refuses bytes that do not
have it: add it and `x-amz-sdk-checksum-algorithm` to `AllowedHeaders`.
A store with a `:cache_control` signs it too: add `cache-control`.

# `__using__`
*macro* 

Defines a store of its own, with a config of its own: a second bucket
beside the first (public and private files, media on R2, files kept in
another region).

    defmodule MyApp.PrivateFiles, do: use Storehouse.S3

    config :storehouse, MyApp.PrivateFiles, bucket: "acme-private", region: "eu-west-1", …

The module is a `Storehouse.Adapter`: give it as `adapter:` (or a field's
`store:`, `Storehouse.Ecto.File`). With `otp_app: :my_app`, its config is
`config :my_app, MyApp.PrivateFiles, …`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
