# ex_dav
> ⚠️ **Beta / heavy WIP.** APIs are unstable and may change between
> releases. Not yet recommended for production use.
CalDAV server library for Elixir. Mounts as a `Plug` under any host
application. Ships with a Postgres-backed reference adapter and an
in-memory adapter; bring your own for arbitrary backends.
```elixir
plug ExDav.CalDav.Plug,
storage: ExDav.Storage.Postgres,
authenticator: {ExDav.Authenticator.Basic,
verify: {ExDav.Storage.Postgres, :authenticate}}
```
## What you get
- `ExDav.CalDav.Plug` — handles `PROPFIND`, `REPORT`, `MKCALENDAR`,
`PROPPATCH`, `PUT`, `GET`, `DELETE` under `/dav/...` plus the
RFC 6764 `.well-known/caldav` redirect. Other paths pass through
untouched.
- `ExDav.Storage` — behaviour for storage adapters. Adapters return
plain maps so the protocol layer stays ORM-agnostic.
- `ExDav.Storage.Postgres` — reference adapter using Ecto. Owns its
own users / calendars / objects / tombstones tables; reads its
`Ecto.Repo` from `Application.fetch_env!(:ex_dav, :repo)`.
- `ExDav.Storage.Memory` — in-memory adapter (Agent-backed) for dev
demos and tests.
- `ExDav.Authenticator` — behaviour for HTTP authenticators.
`ExDav.Authenticator.Basic` ships HTTP Basic with a configurable
`:verify` MFA tuple.
## Usage in a Phoenix app
```elixir
# config/config.exs
config :ex_dav, repo: MyApp.Repo
# lib/my_app_web/endpoint.ex
plug ExDav.CalDav.Plug,
storage: ExDav.Storage.Postgres,
authenticator: {ExDav.Authenticator.Basic,
verify: {ExDav.Storage.Postgres, :authenticate}}
```
Run `ExDav.Storage.Postgres`'s migrations into your repo (copy from
`apps/ex_dav/priv/repo/migrations` — these will move into the library
itself in a future release).
## Custom storage adapter
```elixir
defmodule MyApp.CalDavStorage do
@behaviour ExDav.Storage
@impl true
def list_calendars(username) do
# return [%{name:, displayname:, description:, components:, ctag:, objects:}]
end
@impl true
def put_object(username, cal_name, obj_name, ical), do: ...
# ...all 11 callbacks
end
```
Then mount:
```elixir
plug ExDav.CalDav.Plug,
storage: MyApp.CalDavStorage,
authenticator: {MyApp.CalDavAuth, []}
```
## Custom authenticator
```elixir
defmodule MyApp.CalDavAuth do
@behaviour ExDav.Authenticator
@impl true
def authenticate(conn, _opts) do
case extract_token(conn) do
{:ok, principal} -> {:ok, conn, principal}
:error -> :unauth
end
end
end
```
## Standalone
For a runnable reference deployment, see `apps/ex_dav_server` in this
repository — a minimal Phoenix app that wires the library to Postgres
and ships an admin LiveView for managing CalDAV principals.