towerops/lib/towerops_web/graphql/resolvers/escalation_policy.ex
Graham McIntire f6e9063577
feat: add REST API and GraphQL for on-call schedules and escalation policies
REST API v1 endpoints for schedules (14 endpoints) and escalation
policies (10 endpoints) with full CRUD for nested resources (layers,
members, overrides, rules, targets). GraphQL types, resolvers, and
schema mutations for both resource trees.
2026-03-11 14:01:15 -05:00

168 lines
5.7 KiB
Elixir

defmodule ToweropsWeb.GraphQL.Resolvers.EscalationPolicy do
@moduledoc "GraphQL resolvers for escalation policy queries and mutations."
alias Towerops.OnCall
alias Towerops.OnCall.EscalationPolicy
alias Towerops.OnCall.EscalationRule
alias Towerops.OnCall.EscalationTarget
alias Towerops.Repo
alias ToweropsWeb.GraphQL.Resolvers.Helpers
alias ToweropsWeb.ScopedResource
# --- Queries ---
def list(_parent, _args, %{context: %{organization_id: org_id}}) do
{:ok, OnCall.list_escalation_policies(org_id)}
end
def list(_parent, _args, _resolution), do: Helpers.authentication_error()
def get(_parent, %{id: id}, %{context: %{organization_id: org_id}}) do
case ScopedResource.fetch_preload(EscalationPolicy, id, org_id, rules: :targets) do
{:ok, policy} -> {:ok, policy}
{:error, _} -> {:error, "Escalation policy not found"}
end
end
def get(_parent, _args, _resolution), do: Helpers.authentication_error()
# --- Mutations ---
def create(_parent, %{input: input}, %{context: %{organization_id: org_id}}) do
attrs =
input
|> Map.new(fn {k, v} -> {to_string(k), v} end)
|> Map.put("organization_id", org_id)
case OnCall.create_escalation_policy(attrs) do
{:ok, policy} -> {:ok, policy}
{:error, changeset} -> {:error, Helpers.format_changeset_errors(changeset)}
end
end
def create(_parent, _args, _resolution), do: Helpers.authentication_error()
def update(_parent, %{id: id, input: input}, %{context: %{organization_id: org_id}}) do
with {:ok, policy} <- fetch_org_policy(id, org_id) do
attrs = Map.new(input, fn {k, v} -> {to_string(k), v} end)
case OnCall.update_escalation_policy(policy, attrs) do
{:ok, updated} -> {:ok, updated}
{:error, changeset} -> {:error, Helpers.format_changeset_errors(changeset)}
end
end
end
def update(_parent, _args, _resolution), do: Helpers.authentication_error()
def delete(_parent, %{id: id}, %{context: %{organization_id: org_id}}) do
with {:ok, policy} <- fetch_org_policy(id, org_id) do
case OnCall.delete_escalation_policy(policy) do
{:ok, _} -> {:ok, %{success: true, message: "Escalation policy deleted"}}
{:error, _} -> {:ok, %{success: false, message: "Could not delete escalation policy"}}
end
end
end
def delete(_parent, _args, _resolution), do: Helpers.authentication_error()
# --- Rule mutations ---
def create_rule(_parent, %{escalation_policy_id: policy_id, input: input}, %{context: %{organization_id: org_id}}) do
with {:ok, _policy} <- fetch_org_policy(policy_id, org_id) do
attrs =
input
|> Map.new(fn {k, v} -> {to_string(k), v} end)
|> Map.put("escalation_policy_id", policy_id)
case OnCall.create_escalation_rule(attrs) do
{:ok, rule} -> {:ok, rule}
{:error, changeset} -> {:error, Helpers.format_changeset_errors(changeset)}
end
end
end
def create_rule(_parent, _args, _resolution), do: Helpers.authentication_error()
def update_rule(_parent, %{id: id, input: input}, %{context: %{organization_id: org_id}}) do
with {:ok, rule} <- fetch_rule_by_org(id, org_id) do
attrs = Map.new(input, fn {k, v} -> {to_string(k), v} end)
case OnCall.update_escalation_rule(rule, attrs) do
{:ok, updated} -> {:ok, updated}
{:error, changeset} -> {:error, Helpers.format_changeset_errors(changeset)}
end
end
end
def update_rule(_parent, _args, _resolution), do: Helpers.authentication_error()
def delete_rule(_parent, %{id: id}, %{context: %{organization_id: org_id}}) do
with {:ok, rule} <- fetch_rule_by_org(id, org_id) do
case OnCall.delete_escalation_rule(rule) do
{:ok, _} -> {:ok, %{success: true, message: "Rule deleted"}}
{:error, _} -> {:ok, %{success: false, message: "Could not delete rule"}}
end
end
end
def delete_rule(_parent, _args, _resolution), do: Helpers.authentication_error()
# --- Target mutations ---
def create_target(_parent, %{escalation_rule_id: rule_id, input: input}, %{context: %{organization_id: org_id}}) do
with {:ok, _rule} <- fetch_rule_by_org(rule_id, org_id) do
attrs =
input
|> Map.new(fn {k, v} -> {to_string(k), v} end)
|> Map.put("escalation_rule_id", rule_id)
case OnCall.create_escalation_target(attrs) do
{:ok, target} -> {:ok, target}
{:error, changeset} -> {:error, Helpers.format_changeset_errors(changeset)}
end
end
end
def create_target(_parent, _args, _resolution), do: Helpers.authentication_error()
def delete_target(_parent, %{id: id}, %{context: %{organization_id: _org_id}}) do
case Repo.get(EscalationTarget, id) do
nil ->
{:error, "Target not found"}
target ->
case OnCall.delete_escalation_target(target) do
{:ok, _} -> {:ok, %{success: true, message: "Target deleted"}}
{:error, _} -> {:ok, %{success: false, message: "Could not delete target"}}
end
end
end
def delete_target(_parent, _args, _resolution), do: Helpers.authentication_error()
# --- Private helpers ---
defp fetch_org_policy(id, org_id) do
case ScopedResource.fetch(EscalationPolicy, id, org_id) do
{:ok, policy} -> {:ok, policy}
{:error, _} -> {:error, "Escalation policy not found"}
end
end
defp fetch_rule_by_org(rule_id, org_id) do
case Repo.get(EscalationRule, rule_id) do
nil ->
{:error, "Rule not found"}
rule ->
rule = Repo.preload(rule, :escalation_policy)
if rule.escalation_policy.organization_id == org_id do
{:ok, rule}
else
{:error, "Rule not found"}
end
end
end
end