feat: Add rudimentary follow UI

This commit is contained in:
Ro 2024-10-13 20:02:59 -05:00
parent 9b313ecfdf
commit 7894049b6c
6 changed files with 173 additions and 17 deletions

View file

@ -1,15 +1,31 @@
defmodule ActivityPub do
def get(url, actor_url, private_key) do
headers = ActivityPub.Headers.signing_headers("GET", url, "", actor_url, private_key)
Req.new(url: url)
|> Req.Request.put_header("accept", "application/activity+json")
|> Req.Request.put_headers(headers)
|> Req.get()
|> case do
{:ok, response} ->
{:ok, response.body}
other ->
other
end
end
def fetch_actor(actor_id) do
request =
Req.new(url: actor_id)
|> Req.Request.put_header("accept", "application/json")
request =
Req.new(url: actor_id)
|> Req.Request.put_header("accept", "application/json")
case Req.get(request) do
{:ok, result} ->
{:ok, result.body}
case Req.get(request) do
{:ok, result} ->
{:ok, result.body}
error ->
error
end
error ->
error
end
end
end

View file

@ -0,0 +1,22 @@
defmodule ActivityPub.Webfinger do
def lookup_resource(acct_handle) do
[_handle, domain] = String.split(acct_handle, "@")
uri = %URI{
scheme: "https",
authority: domain,
host: domain,
port: 443,
path: "/.well-known/webfinger",
query: "resource=acct:#{acct_handle}"
}
case Req.get(uri) do
{:ok, response} ->
{:ok, response.body}
other ->
other
end
end
end

View file

@ -43,14 +43,27 @@ defmodule Postland.Follows do
}
|> Jason.encode!()
headers = Headers.signing_headers("POST", inbox, body, Postland.my_actor_id(), Accounts.solo_user().private_key)
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()
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
@ -60,6 +73,19 @@ defmodule Postland.Follows do
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
@ -68,22 +94,32 @@ defmodule Postland.Follows do
{:ok, actor} = ActivityPub.fetch_actor(to_actor_id)
inbox = Map.get(actor, "inbox")
follow_request = %{
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!()
}
|> Jason.encode!()
headers = Headers.signing_headers("POST", inbox, follow_request, Postland.my_actor_id(), Accounts.solo_user().private_key)
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()
from(f in Follow, where: f.followee == ^followee, where: f.follower == ^follower)
|> Repo.one()
end
def pending_inbound_requests() do
@ -109,5 +145,9 @@ defmodule Postland.Follows do
request
|> Follow.confirm_changeset()
|> Repo.update()
|> case do
{:ok, follow} ->
Phoenix.PubSub.broadcast(Postland.PubSub, "follows:#{follow.followee}", {:update, follow})
end
end
end

View file

@ -9,7 +9,7 @@ defmodule PostlandWeb.ExportController do
files = [filename, "#{filename}-shm", "#{filename}-wal"] |> Enum.map(&String.to_charlist/1)
zip_path = Path.join([File.cwd!(), "priv", "tmp", "db-export.zip"])
:zip.create(zip_path, files, cwd: dirname)
:zip.create(String.to_charlist(zip_path), files, cwd: dirname)
send_download(conn, {:file, zip_path}, filename: "db-export.zip")
end

View file

@ -0,0 +1,77 @@
defmodule PostlandWeb.OtherProfileLive do
use PostlandWeb, :live_view
alias Postland.Follows
def render(assigns) do
~H"""
<h1><%= Map.get(@profile, "name") %> (<%= @acct %>)</h1>
<%= case @follow do %>
<% nil -> %>
<.button phx-click="follow">Follow</.button>
<% %{confirmed_at: nil} -> %>
<.button disabled>Pending</.button>
<% _ -> %>
<.button phx-click="unfollow">Unfollow</.button>
<% end %>
<.list>
<:item :for={attachment <- Map.get(@profile, "attachment")} title={attachment["name"]}>
<%= attachment["value"] %>
</:item>
</.list>
"""
end
def mount(params, _session, socket) do
{:ok, resource} = ActivityPub.Webfinger.lookup_resource(Map.get(params, "acct"))
%{"href" => json_profile} =
resource
|> Map.get("links")
|> Enum.find(fn %{"rel" => rel} = link ->
rel == "self" && String.contains?(Map.get(link, "type"), "json")
end)
{:ok, profile} =
ActivityPub.get(
json_profile,
Postland.my_actor_id(),
Postland.Accounts.solo_user().private_key
)
follow = Follows.get(Postland.my_actor_id(), json_profile)
if Phoenix.LiveView.connected?(socket) do
Phoenix.PubSub.subscribe(Postland.PubSub, "follows:#{json_profile}")
end
{:ok,
assign(socket,
acct: Map.get(params, "acct"),
actor_id: json_profile,
follow: follow,
profile: profile
)}
end
def handle_event("follow", _unsigned_params, socket) do
actor_id = socket.assigns.actor_id
case Follows.record_and_send_follow_request(actor_id) do
{:ok, _} ->
{:noreply,
socket
|> put_flash(:success, "Follow request sent.")
|> assign(follow: Follows.get(Postland.my_actor_id(), actor_id))}
_other ->
{:noreply,
socket
|> put_flash(:error, "An unexpected error has occurred.")}
end
end
def handle_info({:update, follow}, socket) do
{:noreply, assign(socket, follow: follow)}
end
end

View file

@ -80,6 +80,7 @@ defmodule PostlandWeb.Router do
live_session :require_authenticated_user,
on_mount: [{PostlandWeb.UserAuth, :ensure_authenticated}] do
live "/users/settings", UserSettingsLive, :edit
live "/@:acct", OtherProfileLive, :show
end
end