- Created Towerops.Schema base macro (95+ schemas updated to use it) - Created Towerops.Snmp.Reading macro (7 reading schemas consolidated) - Created Towerops.Gaiia.BaseSchema macro (4 Gaiia schemas consolidated) - Created Towerops.SyncLog shared module (UISP + Preseem merge) - Created Towerops.LogFilters (3 log filter modules merged into 1) - Created Towerops.OnCall.ChangesetHelpers (9 on-call schemas simplified) - Created API v1 ResourceController shared helpers (7 controllers) - Extracted ConnectionHelpers.format_connection_result (2 LiveViews) - Removed 5 dead config keys (scopes, mib_dirs, stripe_meter_id, etc.) - Fixed 2 pre-existing broken tests - All 13,219 tests pass, Credo clean (0 issues)
131 lines
3.8 KiB
Elixir
131 lines
3.8 KiB
Elixir
defmodule ToweropsWeb.Api.V1.ResourceController do
|
|
@moduledoc """
|
|
Shared helpers for standard API v1 CRUD resource operations.
|
|
|
|
Eliminates the repetitive `ScopedResource.fetch` + case + changeset
|
|
error handling pattern that appears in every resource controller.
|
|
|
|
## Usage
|
|
|
|
defmodule ToweropsWeb.Api.V1.SitesController do
|
|
use ToweropsWeb, :controller
|
|
|
|
alias ToweropsWeb.Api.V1.ResourceController, as: Resources
|
|
|
|
def show(conn, %{"id" => id}) do
|
|
org_id = conn.assigns.current_organization_id
|
|
Resources.fetch_and_render(conn, Site, id, org_id, &format_site/1, "Site not found")
|
|
end
|
|
end
|
|
"""
|
|
|
|
alias Phoenix.Controller
|
|
alias Plug.Conn
|
|
alias ToweropsWeb.Api.ErrorHelpers
|
|
alias ToweropsWeb.ScopedResource
|
|
|
|
@doc """
|
|
Fetch a resource by id and org scope, render JSON or 404.
|
|
|
|
## Examples
|
|
|
|
Resources.fetch_and_render(conn, Site, id, org_id, &format_site/1, "Site not found")
|
|
"""
|
|
def fetch_and_render(conn, schema, id, org_id, format_fn, not_found_msg) do
|
|
case ScopedResource.fetch(schema, id, org_id) do
|
|
{:ok, resource} ->
|
|
Controller.json(conn, format_fn.(resource))
|
|
|
|
{:error, :not_found} ->
|
|
not_found(conn, not_found_msg)
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Fetch a resource by id and org scope with preloads, render JSON or 404.
|
|
"""
|
|
def fetch_and_render(conn, schema, id, org_id, preloads, format_fn, not_found_msg) do
|
|
case ScopedResource.fetch_preload(schema, id, org_id, preloads) do
|
|
{:ok, resource} ->
|
|
Controller.json(conn, format_fn.(resource))
|
|
|
|
{:error, :not_found} ->
|
|
not_found(conn, not_found_msg)
|
|
end
|
|
end
|
|
|
|
def update_and_render(conn, schema, id, org_id, opts) do
|
|
context = opts[:context]
|
|
update_fn = opts[:update_fn]
|
|
params = opts[:params]
|
|
format_fn = opts[:format_fn]
|
|
not_found_msg = opts[:not_found_msg]
|
|
|
|
case ScopedResource.fetch(schema, id, org_id) do
|
|
{:ok, resource} ->
|
|
case apply(context, update_fn, [resource, params]) do
|
|
{:ok, updated} ->
|
|
Controller.json(conn, format_fn.(updated))
|
|
|
|
{:error, %Ecto.Changeset{} = changeset} ->
|
|
unprocessable_entity(conn, changeset)
|
|
end
|
|
|
|
{:error, :not_found} ->
|
|
not_found(conn, not_found_msg)
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Fetch a resource, apply a delete function, render success or errors.
|
|
"""
|
|
def delete_and_render(conn, schema, id, org_id, context, delete_fn, not_found_msg) do
|
|
case ScopedResource.fetch(schema, id, org_id) do
|
|
{:ok, resource} ->
|
|
case apply(context, delete_fn, [resource]) do
|
|
{:ok, _} ->
|
|
Controller.json(conn, %{success: true})
|
|
|
|
{:error, %Ecto.Changeset{} = changeset} ->
|
|
unprocessable_entity(conn, changeset)
|
|
end
|
|
|
|
{:error, :not_found} ->
|
|
not_found(conn, not_found_msg)
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Render 201 created with formatted resource, or 422 with changeset errors.
|
|
"""
|
|
def created_or_error(conn, {:ok, resource}, format_fn) do
|
|
conn
|
|
|> Conn.put_status(:created)
|
|
|> Controller.json(format_fn.(resource))
|
|
end
|
|
|
|
def created_or_error(conn, {:error, %Ecto.Changeset{} = changeset}, _format_fn) do
|
|
unprocessable_entity(conn, changeset)
|
|
end
|
|
|
|
@doc "Render 422 with changeset errors."
|
|
def unprocessable_entity(conn, changeset) do
|
|
conn
|
|
|> Conn.put_status(:unprocessable_entity)
|
|
|> Controller.json(%{errors: ErrorHelpers.translate_errors(changeset)})
|
|
end
|
|
|
|
@doc "Render 404 JSON error."
|
|
def not_found(conn, message) do
|
|
conn
|
|
|> Conn.put_status(:not_found)
|
|
|> Controller.json(%{error: message})
|
|
end
|
|
|
|
@doc "Render 400 for missing required parameter."
|
|
def missing_param(conn, param_name) do
|
|
conn
|
|
|> Conn.put_status(:bad_request)
|
|
|> Controller.json(%{error: "Missing '#{param_name}' parameter"})
|
|
end
|
|
end
|