feat: Support unfollowing

This commit is contained in:
Ro 2024-11-26 18:04:08 -06:00
parent dc7c855095
commit e89cc78274
Signed by: ro
GPG key ID: 5B5AD5A568CDABF9
8 changed files with 97 additions and 9 deletions

View file

@ -73,7 +73,7 @@
## Protocol Support
- [x] Check that signature header (digest) matches digest of body contents
- [ ] Check the domain of the public key against the domain of the object being CRUDed
- [x] Check the domain of the public key against the domain of the object being CRUDed
## Testing

View file

@ -38,9 +38,12 @@ defmodule ActivityPub do
|> Req.Request.put_header("accept", "application/json")
case Req.get(request) do
{:ok, result} ->
{:ok, %{status: 200} = result} ->
{:ok, result.body}
{:ok, %{status: 404}} ->
nil
error ->
error
end

View file

@ -77,6 +77,9 @@ defmodule ActivityPub.Headers do
{:ok, public_key} ->
:public_key.verify(to_verify, :sha256, signature, public_key)
nil ->
false
error ->
error
end

View file

@ -6,9 +6,13 @@ defmodule Postland.Actors do
def actor(actor_id) do
result =
Cachex.fetch(:main_cache, "actor:#{actor_id}", fn _key ->
{:ok, actor} = ActivityPub.fetch_actor(actor_id)
case ActivityPub.fetch_actor(actor_id) do
{:ok, actor} ->
{:commit, actor, expire: :timer.seconds(300)}
{:commit, actor, expire: :timer.seconds(300)}
nil ->
{:commit, nil, expire: :timer.seconds(300)}
end
end)
case result do

View file

@ -52,14 +52,30 @@ defmodule Postland.Follows do
def record_and_send_withdrawl(request) do
Multi.new()
|> Multi.delete(:delete, request)
|> Multi.run(:send_acceptance, fn _data, _repo ->
|> 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
{:ok, actor} = ActivityPub.fetch_actor(request.followee)
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 =
@ -84,7 +100,14 @@ defmodule Postland.Follows do
end
def send_acceptance(request) do
{:ok, actor} = ActivityPub.fetch_actor(request.follower)
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
@ -180,6 +203,34 @@ defmodule Postland.Follows do
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()

View file

@ -60,6 +60,14 @@ defmodule PostlandWeb.CoreComponents do
>
Withdraw Request
</.button>
<.button
:if={@status == :following_confirmed}
phx-click="unfollow"
phx-value-dom-id={@dom_id}
phx-value-id={@account["id"]}
>
Unfollow
</.button>
</div>
</div>
</div>
@ -177,7 +185,7 @@ defmodule PostlandWeb.CoreComponents do
end
def post_card(assigns) do
author = Postland.Timeline.attribution(assigns.post) || %{}
author = Postland.Timeline.attribution(assigns.post)
%{host: host} =
case author do
@ -201,7 +209,7 @@ defmodule PostlandWeb.CoreComponents do
|> Map.put(:cw, cw)
~H"""
<div class="flex items-start space-x-4 w-full">
<div :if={@author} class="flex items-start space-x-4 w-full">
<div class="flex-shrink-0">
<img
class="inline-block h-16 w-16 rounded-full drop-shadow-lg"

View file

@ -36,6 +36,7 @@ defmodule PostlandWeb.FollowersLive do
account: Actors.actor(follow.follower)
}
end)
|> Enum.reject(&is_nil(&1.account))
account_count = Enum.count(followers)

View file

@ -33,6 +33,7 @@ defmodule PostlandWeb.FollowingLive do
account: Actors.actor(follow.followee)
}
end)
|> Enum.reject(&is_nil(&1.account))
account_count = Enum.count(follows)
@ -60,4 +61,21 @@ defmodule PostlandWeb.FollowingLive do
{:noreply, socket}
end
def handle_event("unfollow", %{"id" => id, "dom-id" => dom_id}, socket) do
request = Follows.get(Postland.my_actor_id(), id)
socket =
case Follows.record_and_unfollow(request) do
{:ok, _} ->
socket
|> stream_delete_by_dom_id(:accounts, dom_id)
|> assign(:count, socket.assigns.count - 1)
_ ->
put_flash(socket, :error, "An unexpected error occurred.")
end
{:noreply, socket}
end
end