postland/lib/postland/follows.ex

167 lines
4.4 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, where: not is_nil(f.confirmed_at))
|> Repo.all()
end
def all_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 -> confirm_request(request) end)
|> Multi.run(:send_acceptance, fn _data, _repo ->
send_acceptance(request)
end)
|> Repo.transaction()
end
def send_acceptance(request) do
{:ok, actor} = ActivityPub.fetch_actor(request.follower)
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 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