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
|
||||
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
|
||||
|
|
|
|||
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!()
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
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,
|
||||
on_mount: [{PostlandWeb.UserAuth, :ensure_authenticated}] do
|
||||
live "/users/settings", UserSettingsLive, :edit
|
||||
live "/@:acct", OtherProfileLive, :show
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue