feat: Add rudimentary follow UI
This commit is contained in:
parent
9b313ecfdf
commit
7894049b6c
6 changed files with 173 additions and 17 deletions
|
|
@ -1,15 +1,31 @@
|
||||||
defmodule ActivityPub do
|
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
|
def fetch_actor(actor_id) do
|
||||||
request =
|
request =
|
||||||
Req.new(url: actor_id)
|
Req.new(url: actor_id)
|
||||||
|> Req.Request.put_header("accept", "application/json")
|
|> Req.Request.put_header("accept", "application/json")
|
||||||
|
|
||||||
case Req.get(request) do
|
case Req.get(request) do
|
||||||
{:ok, result} ->
|
{:ok, result} ->
|
||||||
{:ok, result.body}
|
{:ok, result.body}
|
||||||
|
|
||||||
error ->
|
error ->
|
||||||
error
|
error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
22
lib/activity_pub/webfinger.ex
Normal file
22
lib/activity_pub/webfinger.ex
Normal 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
|
||||||
|
|
@ -43,14 +43,27 @@ defmodule Postland.Follows do
|
||||||
}
|
}
|
||||||
|> Jason.encode!()
|
|> 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)
|
Req.post(inbox, headers: headers, body: body)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_follow_activity(follower) do
|
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
|
end
|
||||||
|
|
||||||
def record_and_send_follow_request(to_actor_id) do
|
def record_and_send_follow_request(to_actor_id) do
|
||||||
|
|
@ -60,6 +73,19 @@ defmodule Postland.Follows do
|
||||||
send_follow_request(to_actor_id)
|
send_follow_request(to_actor_id)
|
||||||
end)
|
end)
|
||||||
|> Repo.transaction()
|
|> 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
|
end
|
||||||
|
|
||||||
def send_follow_request(to_actor_id) do
|
def send_follow_request(to_actor_id) do
|
||||||
|
|
@ -68,22 +94,32 @@ defmodule Postland.Follows do
|
||||||
|
|
||||||
{:ok, actor} = ActivityPub.fetch_actor(to_actor_id)
|
{:ok, actor} = ActivityPub.fetch_actor(to_actor_id)
|
||||||
inbox = Map.get(actor, "inbox")
|
inbox = Map.get(actor, "inbox")
|
||||||
follow_request = %{
|
|
||||||
|
follow_request =
|
||||||
|
%{
|
||||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
"id" => url(~p"/follows/#{encoded_followee}/#{encoded_follower}"),
|
"id" => url(~p"/follows/#{encoded_followee}/#{encoded_follower}"),
|
||||||
"type" => "Follow",
|
"type" => "Follow",
|
||||||
"actor" => Postland.my_actor_id(),
|
"actor" => Postland.my_actor_id(),
|
||||||
"object" => to_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)
|
Req.post(inbox, headers: headers, body: follow_request)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(follower, followee) do
|
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
|
end
|
||||||
|
|
||||||
def pending_inbound_requests() do
|
def pending_inbound_requests() do
|
||||||
|
|
@ -109,5 +145,9 @@ defmodule Postland.Follows do
|
||||||
request
|
request
|
||||||
|> Follow.confirm_changeset()
|
|> Follow.confirm_changeset()
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
|
|> case do
|
||||||
|
{:ok, follow} ->
|
||||||
|
Phoenix.PubSub.broadcast(Postland.PubSub, "follows:#{follow.followee}", {:update, follow})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ defmodule PostlandWeb.ExportController do
|
||||||
files = [filename, "#{filename}-shm", "#{filename}-wal"] |> Enum.map(&String.to_charlist/1)
|
files = [filename, "#{filename}-shm", "#{filename}-wal"] |> Enum.map(&String.to_charlist/1)
|
||||||
zip_path = Path.join([File.cwd!(), "priv", "tmp", "db-export.zip"])
|
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")
|
send_download(conn, {:file, zip_path}, filename: "db-export.zip")
|
||||||
end
|
end
|
||||||
|
|
|
||||||
77
lib/postland_web/live/other_profile_live.ex
Normal file
77
lib/postland_web/live/other_profile_live.ex
Normal 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
|
||||||
|
|
@ -80,6 +80,7 @@ defmodule PostlandWeb.Router do
|
||||||
live_session :require_authenticated_user,
|
live_session :require_authenticated_user,
|
||||||
on_mount: [{PostlandWeb.UserAuth, :ensure_authenticated}] do
|
on_mount: [{PostlandWeb.UserAuth, :ensure_authenticated}] do
|
||||||
live "/users/settings", UserSettingsLive, :edit
|
live "/users/settings", UserSettingsLive, :edit
|
||||||
|
live "/@:acct", OtherProfileLive, :show
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue