# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.3.1] - 2026-08-14

Tracks the June–July 2026 Dropbox API spec updates that touch routes
Magpie already wraps.

### Added

- `Magpie.Files.ListFolder.list_folder/3` and `stream/3` now take an
  optional `opts` map merged into the request body, so callers can use
  the remaining `/files/list_folder` arguments — including the new
  `include_restorable_info` flag (each returned deleted entry then says
  whether it can be restored via `is_restorable`)
- `Magpie.Files.ListFolder.list_revisions/4` gained the same optional
  `opts` map (`"mode"`, `"before_rev"`, `"include_restorable_info"`)

### Changed

- `Magpie.Files.get_thumbnail_v2/3` docs no longer list `"quality"` as an
  option — Dropbox pulled the field from the public API surface in the
  July 2026 spec update

## [0.3.0] - 2026-08-10

Phoenix uploads. LiveView already owns the upload experience, so Magpie
does not ship a component — it fills the two gaps a Dropbox backend
creates: getting the bytes there without spooling them to disk, and
skipping the server altogether.

### Added

- `Magpie.LiveView.UploadWriter` — a `Phoenix.LiveView.UploadWriter` that
  streams a LiveView upload straight into a Dropbox upload session, so the
  bytes never land on the server's disk. Chunks are buffered to
  `:chunk_size` (default 8 MiB, Dropbox wants multiples of 4 MiB) and the
  tail rides along with the finish call. Magpie does not depend on
  `:phoenix_live_view` — the behaviour is a plain set of callbacks
- `Magpie.LiveView.presign_upload/4` — a LiveView `:external` uploader that
  mints a one-time link with `Magpie.Files.get_temporary_upload_link/3` so the
  browser posts the file straight to Dropbox, bypassing the server. Entries
  above Dropbox's 150 MB single-request limit are rejected at presign time
  instead of being handed a link that cannot work. The client-side half ships
  as `priv/static/magpie_uploader.js`

## [0.2.1] - 2026-08-05

### Added

- `Magpie.Auth.TokenServer` no longer requires a refresh token at startup.
  A server started without one sits in an *unconfigured* state — calls
  return a pattern-matchable
  `{:error, %Magpie.Error{summary: "no_refresh_token"}}` without touching
  the network — and the new `set_refresh_token/3` configures it (or
  replaces the token, for re-authorization) at any time, discarding any
  cached access token unless a valid `access_token`/`expires_at` pair is
  seeded. Fresh installs whose token arrives through the OAuth callback now
  work from a plain static supervision tree
- `Magpie.Auth.authorize_url/2` accepts `extra_params:` (keyword list or
  map) for additional Dropbox authorization params such as
  `force_reapprove`, `locale`, `require_role` and `disable_signup`.
  Params the function already sets cannot be overridden — collisions raise
  `ArgumentError`

## [0.2.0] - 2026-08-05

OAuth 2 support. Dropbox access tokens expire after ~4 hours, so a static
token is not enough for anything that runs unattended — Magpie now handles
the whole flow and keeps tokens fresh on its own.

### Added

- `Magpie.Auth` — OAuth 2 flow helpers: `authorize_url/2` (offline access
  by default), `pkce_pair/0` and `pkce_challenge/1` for public apps,
  `exchange_code/3` and `refresh/3`
- `Magpie.Auth.Token` — token struct with an absolute `expires_at` computed
  from Dropbox's `expires_in`
- `Magpie.Auth.TokenProvider` — behaviour that decouples the client from
  where tokens live, with two implementations: `Magpie.Auth.StaticToken`
  and `Magpie.Auth.TokenServer`
- `Magpie.Auth.TokenServer` — supervised token holder that refreshes
  proactively (configurable `:refresh_margin`, default 300s), serializes
  concurrent refreshes into a single request, survives failed refreshes and
  can persist tokens through an `:on_refresh` callback
- `Magpie.Client.new/1` accepts a refresh token
  (`refresh_token:`/`app_key:` + `app_secret:` or `pkce: true`, starting a
  linked `TokenServer`) or an explicit `token_provider: {module, arg}`
- Transparent recovery from expired tokens: requests rejected with HTTP 401
  `expired_access_token` are refreshed and replayed once. Streamed upload
  bodies cannot be replayed and are not retried — the proactive refresh
  covers them
- OAuth guide on HexDocs: getting a refresh token from the App Console, the
  web redirect flow, PKCE, supervision, persistence and custom providers

### Changed

- `Magpie.Client` gained a `token_provider` field; `access_token` is kept
  and `Magpie.Client.new("ACCESS_TOKEN")` behaves exactly as before
- `Magpie.Error.new/2` uses the OAuth `error` field (e.g. `"invalid_grant"`)
  as the error `summary` when there is no `error_summary`

## [0.1.0] - 2026-08-01

First release of Magpie 🐦 — a modern, actively maintained Elixir client for
the Dropbox API v2, born as a rewrite of the unmaintained
[elixir_dropbox](https://hex.pm/packages/elixir_dropbox) package (see the
Origin section of the README).

### Added

- Coverage of **all 132 current user-scoped routes** of the Dropbox API v2
  (`files`, `sharing`, `file_properties`, `file_requests`, `users`,
  `account`, `auth`, `check`, `contacts`, `openid`), verified against the
  official [dropbox-api-spec](https://github.com/dropbox/dropbox-api-spec)
- `Magpie.Files.upload_file/4` — smart upload: single request for small
  files, chunked upload session for large ones, streamed from disk
- `Magpie.Pager` — lazy `Stream`-based pagination, with ready-made wrappers
  (`Magpie.Files.ListFolder.stream/2`, `Magpie.Files.search_stream/3`,
  `Magpie.Sharing.list_folders_stream/2`, `Magpie.FileRequests.stream/2`)
- `Magpie.Async.await/4` — waits for asynchronous batch jobs by polling the
  check endpoint with exponential backoff
- `Magpie.Error` — normalized error struct (and exception) carrying the HTTP
  `status`, Dropbox's `error_summary` and the full error `body`
- Offline test suite built on `Req.Test` (~94% line coverage) and a
  `config :magpie, req_options: [...]` hook so consumer apps can stub
  Dropbox in their own tests
- Examples guide on HexDocs

### Changed

- HTTP client migrated from HTTPoison/Poison to [Req](https://hexdocs.pm/req)/Jason
- Every call now returns `{:ok, result}` or `{:error, %Magpie.Error{}}`
- Deprecated Dropbox endpoints migrated to their current versions:
  `move_v2`, `search_v2` (+ `search/continue_v2`), `copy_batch_v2`
  (+ `check_v2`), `upload_session/finish_batch_v2` and
  `create_shared_link_with_settings`
- Default endpoint URLs are built in — consumer configuration is optional

### Fixed

- `Magpie.Files.upload/6` sent the file as JSON instead of raw bytes,
  breaking every upload since the Req migration
- `Magpie.Users.get_account_to_struct/2` always returned an error even on
  successful responses
- Paper `docs/users/list/continue` pointed to a nonexistent URL

### Deprecated

- The legacy `/paper/docs/*` wrappers (`Magpie.Paper.*`) remain for
  compatibility, but the whole Paper API is deprecated by Dropbox — prefer
  `Magpie.Files.Paper`

[0.3.1]: https://github.com/alexcassol/magpie/releases/tag/v0.3.1
[0.3.0]: https://github.com/alexcassol/magpie/releases/tag/v0.3.0
[0.2.1]: https://github.com/alexcassol/magpie/releases/tag/v0.2.1
[0.2.0]: https://github.com/alexcassol/magpie/releases/tag/v0.2.0
[0.1.0]: https://github.com/alexcassol/magpie/releases/tag/v0.1.0
