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] case authorize_invite(organization_id, user, role) do {:ok, _} -> attrs = %{ organization_id: organization_id, email: email, role: role, invited_by_id: 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 {:error, reason} -> conn |> put_status(:forbidden) |> json(%{error: reason}) end end def create(conn, _params) do conn |> put_status(:bad_request) |> json(%{error: "Missing 'email' and 'role' parameters"}) end defp authorize_invite(_org_id, nil, _role), do: {:error, "authentication required"} defp authorize_invite(org_id, user, _role) do membership = Organizations.get_membership(org_id, user.id) case membership do nil -> {:error, "not a member of this organization"} %{role: member_role} when member_role not in [:owner, :admin] -> {:error, "only admins and owners can send invitations"} _ -> {:ok, membership} end 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