Current section

Files

Jump to
etcher lib etcher storage.ex
Raw

lib/etcher/storage.ex

defmodule Etcher.Storage do
@moduledoc """
Behaviour for Etcher annotation persistence.
Etcher's `<Etcher.layer>` component doesn't run any persistence itself
— it fires `etcher:created` / `:updated` / `:deleted` events to the
consumer's LiveView. Consumers persist via either:
* the bundled `Etcher.Storage.Default` (writes to the
`etcher_annotations` table — generated by `mix etcher.gen.migration`)
* their own implementation of this behaviour
## Why a behaviour
Etcher is generic; downstream apps often have richer needs than the
default schema can express. PhoenixKit, for example, links each
annotation to a `phoenix_kit_comments` discussion thread, so its
adapter opens a transaction that creates both rows. Other consumers
might fan out to multi-tenant tables, log to an audit trail, etc.
Etcher itself doesn't depend on any specific Repo — the bundled
`Etcher.Storage.Default` reads `config :etcher, repo: …` at runtime.
## Example custom adapter
defmodule MyApp.AnnotationStorage do
@behaviour Etcher.Storage
alias MyApp.Repo
alias MyApp.Annotation
def create(attrs) do
%Annotation{}
|> Annotation.changeset(attrs)
|> Repo.insert()
end
def list_for(target_type, target_uuid) do
Repo.all(
from a in Annotation,
where: a.target_type == ^target_type and a.target_uuid == ^target_uuid
)
end
def update(uuid, attrs) do
Repo.get(Annotation, uuid)
|> Annotation.changeset(attrs)
|> Repo.update()
end
def delete(uuid) do
case Repo.get(Annotation, uuid) do
nil -> {:error, :not_found}
row -> Repo.delete(row)
end
end
end
"""
@typedoc "An annotation as a plain map (or whatever your adapter returns)."
@type annotation :: map() | struct()
@doc """
Persist a new annotation. Returns `{:ok, annotation}` or `{:error, reason}`.
`attrs` is the payload emitted by the `etcher:created` event, with
string keys (`"target_type"`, `"target_uuid"`, `"kind"`, `"geometry"`,
`"creator_uuid"`, optionally `"style"` and `"metadata"`).
"""
@callback create(attrs :: map()) :: {:ok, annotation()} | {:error, term()}
@doc """
Return all annotations for a given target. Plain list; order by
`position` ascending if your schema supports it.
"""
@callback list_for(target_type :: String.t(), target_uuid :: String.t()) :: [annotation()]
@doc """
Update an annotation by uuid. Same `attrs` shape as `create/1` but
typically with just `:geometry` (after the user drags a shape) or
`:style`.
"""
@callback update(uuid :: String.t(), attrs :: map()) ::
{:ok, annotation()} | {:error, term()}
@doc """
Delete an annotation by uuid. Returns `:ok` or `{:error, reason}`.
"""
@callback delete(uuid :: String.t()) :: :ok | {:error, term()}
end