# `Fil.Plugin.URL`
[🔗](https://github.com/pehbehbeh/fil/blob/v0.1.0/lib/fil/plugin/url.ex#L1)

Builds URLs for any disk, served by `Fil.Plug` from your application.

    disk =
      Fil.disk(adapter: Fil.Adapter.Local, root: "priv/storage/uploads")
      |> Fil.Plugin.URL.attach(base_url: "http://localhost:4000/storage/uploads", secret: secret)

    Fil.url(disk, "avatars/1.png")
    #=> {:ok, "http://localhost:4000/storage/uploads/avatars/1.png"}

    Fil.signed_url(disk, "avatars/1.png", method: :put)
    #=> {:ok, "http://localhost:4000/storage/uploads/avatars/1.png?expires=...&signature=..."}

The plugin answers `Fil.url/2` and, with a `:secret`, `Fil.signed_url/3` itself, so the adapter never runs. That's how
local and memory disks get URLs, since there is no storage service that could build them. A public URL only works
where `Fil.Plug` serves the disk with `public: true` (or something else serves the files at `:base_url`).

On an S3 disk, `:base_url` replaces the bucket URL in `Fil.url/2`, for a CDN in front of the bucket. Without a
`:secret`, S3 still presigns `Fil.signed_url/3` itself. With one, signed URLs go to your application, which then
passes the file through: useful when the bucket shouldn't be reachable from outside, but every download and upload
runs through your application, and files are read into memory whole.

A signed URL is `:base_url`, the path, and two query parameters: `expires` (Unix seconds) and `signature`, an
HMAC-SHA256 over the method, the URL path and the expiry. `expires_in:` is capped at 7 days, as on S3, so a URL that
works on one disk works on every disk.

## Options

* `:base_url` (`t:String.t/0`) - Required. The URL the disk root is served at, e.g. `"http://localhost:4000/storage/uploads"`: `Fil.Plug` in your
  application, or a CDN in front of the disk.

* `:secret` (`t:String.t/0`) - The key URLs are signed with. Use at least 32 random bytes (`mix phx.gen.secret` prints 64). `Fil.Plug`
  reads it from the disk, so it's only configured here. Without it, the plugin only builds public URLs, and
  `Fil.signed_url/3` goes to the adapter.

# `attach`

```elixir
@spec attach(
  Fil.Disk.t(),
  keyword()
) :: Fil.Disk.t()
```

Attaches the plugin to `disk` under the name `Fil.Plugin.URL`.

The same as `plugins: [{Fil.Plugin.URL, :call, opts}]` in `Fil.disk/1`, except that the options are validated here
instead of on the first URL.

