lib/etcher/annotation.ex

defmodule Etcher.Annotation do
  @moduledoc """
  Ecto schema for the bundled `etcher_annotations` table.

  Used by `Etcher.Storage.Default`. Consumers who implement their own
  `Etcher.Storage` adapter against a custom table can ignore this
  schema entirely.

  ## Fields

    * `:uuid` — UUIDv7 primary key
    * `:target_type` — string identifying the kind of resource the
      annotation is on (e.g. `"file"`, `"document"`, `"product"`)
    * `:target_uuid` — UUID of the resource
    * `:creator_uuid` — UUID of the user who drew it (nullable)
    * `:kind` — `"rectangle"` | `"circle"` | `"polygon"` | `"freehand"`
    * `:geometry` — JSONB; shape-specific coordinates in image pixels
        * rectangle: `%{"x" => x, "y" => y, "w" => w, "h" => h}`
        * circle: `%{"cx" => cx, "cy" => cy, "r" => r}`
        * polygon: `%{"points" => [[x1, y1], [x2, y2], ...]}`
        * freehand: `%{"points" => [[x1, y1], [x2, y2], ...]}`
    * `:style` — JSONB; optional stroke color / line width
    * `:metadata` — JSONB; consumer-defined extension point (link to
      external systems, tags, etc.)
    * `:position` — integer; ordering on the same target
    * `:inserted_at` / `:updated_at` — timestamps
  """
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:uuid, Ecto.UUID, autogenerate: true}
  @foreign_key_type Ecto.UUID

  @kinds ~w(rectangle circle polygon freehand)

  schema "etcher_annotations" do
    field(:target_type, :string)
    field(:target_uuid, Ecto.UUID)
    field(:creator_uuid, Ecto.UUID)
    field(:kind, :string)
    field(:geometry, :map)
    field(:style, :map)
    field(:metadata, :map)
    field(:position, :integer, default: 0)

    timestamps(type: :utc_datetime)
  end

  @cast_fields ~w(target_type target_uuid creator_uuid kind geometry style metadata position)a
  @required_fields ~w(target_type target_uuid kind geometry)a

  @doc false
  def changeset(annotation, attrs) do
    annotation
    |> cast(attrs, @cast_fields)
    |> validate_required(@required_fields)
    |> validate_inclusion(:kind, @kinds)
    |> validate_length(:target_type, min: 1, max: 64)
  end
end