From 90a243845ed04e7da00259c31dffa48ef93d322b Mon Sep 17 00:00:00 2001
From: Ro
Date: Thu, 26 Dec 2024 21:03:37 -0600
Subject: [PATCH] feat: Reject requests
---
lib/activity_pub.ex | 59 +++++++++++------
lib/postland/activities.ex | 2 +-
lib/postland/follows.ex | 64 +++++++++++++++----
.../components/core_components.ex | 12 +++-
lib/postland_web/live/dev_live.ex | 28 ++++++++
lib/postland_web/live/followers_live.ex | 27 +++++++-
lib/postland_web/live/user_login_live.ex | 26 ++++----
lib/postland_web/router.ex | 5 ++
8 files changed, 175 insertions(+), 48 deletions(-)
create mode 100644 lib/postland_web/live/dev_live.ex
diff --git a/lib/activity_pub.ex b/lib/activity_pub.ex
index b6c3aa7..a0817e8 100644
--- a/lib/activity_pub.ex
+++ b/lib/activity_pub.ex
@@ -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
diff --git a/lib/postland/activities.ex b/lib/postland/activities.ex
index a3f2687..6c57ac4 100644
--- a/lib/postland/activities.ex
+++ b/lib/postland/activities.ex
@@ -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
diff --git a/lib/postland/follows.ex b/lib/postland/follows.ex
index 42b99d3..01d712f 100644
--- a/lib/postland/follows.ex
+++ b/lib/postland/follows.ex
@@ -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
diff --git a/lib/postland_web/components/core_components.ex b/lib/postland_web/components/core_components.ex
index 52cef1f..068d442 100644
--- a/lib/postland_web/components/core_components.ex
+++ b/lib/postland_web/components/core_components.ex
@@ -52,13 +52,21 @@ defmodule PostlandWeb.CoreComponents do
+ <.button
+ :if={@status == :follower_pending}
+ phx-click="reject"
+ phx-value-dom-id={@dom_id}
+ phx-value-id={@account["id"]}
+ >
+ Reject
+
<.button
:if={@status == :follower_pending}
phx-click="confirm"
phx-value-dom-id={@dom_id}
phx-value-id={@account["id"]}
>
- Accept Request
+ Accept
<.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}>
-
+
<%= render_slot(@inner_block, f) %>
<%= render_slot(action, f) %>
diff --git a/lib/postland_web/live/dev_live.ex b/lib/postland_web/live/dev_live.ex
new file mode 100644
index 0000000..e134225
--- /dev/null
+++ b/lib/postland_web/live/dev_live.ex
@@ -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
+ """
+ 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
diff --git a/lib/postland_web/live/followers_live.ex b/lib/postland_web/live/followers_live.ex
index 939a28d..a632c8c 100644
--- a/lib/postland_web/live/followers_live.ex
+++ b/lib/postland_web/live/followers_live.ex
@@ -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
diff --git a/lib/postland_web/live/user_login_live.ex b/lib/postland_web/live/user_login_live.ex
index d2791c9..fbe9304 100644
--- a/lib/postland_web/live/user_login_live.ex
+++ b/lib/postland_web/live/user_login_live.ex
@@ -8,19 +8,21 @@ defmodule PostlandWeb.UserLoginLive do
Log in to account
- <.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 />
+
+ <.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>
- <.button phx-disable-with="Logging in..." class="w-full">
- Log in →
-
-
-
+ <:actions>
+ <.input field={@form[:remember_me]} type="checkbox" label="Keep me logged in" />
+
+ <:actions>
+ <.button phx-disable-with="Logging in..." class="w-full">
+ Log in →
+
+
+
+
"""
end
diff --git a/lib/postland_web/router.ex b/lib/postland_web/router.ex
index 2dc9bb4..634328d 100644
--- a/lib/postland_web/router.ex
+++ b/lib/postland_web/router.ex
@@ -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