Current section

Files

Jump to
bunnyx lib bunnyx storage_zone.ex
Raw

lib/bunnyx/storage_zone.ex

defmodule Bunnyx.StorageZone do
@moduledoc """
Storage zones are the containers that hold files served through bunny.net's edge
storage network. This module manages storage zones through the main API — creating,
configuring, and deleting them.
This is separate from `Bunnyx.Storage`, which handles file operations (upload,
download) within a storage zone using a different authentication method.
Uses the main API client created with `Bunnyx.new/1`.
## Usage
client = Bunnyx.new(api_key: "sk-...")
{:ok, zone} = Bunnyx.StorageZone.create(client,
name: "my-zone",
region: "DE"
)
{:ok, zone} = Bunnyx.StorageZone.get(client, zone.id)
{:ok, page} = Bunnyx.StorageZone.list(client)
{:ok, zone} = Bunnyx.StorageZone.update(client, zone.id, rewrite_404_to_200: true)
{:ok, nil} = Bunnyx.StorageZone.delete(client, zone.id)
"""
@type t :: %__MODULE__{
id: pos_integer() | nil,
name: String.t() | nil,
password: String.t() | nil,
read_only_password: String.t() | nil,
date_modified: String.t() | nil,
deleted: boolean() | nil,
storage_used: integer() | nil,
files_stored: integer() | nil,
region: String.t() | nil,
replication_regions: [String.t()] | nil,
storage_hostname: String.t() | nil,
rewrite_404_to_200: boolean() | nil,
custom_404_file_path: String.t() | nil,
zone_tier: integer() | nil
}
@derive {Inspect, except: [:password, :read_only_password]}
defstruct [
:id,
:name,
:password,
:read_only_password,
:date_modified,
:deleted,
:storage_used,
:files_stored,
:region,
:replication_regions,
:storage_hostname,
:rewrite_404_to_200,
:custom_404_file_path,
:zone_tier
]
@field_mapping %{
"Id" => :id,
"Name" => :name,
"Password" => :password,
"ReadOnlyPassword" => :read_only_password,
"DateModified" => :date_modified,
"Deleted" => :deleted,
"StorageUsed" => :storage_used,
"FilesStored" => :files_stored,
"Region" => :region,
"ReplicationRegions" => :replication_regions,
"StorageHostname" => :storage_hostname,
"Rewrite404To200" => :rewrite_404_to_200,
"Custom404FilePath" => :custom_404_file_path,
"ZoneTier" => :zone_tier,
# Write-only: update endpoint uses different names than the response
"OriginUrl" => :origin_url,
"ReplicationZones" => :replication_zones
}
@reverse_mapping Map.new(@field_mapping, fn {pascal, atom} -> {atom, pascal} end)
@doc """
Lists storage zones.
## Options
* `:page` — page number
* `:per_page` — items per page
* `:search` — search term
* `:include_deleted` — include deleted zones
"""
@spec list(Bunnyx.t() | keyword(), keyword()) ::
{:ok,
[t()]
| %{
items: [t()],
current_page: integer(),
total_items: integer(),
has_more_items: boolean()
}}
| {:error, Bunnyx.Error.t()}
def list(client, opts \\ []) do
client = Bunnyx.resolve(client)
params =
opts
|> Keyword.take([:page, :per_page, :search, :include_deleted])
|> to_query_params()
case Bunnyx.HTTP.request(client.req, :get, "/storagezone", params: params) do
{:ok, body} when is_list(body) ->
{:ok, Enum.map(body, &from_response/1)}
{:ok, body} when is_map(body) ->
{:ok,
%{
items: Enum.map(body["Items"], &from_response/1),
current_page: body["CurrentPage"],
total_items: body["TotalItems"],
has_more_items: body["HasMoreItems"]
}}
{:error, _} = error ->
error
end
end
@doc "Fetches a storage zone by ID."
@spec get(Bunnyx.t() | keyword(), pos_integer()) :: {:ok, t()} | {:error, Bunnyx.Error.t()}
def get(client, id) do
client = Bunnyx.resolve(client)
case Bunnyx.HTTP.request(client.req, :get, "/storagezone/#{id}", []) do
{:ok, body} -> {:ok, from_response(body)}
{:error, _} = error -> error
end
end
@doc "Creates a storage zone with the given attributes."
@spec create(Bunnyx.t() | keyword(), keyword()) :: {:ok, t()} | {:error, Bunnyx.Error.t()}
def create(client, attrs) do
client = Bunnyx.resolve(client)
case Bunnyx.HTTP.request(client.req, :post, "/storagezone", json: to_request_body(attrs)) do
{:ok, body} -> {:ok, from_response(body)}
{:error, _} = error -> error
end
end
@doc "Updates a storage zone."
@spec update(Bunnyx.t() | keyword(), pos_integer(), keyword()) ::
{:ok, t()} | {:error, Bunnyx.Error.t()}
def update(client, id, attrs) do
client = Bunnyx.resolve(client)
case Bunnyx.HTTP.request(client.req, :post, "/storagezone/#{id}",
json: to_request_body(attrs)
) do
{:ok, body} -> {:ok, from_response(body)}
{:error, _} = error -> error
end
end
@doc "Deletes a storage zone."
@spec delete(Bunnyx.t() | keyword(), pos_integer()) :: {:ok, nil} | {:error, Bunnyx.Error.t()}
def delete(client, id) do
client = Bunnyx.resolve(client)
case Bunnyx.HTTP.request(client.req, :delete, "/storagezone/#{id}", []) do
{:ok, _} -> {:ok, nil}
{:error, _} = error -> error
end
end
@doc """
Returns storage zone statistics.
## Options
* `:date_from` — start date (ISO 8601 string)
* `:date_to` — end date (ISO 8601 string)
"""
@spec statistics(Bunnyx.t() | keyword(), pos_integer(), keyword()) ::
{:ok, %{storage_used_chart: map(), file_count_chart: map()}}
| {:error, Bunnyx.Error.t()}
def statistics(client, id, opts \\ []) do
client = Bunnyx.resolve(client)
params =
opts
|> Keyword.take([:date_from, :date_to])
|> to_statistics_params()
case Bunnyx.HTTP.request(client.req, :get, "/storagezone/#{id}/statistics", params: params) do
{:ok, body} ->
{:ok,
%{
storage_used_chart: body["StorageUsedChart"],
file_count_chart: body["FileCountChart"]
}}
{:error, _} = error ->
error
end
end
@doc "Resets the storage zone password."
@spec reset_password(Bunnyx.t() | keyword(), pos_integer()) ::
{:ok, nil} | {:error, Bunnyx.Error.t()}
def reset_password(client, id) do
client = Bunnyx.resolve(client)
case Bunnyx.HTTP.request(client.req, :post, "/storagezone/#{id}/resetPassword", []) do
{:ok, _} -> {:ok, nil}
{:error, _} = error -> error
end
end
@doc "Resets the storage zone read-only password."
@spec reset_read_only_password(Bunnyx.t() | keyword(), pos_integer()) ::
{:ok, nil} | {:error, Bunnyx.Error.t()}
def reset_read_only_password(client, id) do
client = Bunnyx.resolve(client)
case Bunnyx.HTTP.request(client.req, :post, "/storagezone/resetReadOnlyPassword",
params: %{"id" => id}
) do
{:ok, _} -> {:ok, nil}
{:error, _} = error -> error
end
end
@doc "Checks if a storage zone name is available."
@spec check_availability(Bunnyx.t() | keyword(), String.t()) ::
{:ok, boolean()} | {:error, Bunnyx.Error.t()}
def check_availability(client, name) do
client = Bunnyx.resolve(client)
case Bunnyx.HTTP.request(client.req, :post, "/storagezone/checkavailability",
json: %{"Name" => name}
) do
{:ok, body} -> {:ok, body["Available"]}
{:error, _} = error -> error
end
end
defp from_response(data) when is_map(data) do
fields =
for {pascal, atom} <- @field_mapping, Map.has_key?(data, pascal), into: %{} do
{atom, data[pascal]}
end
struct(__MODULE__, fields)
end
defp to_request_body(attrs) do
Bunnyx.Params.map_keys!(attrs, @reverse_mapping)
end
defp to_statistics_params(opts) do
mapping = %{date_from: "dateFrom", date_to: "dateTo"}
Map.new(opts, fn {key, value} ->
{Map.fetch!(mapping, key), value}
end)
end
defp to_query_params(opts) do
mapping = %{
page: "page",
per_page: "perPage",
search: "search",
include_deleted: "includeDeleted"
}
Map.new(opts, fn {key, value} ->
{Map.fetch!(mapping, key), value}
end)
end
end