2.0 KiB · text History 8ccf537
defmodule GitGud.Audit.Actor do
  @moduledoc """
  Who did something, when it wasn't a signed-in user's request (which is
  a `GitGud.Accounts.Scope`): a runner, a CI job, the forge itself, a
  remote ActivityPub actor, or a user over the API.
  """

  alias GitGud.Accounts.User

  @enforce_keys [:type, :label]
  defstruct [:type, :id, :ref, :label, :via, :ip, :user_agent]

  @type t :: %__MODULE__{
          type: String.t(),
          id: integer() | nil,
          ref: integer() | nil,
          label: String.t(),
          via: String.t() | nil,
          ip: String.t() | nil,
          user_agent: String.t() | nil
        }

  @doc "A registered runner, from the address it called from."
  def runner(%{id: id, name: name}, ip \\ nil),
    do: %__MODULE__{
      type: "runner",
      ref: id,
      label: "runner #{name}",
      via: "runner",
      ip: ip_string(ip)
    }

  @doc "A CI job, by its run and job id."
  def job(%{id: job_id, job_id: name}, %{number: number}),
    do: %__MODULE__{type: "job", ref: job_id, label: "run ##{number} / job #{name}", via: "job"}

  @doc "The forge itself: a cron job, an import, an incoming webhook."
  def system(kind) when kind in [:cron, :import, :webhook],
    do: %__MODULE__{
      type: "system",
      label: "system",
      via: if(kind == :cron, do: "cron", else: "job")
    }

  @doc "A remote ActivityPub actor, by its id URL."
  def federated(%{actor_url: url}) when is_binary(url),
    do: %__MODULE__{type: "federated", label: String.slice(url, 0, 255)}

  @doc "A user acting with a token over one of the APIs."
  def api(%User{} = user, via \\ "api", ip \\ nil, user_agent \\ nil)
      when via in ["api", "mobile_api", "git"] do
    %__MODULE__{
      type: "user",
      id: user.id,
      label: user.handle,
      via: via,
      ip: ip_string(ip),
      user_agent: user_agent
    }
  end

  @doc "The address as stored: a string, from a tuple or a string."
  def ip_string(nil), do: nil
  def ip_string(ip) when is_binary(ip), do: ip
  def ip_string(ip) when is_tuple(ip), do: ip |> :inet.ntoa() |> to_string()
end