feat: Withdraw follow request

This commit is contained in:
Ro 2024-11-03 10:17:42 -06:00
parent f27dd6a9a7
commit 75421b7446
Signed by: ro
GPG key ID: 5B5AD5A568CDABF9
6 changed files with 129 additions and 28 deletions

View file

@ -19,7 +19,7 @@
- [x] Sending follow request
- [x] View following list
- [ ] Withdrawing follow request
- [x] Withdrawing follow request
- [ ] Unfollowing
- [ ] Proactively check the outbox of newly-accepted follows
@ -45,12 +45,23 @@
- [ ] Displaying polls
- [ ] Voting in polls
## Individual Post Page
- [ ] Show post
- [ ] Show replies to post (chronological order)
## DMs
- [ ] Receiving DMs
- [ ] Replying to DMs
- [ ] Sending new DMs
## Notifications
- [ ] Like notifications
- [ ] Reply notifications
- [ ] Boost notifications
## Allowlist
- [ ] Manage approved instance list

View file

@ -1,5 +1,6 @@
defmodule Postland.Follow do
use Ecto.Schema
use Phoenix.VerifiedRoutes, endpoint: PostlandWeb.Endpoint, router: PostlandWeb.Router
import Ecto.Changeset
@ -10,11 +11,18 @@ defmodule Postland.Follow do
field :confirmed_at, :naive_datetime
end
def id(follow) do
encoded_followee = Base.url_encode64(follow.followee, padding: false)
encoded_follower = Base.url_encode64(follow.follower, padding: false)
url(~p"/follows/#{encoded_followee}/#{encoded_follower}")
end
def changeset(follower, followee, confirmed \\ false) do
attrs = %{
follower: follower,
followee: followee,
confirmed_at: (if confirmed, do: NaiveDateTime.utc_now())
confirmed_at: if(confirmed, do: NaiveDateTime.utc_now())
}
%__MODULE__{}

View file

@ -49,6 +49,40 @@ defmodule Postland.Follows do
|> Repo.transaction()
end
def record_and_send_withdrawl(request) do
Multi.new()
|> Multi.delete(:delete, request)
|> Multi.run(:send_acceptance, fn _data, _repo ->
send_withdrawl(request)
end)
|> Repo.transaction()
end
def send_withdrawl(request) do
{:ok, actor} = ActivityPub.fetch_actor(request.followee)
inbox = Map.get(actor, "inbox")
body =
%{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Undo",
"actor" => Postland.my_actor_id(),
"object" => Follow.id(request)
}
|> 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
def send_acceptance(request) do
{:ok, actor} = ActivityPub.fetch_actor(request.follower)
inbox = Map.get(actor, "inbox")

View file

@ -44,12 +44,22 @@ defmodule PostlandWeb.CoreComponents do
@<%= @account["preferredUsername"] %>@<%= @host %>
</span>
</p>
<p :if={@status == :following_pending} class="font-semibold text-violet-600">
Pending follow request.
</p>
<p class="text-gray-500 text-sm">
<%= {:safe, Earmark.as_html!(@account["summary"] || "")} %>
</p>
</div>
<div class="flex-grow text-right">
<.button disabled>Pending</.button>
<.button
:if={@status == :following_pending}
phx-click="withdraw"
phx-value-dom-id={@dom_id}
phx-value-id={@account["id"]}
>
Withdraw Request
</.button>
</div>
</div>
</div>

View file

@ -1,25 +0,0 @@
defmodule PostlandWeb.FollowingLive do
use PostlandWeb, :live_view
alias Postland.Actors
alias Postland.Follows
def render(assigns) do
~H"""
<div class="py-4">
<h3 class="text-base font-semibold">Following</h3>
<p :if={@accounts == []} class="text-gray-400">
You aren't following anyone.
</p>
<div :for={acct <- @accounts} class="mt-2">
<.profile_card account={acct} />
</div>
</div>
"""
end
def mount(_params, _session, socket) do
follows = Follows.all_following() |> Enum.map(fn follow -> Actors.actor(follow.followee) end)
{:ok, assign(socket, :accounts, follows)}
end
end

View file

@ -0,0 +1,63 @@
defmodule PostlandWeb.FollowingLive do
use PostlandWeb, :live_view
alias Postland.Actors
alias Postland.Follows
def render(assigns) do
~H"""
<div class="py-4">
<h3 class="text-base font-semibold">Following</h3>
<p :if={@count == 0} class="text-gray-400">
You aren't following anyone.
</p>
<div :for={{dom_id, %{confirmed: confirmed, account: acct}} <- @streams.accounts} class="mt-2">
<.profile_card
id={dom_id}
account={acct}
dom_id={dom_id}
status={if confirmed, do: :following_confirmed, else: :following_pending}
/>
</div>
</div>
"""
end
def mount(_params, _session, socket) do
follows =
Follows.all_following()
|> Enum.map(fn follow ->
%{
id: follow.followee,
confirmed: !!follow.confirmed_at,
account: Actors.actor(follow.followee)
}
end)
account_count = Enum.count(follows)
socket =
socket
|> stream(:accounts, follows)
|> assign(:count, account_count)
{:ok, socket}
end
def handle_event("withdraw", %{"id" => id, "dom-id" => dom_id}, socket) do
request = Follows.get(Postland.my_actor_id(), id)
socket =
case Follows.record_and_send_withdrawl(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