# `Kotoba.Storage`
[🔗](https://github.com/hattoriai/kotoba/blob/v0.8.0/lib/kotoba/storage.ex#L2)

The store of attachments, kept with [Storehouse](https://hexdocs.pm/storehouse).

`Kotoba.Live.consume_uploads/4` and `Kotoba.Attachments.store/2` check
each file and store it with `Storehouse.store/2`, through the store of
`adapter/0`, and put the URL that it gives in the attachment node. Name
the store in Storehouse's config:

    config :storehouse, adapter: Storehouse.S3

The default is `Storehouse.Local`, the local disk, served by
`Storehouse.Local.Plug`. Storehouse's [Stores](https://hexdocs.pm/storehouse/stores.html)
guide has S3 (and R2, MinIO, LocalStack), and a store of your own.

## Keys

A key is a relative path with segments of ASCII letters, digits, `.`, `-`
and `_`, separated by `/`. `Storehouse.key/2` makes a new key in the form
`yyyy/mm/<uuid>-<safe name><extension>`; the extension comes from the
checked content type, never from the client's file name, so a key never
ends in `.html` or `.svg`.

## The store of Kotoba 0.7 and before (deprecated)

A store of the app's written for `Kotoba.Storage` (`put/3`, `url/1`,
`delete/1`) is a `Storehouse.Adapter` as it is: the callbacks are the
same, so it changes only its `@behaviour`. Until Kotoba 1.0,
`config :kotoba, storage: MyApp.KotobaStorage` still names it, and
`Kotoba.Storage.Local` (`config :kotoba, Kotoba.Storage.Local`) still
works, each with a deprecation warning: move to
`config :storehouse, adapter:` and `Storehouse.Local` (see the
CHANGELOG).

Storehouse is an optional dependency of Kotoba: an app with uploads has
`{:storehouse, "~> 0.2"}` in its deps, and this module is there only
with it.

# `key`

```elixir
@type key() :: Storehouse.Adapter.key()
```

A storage key, for example `"2026/09/0b6…-cat.png"`.

# `meta`

```elixir
@type meta() :: %{
  optional(:name) =&gt; String.t(),
  optional(:content_type) =&gt; String.t(),
  optional(:bytes) =&gt; non_neg_integer()
}
```

What Kotoba knows about the file: `:name` (the client's file name, for
display only, with control and format characters removed),
`:content_type` (checked on the server, see `Kotoba.Attachments`) and
`:bytes`.

# `delete`

```elixir
@callback delete(key()) :: :ok | {:error, term()}
```

Deletes the file with `key`. A key with no file gives `:ok`.

# `put`

```elixir
@callback put(key(), path :: Path.t(), meta()) ::
  {:ok, url :: String.t()} | {:error, term()}
```

Stores the file at `path` under `key`, and returns its URL.

The file at `path` is a temporary file: the adapter copies it, and must
not keep a reference to it.

# `url`

```elixir
@callback url(key()) :: String.t()
```

Returns the URL of the file with `key`.

# `adapter`

```elixir
@spec adapter() :: module()
```

Returns the store of attachments: `config :kotoba, storage:` when the
app names one (deprecated), `Kotoba.Storage.Local` when the app
configures it (deprecated), else Storehouse's (`Storehouse.adapter/0`).

---

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