feat: Reject requests

This commit is contained in:
Ro 2024-12-26 21:03:37 -06:00
parent ac462cfe14
commit 90a243845e
Signed by: ro
GPG key ID: 5B5AD5A568CDABF9
8 changed files with 175 additions and 48 deletions

View file

@ -18,34 +18,53 @@ defmodule ActivityPub do
end
def post_activity(sender_id, private_key, inbox, activity) do
body = Jason.encode!(activity)
if String.contains?(inbox, "example.com") do
{:ok, %{status: 200, body: "ok"}}
else
body = Jason.encode!(activity)
headers =
Headers.signing_headers(
"POST",
inbox,
body,
sender_id,
private_key
)
headers =
Headers.signing_headers(
"POST",
inbox,
body,
sender_id,
private_key
)
Req.post(inbox, headers: headers, body: body)
Req.post(inbox, headers: headers, body: body)
end
end
def fetch_actor(actor_id) do
request =
Req.new(url: actor_id)
|> Req.Request.put_header("accept", "application/json")
if String.contains?(actor_id, "example.com") do
{:ok,
%{
"@context" => [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1"
],
"id" => actor_id,
"type" => "Person",
"preferredUsername" => "example",
"name" => "Example User",
"inbox" => "#{actor_id}/inbox"
}}
else
request =
Req.new(url: actor_id)
|> Req.Request.put_header("accept", "application/json")
case Req.get(request) do
{:ok, %{status: 200} = result} ->
{:ok, result.body}
case Req.get(request) do
{:ok, %{status: 200} = result} ->
{:ok, result.body}
{:ok, %{status: 404}} ->
nil
{:ok, %{status: 404}} ->
nil
error ->
error
error ->
error
end
end
end
end

View file

@ -187,7 +187,7 @@ defmodule Postland.Activities do
def record_activity(params) do
params
|> Activity.changeset()
|> Repo.insert()
|> Repo.insert(on_conflict: :replace_all, conflict_target: :id)
end
# TODO: add effects for CRUD notes

View file

@ -67,6 +67,15 @@ defmodule Postland.Follows do
|> Repo.transaction()
end
def record_and_reject(follow) do
Multi.new()
|> Multi.delete(:delete, follow)
|> Multi.run(:send_reject_follow, fn _data, _repo ->
send_reject(follow)
end)
|> Repo.transaction()
end
def send_withdrawl(request) do
request.followee
|> ActivityPub.fetch_actor()
@ -115,7 +124,7 @@ defmodule Postland.Follows do
{:error, "could not find Follow activity to Accept"}
follow_activity ->
body =
activity =
%{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Accept",
@ -127,18 +136,51 @@ defmodule Postland.Follows do
"type" => "Follow"
}
}
|> Jason.encode!()
headers =
Headers.signing_headers(
"POST",
inbox,
body,
Postland.my_actor_id(),
Accounts.solo_user().private_key
)
ActivityPub.post_activity(
Postland.my_actor_id(),
Accounts.solo_user().private_key,
inbox,
activity
)
end
end
Req.post(inbox, headers: headers, body: body)
def send_reject(request) do
request.follower
|> ActivityPub.fetch_actor()
|> do_send_reject(request)
end
defp do_send_reject(nil, _), do: {:ok, nil}
defp do_send_reject({:ok, actor}, request) do
inbox = Map.get(actor, "inbox")
case get_follow_activity(request.follower) do
nil ->
{:error, "could not find Follow activity to Reject"}
follow_activity ->
activity =
%{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Reject",
"actor" => Postland.my_actor_id(),
"object" => %{
"actor" => request.follower,
"id" => Map.get(follow_activity.data, "id"),
"object" => Postland.my_actor_id(),
"type" => "Follow"
}
}
ActivityPub.post_activity(
Postland.my_actor_id(),
Accounts.solo_user().private_key,
inbox,
activity
)
end
end

View file

@ -52,13 +52,21 @@ defmodule PostlandWeb.CoreComponents do
</p>
</div>
<div class="flex-grow text-right">
<.button
:if={@status == :follower_pending}
phx-click="reject"
phx-value-dom-id={@dom_id}
phx-value-id={@account["id"]}
>
Reject
</.button>
<.button
:if={@status == :follower_pending}
phx-click="confirm"
phx-value-dom-id={@dom_id}
phx-value-id={@account["id"]}
>
Accept Request
Accept
</.button>
<.button
:if={@status == :following_pending}
@ -484,7 +492,7 @@ defmodule PostlandWeb.CoreComponents do
def simple_form(assigns) do
~H"""
<.form :let={f} for={@for} as={@as} {@rest}>
<div class="mt-10 space-y-8 bg-white">
<div class="space-y-8 bg-white">
<%= render_slot(@inner_block, f) %>
<div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
<%= render_slot(action, f) %>

View file

@ -0,0 +1,28 @@
defmodule PostlandWeb.DevLive do
use PostlandWeb, :live_view
use Phoenix.VerifiedRoutes, endpoint: PostlandWeb.Endpoint, router: PostlandWeb.Router
alias Postland.Activities
def render(assigns) do
~H"""
<.button phx-click="make_follower_request">Make follower request</.button>
"""
end
def handle_event("make_follower_request", _, socket) do
mock_actor = "https://example.com/actors/test"
encoded_followee = Base.url_encode64(Postland.my_actor_id(), padding: false)
encoded_follower = Base.url_encode64(mock_actor, padding: false)
Activities.process_activity(%{
"@context" => "https://www.w3.org/ns/activitystreams",
"id" => url(~p"/follows/#{encoded_followee}/#{encoded_follower}"),
"type" => "Follow",
"actor" => mock_actor,
"object" => Postland.my_actor_id()
})
{:noreply, socket}
end
end

View file

@ -43,7 +43,7 @@ defmodule PostlandWeb.FollowersLive do
{:ok, socket |> stream(:accounts, followers) |> assign(count: account_count)}
end
def handle_event("confirm", %{"id" => id, "dom-id" => dom_id}, socket) do
def handle_event("confirm", %{"id" => id}, socket) do
request = Follows.get(id, Postland.my_actor_id())
socket =
@ -53,7 +53,7 @@ defmodule PostlandWeb.FollowersLive do
acct = %{
id: request.follower,
confirmed: !!request.onfirmed_at,
confirmed: !!request.confirmed_at,
account: Actors.actor(request.follower)
}
@ -66,4 +66,27 @@ defmodule PostlandWeb.FollowersLive do
{:noreply, socket}
end
def handle_event("reject", %{"id" => id}, socket) do
request = Follows.get(id, Postland.my_actor_id())
socket =
case Follows.record_and_reject(request) do
{:ok, _} ->
acct = %{
id: request.follower,
confirmed: true,
account: Actors.actor(request.follower)
}
socket
|> stream_delete(:accounts, acct)
|> assign(count: socket.assigns.count - 1)
_ ->
put_flash(socket, :error, "An unexpected error occurred.")
end
{:noreply, socket}
end
end

View file

@ -8,19 +8,21 @@ defmodule PostlandWeb.UserLoginLive do
Log in to account
</.header>
<.simple_form for={@form} id="login_form" action={~p"/users/log_in"} phx-update="ignore">
<.input field={@form[:username]} type="text" label="Username" required />
<.input field={@form[:password]} type="password" label="Password" required />
<div class="p-4 mt-10 bg-white rounded">
<.simple_form for={@form} id="login_form" action={~p"/users/log_in"} phx-update="ignore">
<.input field={@form[:username]} type="text" label="Username" required />
<.input field={@form[:password]} type="password" label="Password" required />
<:actions>
<.input field={@form[:remember_me]} type="checkbox" label="Keep me logged in" />
</:actions>
<:actions>
<.button phx-disable-with="Logging in..." class="w-full">
Log in <span aria-hidden="true"></span>
</.button>
</:actions>
</.simple_form>
<:actions>
<.input field={@form[:remember_me]} type="checkbox" label="Keep me logged in" />
</:actions>
<:actions>
<.button phx-disable-with="Logging in..." class="w-full">
Log in <span aria-hidden="true"></span>
</.button>
</:actions>
</.simple_form>
</div>
</div>
"""
end

View file

@ -58,6 +58,11 @@ defmodule PostlandWeb.Router do
pipe_through :browser
live_dashboard "/dashboard", metrics: PostlandWeb.Telemetry
live_session :mount_current_user_dev,
on_mount: [{PostlandWeb.UserAuth, :mount_current_user}] do
live "/mocks", PostlandWeb.DevLive
end
end
end