towerops/lib/towerops_web/controllers/api/v1/invitations_controller.ex
Graham McIntire 146f5745cf
fix: resolve compilation errors, test failures, and credo issues
- Escape HEEx template braces in GraphQL/API docs with raw(~S[...])
- Fix test assertions for updated marketing copy and UI text
- Extract helper functions in GraphQL resolvers to reduce nesting depth
- Create shared ErrorHelpers module for API controllers
- Fix ETS race condition in brute force whitelist cache for async tests
- Fix property test generators to use ASCII instead of printable unicode
- Add alert_severity helper to site_live/show
- Update accounts fixtures for explicit user confirmation
2026-02-14 12:23:10 -06:00

86 lines
2.6 KiB
Elixir

defmodule ToweropsWeb.Api.V1.InvitationsController do
@moduledoc "API controller for organization invitations."
use ToweropsWeb, :controller
import ToweropsWeb.Api.ErrorHelpers, only: [translate_errors: 1]
alias Towerops.Organizations
def index(conn, _params) do
organization_id = conn.assigns.current_organization_id
invitations =
organization_id
|> Organizations.list_pending_invitations()
|> Enum.map(&format_invitation/1)
json(conn, %{data: invitations})
end
def create(conn, %{"email" => email, "role" => role}) do
organization_id = conn.assigns.current_organization_id
user = conn.assigns[:current_user]
attrs = %{
organization_id: organization_id,
email: email,
role: role,
invited_by_id: user && user.id,
token: Ecto.UUID.generate(),
expires_at: DateTime.utc_now() |> DateTime.add(7 * 86_400, :second) |> DateTime.truncate(:second)
}
case Organizations.create_invitation(attrs) do
{:ok, invitation} ->
conn
|> put_status(:created)
|> json(%{data: format_invitation(Towerops.Repo.preload(invitation, :invited_by))})
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{errors: translate_errors(changeset)})
end
end
def create(conn, _params) do
conn |> put_status(:bad_request) |> json(%{error: "Missing 'email' and 'role' parameters"})
end
def delete(conn, %{"id" => id}) do
organization_id = conn.assigns.current_organization_id
invitation = Towerops.Repo.get!(Towerops.Organizations.Invitation, id)
if invitation.organization_id == organization_id do
case Organizations.delete_invitation(invitation) do
{:ok, _} -> conn |> put_status(:no_content) |> send_resp(204, "")
{:error, _} -> conn |> put_status(:unprocessable_entity) |> json(%{error: "Could not delete invitation"})
end
else
conn |> put_status(:not_found) |> json(%{error: "Invitation not found"})
end
rescue
Ecto.NoResultsError ->
conn |> put_status(:not_found) |> json(%{error: "Invitation not found"})
end
defp format_invitation(invitation) do
%{
id: invitation.id,
email: invitation.email,
role: invitation.role,
expires_at: invitation.expires_at,
invited_by_email: get_in_loaded(invitation, :invited_by, :email),
inserted_at: invitation.inserted_at
}
end
defp get_in_loaded(struct, assoc, field) do
case Map.get(struct, assoc) do
%Ecto.Association.NotLoaded{} -> nil
nil -> nil
loaded -> Map.get(loaded, field)
end
end
end