defmodule GitGud.Federation.Actor do
use Ecto.Schema
import Ecto.Changeset
alias GitGud.Accounts.User
alias GitGud.Organizations.Organization
alias GitGud.Repositories.Repository
@kinds ~w(local remote)
# ActivityStreams + ForgeFed types we issue. Inbound activities may
# carry unknown types — we keep them as strings.
@types ~w(Person Group Organization Service Application Repository)
@statuses ~w(active suspended shadow_banned)
schema "actors" do
field :kind, :string
field :actor_type, :string
field :actor_url, :string
field :inbox_url, :string
field :outbox_url, :string
field :followers_url, :string
field :following_url, :string
field :shared_inbox_url, :string
field :preferred_username, :string
field :name, :string
field :summary, :string
field :public_key_pem, :string
field :public_key_id, :string
field :private_key_pem, :string, redact: true
field :remote_doc, :map
field :last_fetched_at, :utc_datetime
field :fetch_error, :string
field :status, :string, default: "active"
belongs_to :local_user, User
belongs_to :local_organization, Organization
belongs_to :local_repository, Repository
timestamps(type: :utc_datetime)
end
@doc "Changeset for a new local actor — caller supplies the keypair."
def local_changeset(attrs) do
%__MODULE__{kind: "local"}
|> cast(attrs, [
:actor_type,
:actor_url,
:inbox_url,
:outbox_url,
:followers_url,
:following_url,
:shared_inbox_url,
:preferred_username,
:name,
:summary,
:public_key_pem,
:public_key_id,
:private_key_pem,
:local_user_id,
:local_organization_id,
:local_repository_id
])
|> validate_required([
:actor_type,
:actor_url,
:public_key_pem,
:public_key_id,
:private_key_pem
])
|> validate_inclusion(:actor_type, @types)
|> validate_exactly_one_local_fk()
|> unique_constraint(:actor_url)
end
@doc "Changeset for a freshly fetched remote actor."
def remote_changeset(struct \\ %__MODULE__{kind: "remote"}, attrs) do
struct
|> Map.put(:kind, "remote")
|> cast(attrs, [
:actor_type,
:actor_url,
:inbox_url,
:outbox_url,
:followers_url,
:following_url,
:shared_inbox_url,
:preferred_username,
:name,
:summary,
:public_key_pem,
:public_key_id,
:remote_doc,
:last_fetched_at
])
|> validate_required([:actor_type, :actor_url, :public_key_pem, :public_key_id])
|> unique_constraint(:actor_url)
end
def status_changeset(actor, status) when status in @statuses,
do: change(actor, status: status)
defp validate_exactly_one_local_fk(cs) do
set =
Enum.count(
[:local_user_id, :local_organization_id, :local_repository_id],
&(get_field(cs, &1) not in [nil, ""])
)
case set do
1 -> cs
_ -> add_error(cs, :base, "exactly one local_* foreign key must be set for local actors")
end
end
def kinds, do: @kinds
def types, do: @types
def statuses, do: @statuses
@doc "Host part of the actor's URL (used for instance-block checks)."
def host(%__MODULE__{actor_url: url}) when is_binary(url) do
case URI.parse(url) do
%URI{host: h} when is_binary(h) -> h
_ -> nil
end
end
end
3.3 KiB · text
5af55d9