lib/supabase/storage/bucket.ex

defmodule Supabase.Storage.Bucket do
  @moduledoc """
  Represents a Bucket on Supabase Storage.

  This module defines the structure and operations related to a storage bucket on Supabase.

  ## Structure

  A `Bucket` consists of:

  - `id`: The unique identifier for the bucket.
  - `name`: The display name of the bucket.
  - `owner`: The owner of the bucket.
  - `file_size_limit`: The maximum file size allowed in the bucket (in bytes). Can be `nil` for no limit.
  - `allowed_mime_types`: List of MIME types permitted in this bucket. Can be `nil` for no restrictions.
  - `created_at`: Timestamp indicating when the bucket was created.
  - `updated_at`: Timestamp indicating the last update to the bucket.
  - `public`: Boolean flag determining if the bucket is publicly accessible or not.

  ## Functions

  - `parse!/1`: Parses and returns a bucket structure.
  - `create_changeset/1`: Generates a changeset for creating a bucket.
  - `update_changeset/2`: Generates a changeset for updating an existing bucket.

  ## Examples

  ### Parsing a bucket

      bucket_attrs = %{
        id: "bucket_id",
        name: "My Bucket",
        ...
      }
      Supabase.Storage.Bucket.parse!(bucket_attrs)

  ### Creating a bucket changeset

      new_bucket_attrs = %{
        id: "new_bucket_id",
        ...
      }
      Supabase.Storage.Bucket.create_changeset(new_bucket_attrs)

  ### Updating a bucket

      existing_bucket = %Supabase.Storage.Bucket{
        id: "existing_bucket_id",
        ...
      }
      updated_attrs = %{
        public: true
      }
      Supabase.Storage.Bucket.update_changeset(existing_bucket, updated_attrs)
  """

  use Ecto.Schema

  import Ecto.Changeset

  @type t :: %__MODULE__{
          id: String.t(),
          name: String.t(),
          owner: String.t(),
          file_size_limit: integer | nil,
          allowed_mime_types: list(String.t()) | nil,
          created_at: NaiveDateTime.t(),
          updated_at: NaiveDateTime.t(),
          public: boolean
        }

  @fields ~w(id name created_at updated_at file_size_limit allowed_mime_types public owner)a
  @create_fields ~w(id name file_size_limit allowed_mime_types public)a
  @update_fields ~w(file_size_limit allowed_mime_types public)a

  @primary_key false
  embedded_schema do
    field(:id, :string)
    field(:name, :string)
    field(:owner, :string)
    field(:file_size_limit, :integer)
    field(:allowed_mime_types, {:array, :string})
    field(:created_at, :naive_datetime)
    field(:updated_at, :naive_datetime)
    field(:public, :boolean, default: false)
  end

  @spec parse!(map) :: t
  def parse!(attrs) do
    %__MODULE__{}
    |> cast(attrs, @fields)
    |> apply_action!(:parse)
  end

  @spec create_changeset(map) :: {:ok, map} | {:error, Ecto.Changeset.t()}
  def create_changeset(attrs) do
    %__MODULE__{}
    |> cast(attrs, @create_fields)
    |> validate_required([:id])
    |> maybe_put_name()
    |> apply_action(:create)
    |> case do
      {:ok, data} -> {:ok, Map.take(data, @create_fields)}
      err -> err
    end
  end

  defp maybe_put_name(changeset) do
    if get_change(changeset, :name) do
      changeset
    else
      id = get_change(changeset, :id)
      put_change(changeset, :name, id)
    end
  end

  @spec update_changeset(t, map) :: {:ok, map} | {:error, Ecto.Changeset.t()}
  def update_changeset(%__MODULE__{} = bucket, attrs) do
    bucket
    |> cast(attrs, @update_fields)
    |> apply_action(:update)
    |> case do
      {:ok, data} -> {:ok, Map.take(data, @update_fields)}
      err -> err
    end
  end
end