Where the bytes of an attachment live.
Coelho stores the document and the attachment metadata; the file itself is
somebody else's problem, and this is the smallest contract that lets it be
solved without writing the same glue in every application. A storage is a
struct whose module implements these callbacks, so an application can point
at the one it ships with — Coelho.Storage.Disk — or write its own for
object storage without anything else changing.
storage = Coelho.Storage.Disk.new("priv/uploads")
:ok = Coelho.Storage.put(storage, key, {:file, upload_path})
{:ok, path} = Coelho.Storage.path(storage, key)Keys come from Coelho.Attachment.generate_key/0. They are opaque and URL
safe, and a storage must treat them as untrusted: Coelho.Storage.Disk
refuses a key that is not what it hands out, so a key cannot walk out of
the directory it belongs to.
Summary
Callbacks
Removes the bytes. Removing what is not there is not an error.
Whether the storage holds anything under this key.
A local path for the bytes, when there is one.
Stores the bytes under a key, replacing whatever was there.
Reads the bytes back.
Somewhere the reader can fetch the bytes directly, when there is such a place.
Functions
Asks the storage for a URL to redirect to, or :error when it has none.
Types
Callbacks
Removes the bytes. Removing what is not there is not an error.
Whether the storage holds anything under this key.
A local path for the bytes, when there is one.
Lets a plug send the file rather than read it into memory. A remote
storage answers :error, and the caller falls back to read/2.
Stores the bytes under a key, replacing whatever was there.
Reads the bytes back.
Somewhere the reader can fetch the bytes directly, when there is such a place.
Object storage can hand out a URL of its own — presigned, short lived —
and answering with one is what stops every byte travelling through the
application. Coelho.Plug.Attachments redirects to it after checking its
own signature, so the check still happens and the transfer does not.
opts carries:
:expires_in— the seconds left on the signature that got the reader this far. A URL outliving it would widen the window the signature was there to narrow.:content_type— what the application recorded for this file. An implementation is expected to pin it, through whatever its service offers —response-content-typeon a presigned S3 URL, and the like. The plug's own defence against a file that lies about what it is lives in headers a redirect does not carry, so an implementation that passes this over hands that defence back to whatever the bucket decides.:filename— for a service that can pin a download name too.
Optional: a storage that has no such URL — the local filesystem — simply does not implement it.
Functions
Asks the storage for a URL to redirect to, or :error when it has none.
Answers :error for a storage that does not implement the callback, so
callers need not know which do.