postland/lib/postland/follows.ex
2024-11-26 18:04:08 -06:00

267 lines
6.7 KiB
Elixir

defmodule Postland.Follows do
use Phoenix.VerifiedRoutes, endpoint: PostlandWeb.Endpoint, router: PostlandWeb.Router
import Ecto.Query, warn: false
alias Postland.Accounts
alias Postland.Activity
alias Postland.Follow
alias Postland.Repo
alias ActivityPub.Headers
alias Ecto.Multi
def all_following() do
my_actor_id = Postland.my_actor_id()
from(f in Follow, where: f.follower == ^my_actor_id)
|> Repo.all()
end
def all_followers() do
my_actor_id = Postland.my_actor_id()
from(f in Follow, where: f.followee == ^my_actor_id)
|> Repo.all()
end
def confirmed_followers() do
my_actor_id = Postland.my_actor_id()
from(f in Follow, where: f.followee == ^my_actor_id, where: not is_nil(f.confirmed_at))
|> Repo.all()
end
def record_and_send_acceptance(request) do
Multi.new()
|> Multi.run(:confirm_timestamp, fn _data, _repo ->
case confirm_request(request) do
:ok ->
{:ok, :ok}
other ->
{:error, other}
end
end)
|> Multi.run(:send_acceptance, fn _data, _repo ->
send_acceptance(request)
end)
|> Repo.transaction()
end
def record_and_send_withdrawl(request) do
Multi.new()
|> Multi.delete(:delete, request)
|> Multi.run(:send_withdrawl, fn _data, _repo ->
send_withdrawl(request)
end)
|> Repo.transaction()
end
def record_and_unfollow(follow) do
Multi.new()
|> Multi.delete(:delete, follow)
|> Multi.run(:send_delete_follow, fn _data, _repo ->
send_delete_follow(follow)
end)
|> Repo.transaction()
end
def send_withdrawl(request) do
request.followee
|> ActivityPub.fetch_actor()
|> do_send_withdrawl(request)
end
defp do_send_withdrawl(nil, _), do: {:ok, nil}
defp do_send_withdrawl({:ok, actor}, request) do
inbox = Map.get(actor, "inbox")
body =
%{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Undo",
"actor" => Postland.my_actor_id(),
"object" => Follow.id(request)
}
|> Jason.encode!()
headers =
Headers.signing_headers(
"POST",
inbox,
body,
Postland.my_actor_id(),
Accounts.solo_user().private_key
)
Req.post(inbox, headers: headers, body: body)
end
def send_acceptance(request) do
request.follower
|> ActivityPub.fetch_actor()
|> do_send_acceptance(request)
end
defp do_send_acceptance(nil, _), do: {:ok, nil}
defp do_send_acceptance({:ok, actor}, request) do
inbox = Map.get(actor, "inbox")
case get_follow_activity(request.follower) do
nil ->
{:error, "could not find Follow activity to Accept"}
follow_activity ->
body =
%{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Accept",
"actor" => Postland.my_actor_id(),
"object" => %{
"actor" => request.follower,
"id" => Map.get(follow_activity.data, "id"),
"object" => Postland.my_actor_id(),
"type" => "Follow"
}
}
|> Jason.encode!()
headers =
Headers.signing_headers(
"POST",
inbox,
body,
Postland.my_actor_id(),
Accounts.solo_user().private_key
)
Req.post(inbox, headers: headers, body: body)
end
end
def get_follow_activity(follower) do
from(a in Activity,
where: a.type == "Follow",
where: a.actor_id == ^follower,
limit: 1,
order_by: [desc: :inserted_at]
)
|> Repo.one()
end
def record_and_send_follow_request(to_actor_id) do
Multi.new()
|> Multi.run(:follow_record, fn _data, _repo -> record_outbound_request(to_actor_id) end)
|> Multi.run(:send_request, fn _data, _repo ->
send_follow_request(to_actor_id)
end)
|> Repo.transaction()
|> case do
{:ok, %{follow_record: follow_record}} = result ->
Phoenix.PubSub.broadcast(
Postland.PubSub,
"follows:#{to_actor_id}",
{:update, follow_record}
)
result
other ->
other
end
end
def send_follow_request(to_actor_id) do
encoded_followee = Base.url_encode64(to_actor_id, padding: false)
encoded_follower = Base.url_encode64(Postland.my_actor_id(), padding: false)
{:ok, actor} = ActivityPub.fetch_actor(to_actor_id)
inbox = Map.get(actor, "inbox")
follow_request =
%{
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => url(~p"/follows/#{encoded_followee}/#{encoded_follower}"),
"type" => "Follow",
"actor" => Postland.my_actor_id(),
"object" => to_actor_id
}
|> Jason.encode!()
headers =
Headers.signing_headers(
"POST",
inbox,
follow_request,
Postland.my_actor_id(),
Accounts.solo_user().private_key
)
Req.post(inbox, headers: headers, body: follow_request)
end
def send_delete_follow(follow) do
encoded_followee = Base.url_encode64(follow.followee, padding: false)
encoded_follower = Base.url_encode64(follow.follower, padding: false)
{:ok, actor} = ActivityPub.fetch_actor(follow.followee)
inbox = Map.get(actor, "inbox")
follow_request =
%{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Delete",
"actor" => Postland.my_actor_id(),
"object" => url(~p"/follows/#{encoded_followee}/#{encoded_follower}")
}
|> Jason.encode!()
headers =
Headers.signing_headers(
"POST",
inbox,
follow_request,
Postland.my_actor_id(),
Accounts.solo_user().private_key
)
Req.post(inbox, headers: headers, body: follow_request)
end
def get(follower, followee) do
from(f in Follow, where: f.followee == ^followee, where: f.follower == ^follower)
|> Repo.one()
end
def pending_inbound_requests() do
my_actor_id = Postland.my_actor_id()
from(f in Follow, where: f.followee == ^my_actor_id, where: is_nil(f.confirmed_at))
|> Repo.all()
end
def record_outbound_request(to_actor_id) do
Postland.my_actor_id()
|> Follow.changeset(to_actor_id)
|> Repo.insert(conflict_target: [:followee, :follower], on_conflict: :nothing)
end
def record_inbound_request(from_actor_id) do
from_actor_id
|> Follow.changeset(Postland.my_actor_id())
|> Repo.insert(conflict_target: [:followee, :follower], on_conflict: :nothing)
end
def confirm_request(request) do
request
|> Follow.confirm_changeset()
|> Repo.update()
|> case do
{:ok, follow} ->
Phoenix.PubSub.broadcast(Postland.PubSub, "follows:#{follow.followee}", {:update, follow})
end
end
end