Add following remote users
This commit is contained in:
parent
8becb6b174
commit
e194f0fc07
|
@ -35,7 +35,7 @@ input, button:not(.btn-link) {
|
||||||
height: 1.75rem;
|
height: 1.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
button[type=submit]:not(.btn-link) {
|
button:not(.btn-link) {
|
||||||
padding: 0 2rem;
|
padding: 0 2rem;
|
||||||
font-family: $sans-serif;
|
font-family: $sans-serif;
|
||||||
background-color: $tint-color;
|
background-color: $tint-color;
|
||||||
|
@ -186,3 +186,7 @@ ul.status-list {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.actor-actions {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
|
@ -110,6 +110,28 @@ defmodule Clacks.ActivityPub do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec follow(
|
||||||
|
actor :: String.t(),
|
||||||
|
followee :: String.t(),
|
||||||
|
published :: NaiveDateTime.t(),
|
||||||
|
state :: String.t()
|
||||||
|
) ::
|
||||||
|
map()
|
||||||
|
def follow(actor, followee, published \\ DateTime.utc_now(), state \\ "pending") do
|
||||||
|
%{
|
||||||
|
"@context" => @context,
|
||||||
|
"type" => "Follow",
|
||||||
|
"id" => activity_id(Ecto.UUID.generate()),
|
||||||
|
"actor" => actor,
|
||||||
|
"object" => followee,
|
||||||
|
"context" => context_id(Ecto.UUID.generate()),
|
||||||
|
"to" => [followee],
|
||||||
|
"cc" => [@public],
|
||||||
|
"published" => published |> DateTime.to_iso8601(),
|
||||||
|
"state" => state
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
@spec object_id(id :: String.t()) :: String.t()
|
@spec object_id(id :: String.t()) :: String.t()
|
||||||
def object_id(id) do
|
def object_id(id) do
|
||||||
url = Application.get_env(:clacks, ClacksWeb.Endpoint)[:url]
|
url = Application.get_env(:clacks, ClacksWeb.Endpoint)[:url]
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
defmodule Clacks.ActivityPub.Helper do
|
||||||
|
alias Clacks.{Activity, Actor, Repo}
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@spec save_and_federate(activity_data :: map(), actor :: Actor.t()) ::
|
||||||
|
{:ok, Activity.t()} | :error
|
||||||
|
def save_and_federate(activity_data, actor) do
|
||||||
|
changeset = Activity.changeset_for_creating(activity_data, true)
|
||||||
|
|
||||||
|
case Repo.insert(changeset) do
|
||||||
|
{:error, changeset} ->
|
||||||
|
Logger.error("Couldn't save activity: #{inspect(changeset)}")
|
||||||
|
:error
|
||||||
|
|
||||||
|
{:ok, activity} ->
|
||||||
|
worker =
|
||||||
|
%{id: activity.id, actor_id: actor.id}
|
||||||
|
|> Clacks.Worker.Federate.new()
|
||||||
|
|
||||||
|
case Oban.insert(worker) do
|
||||||
|
{:ok, _} ->
|
||||||
|
{:ok, activity}
|
||||||
|
|
||||||
|
{:error, changeset} ->
|
||||||
|
Logger.error("Couldn't save federate job: #{inspect(changeset)}")
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,15 +2,16 @@ defmodule Clacks.Inbox do
|
||||||
require Logger
|
require Logger
|
||||||
alias Clacks.{Repo, Activity, Object, Actor, ActivityPub}
|
alias Clacks.{Repo, Activity, Object, Actor, ActivityPub}
|
||||||
|
|
||||||
defp store_activity(%{"actor" => actor} = activity, local \\ false) when is_binary(actor) do
|
defp store_activity(%{"actor" => actor, "id" => ap_id} = activity, local \\ false)
|
||||||
|
when is_binary(actor) do
|
||||||
changeset =
|
changeset =
|
||||||
Activity.changeset(%Activity{}, %{
|
Activity.changeset(Activity.get_cached_by_ap_id(ap_id) || %Activity{}, %{
|
||||||
data: activity,
|
data: activity,
|
||||||
local: local,
|
local: local,
|
||||||
actor: actor
|
actor: actor
|
||||||
})
|
})
|
||||||
|
|
||||||
Repo.insert(changeset)
|
Repo.insert_or_update(changeset)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec handle(activity :: map()) :: :ok | {:error, reason :: any()}
|
@spec handle(activity :: map()) :: :ok | {:error, reason :: any()}
|
||||||
|
@ -47,8 +48,8 @@ defmodule Clacks.Inbox do
|
||||||
|
|
||||||
case Repo.update(changeset) do
|
case Repo.update(changeset) do
|
||||||
{:error, changeset} ->
|
{:error, changeset} ->
|
||||||
Logger.error("Couldn't store Follow activity: #{inspect(changeset)}")
|
Logger.error("Couldn't store updated followers: #{inspect(changeset)}")
|
||||||
{:error, "Couldn't store Follow activity"}
|
{:error, "Couldn't store updated followers"}
|
||||||
|
|
||||||
{:ok, _followed} ->
|
{:ok, _followed} ->
|
||||||
accept = ActivityPub.accept_follow(activity)
|
accept = ActivityPub.accept_follow(activity)
|
||||||
|
@ -64,6 +65,45 @@ defmodule Clacks.Inbox do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle(
|
||||||
|
%{
|
||||||
|
"type" => "Accept",
|
||||||
|
"actor" => followee_id,
|
||||||
|
"object" => %{"type" => "Follow", "id" => follow_activity_id, "actor" => follower_id}
|
||||||
|
} = activity
|
||||||
|
) do
|
||||||
|
followee = Actor.get_by_ap_id(followee_id)
|
||||||
|
follower = Actor.get_by_ap_id(follower_id)
|
||||||
|
|
||||||
|
store_activity(activity)
|
||||||
|
|
||||||
|
follow_activity = Activity.get_cached_by_ap_id(follow_activity_id)
|
||||||
|
|
||||||
|
changeset =
|
||||||
|
Activity.changeset(follow_activity, %{
|
||||||
|
data: %{follow_activity.data | "state" => "accepted"}
|
||||||
|
})
|
||||||
|
|
||||||
|
case Repo.update(changeset) do
|
||||||
|
{:error, changeset} ->
|
||||||
|
Logger.error("Couldn't store updated Follow activity: #{inspect(changeset)}")
|
||||||
|
{:error, "Couldn't store updated Follow activity"}
|
||||||
|
|
||||||
|
{:ok, _follow_activity} ->
|
||||||
|
new_followers = [follower_id | followee.followers] |> Enum.uniq()
|
||||||
|
changeset = Actor.changeset(followee, %{followers: new_followers})
|
||||||
|
|
||||||
|
case Repo.update(changeset) do
|
||||||
|
{:error, changeset} ->
|
||||||
|
Logger.error("Couldn't store updated followers: #{inspect(changeset)}")
|
||||||
|
{:error, "Couldn't store updated followers"}
|
||||||
|
|
||||||
|
{:ok, _followee} ->
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# as a fallback, just store the activity
|
# as a fallback, just store the activity
|
||||||
def handle(activity) do
|
def handle(activity) do
|
||||||
case store_activity(activity) do
|
case store_activity(activity) do
|
||||||
|
|
|
@ -3,6 +3,7 @@ defmodule ClacksWeb.FrontendController do
|
||||||
alias Clacks.{Actor, User, Timeline, Repo, ActivityPub, Activity, Object}
|
alias Clacks.{Actor, User, Timeline, Repo, ActivityPub, Activity, Object}
|
||||||
alias ClacksWeb.Router.Helpers, as: Routes
|
alias ClacksWeb.Router.Helpers, as: Routes
|
||||||
alias ClacksWeb.Endpoint
|
alias ClacksWeb.Endpoint
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
@public "https://www.w3.org/ns/activitystreams#Public"
|
@public "https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
|
||||||
|
@ -129,7 +130,8 @@ defmodule ClacksWeb.FrontendController do
|
||||||
render(conn, "profile.html", %{
|
render(conn, "profile.html", %{
|
||||||
current_user: current_user,
|
current_user: current_user,
|
||||||
actor: user.actor,
|
actor: user.actor,
|
||||||
statuses: actor_statuses(user.actor, params, only_public: true)
|
statuses: actor_statuses(user.actor, params, only_public: true),
|
||||||
|
following_state: following_state(current_user.actor, user.actor)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -145,11 +147,33 @@ defmodule ClacksWeb.FrontendController do
|
||||||
render(conn, "profile.html", %{
|
render(conn, "profile.html", %{
|
||||||
current_user: current_user,
|
current_user: current_user,
|
||||||
actor: actor,
|
actor: actor,
|
||||||
statuses: actor_statuses(actor, params, only_public: true)
|
statuses: actor_statuses(actor, params, only_public: true),
|
||||||
|
following_state: following_state(current_user.actor, actor)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def follow(conn, %{"id" => id}) do
|
||||||
|
current_user = conn.assigns[:user] |> Repo.preload(:actor)
|
||||||
|
|
||||||
|
case Repo.get(Actor, id) do
|
||||||
|
nil ->
|
||||||
|
resp(conn, 404, "Not Found")
|
||||||
|
|
||||||
|
followee ->
|
||||||
|
if current_user.actor.ap_id in followee.followers do
|
||||||
|
redirect(conn, to: ClacksWeb.FrontendView.local_actor_link(followee))
|
||||||
|
else
|
||||||
|
follow = ActivityPub.follow(current_user.actor.ap_id, followee.ap_id)
|
||||||
|
ActivityPub.Helper.save_and_federate(follow, current_user.actor)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Follow request sent")
|
||||||
|
|> redirect(to: ClacksWeb.FrontendView.local_actor_link(followee))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def search(conn, %{"q" => q}) when is_binary(q) do
|
def search(conn, %{"q" => q}) when is_binary(q) do
|
||||||
current_user = conn.assigns[:user]
|
current_user = conn.assigns[:user]
|
||||||
|
|
||||||
|
@ -201,12 +225,7 @@ defmodule ClacksWeb.FrontendController do
|
||||||
{:ok, object} = Repo.insert(note_changeset)
|
{:ok, object} = Repo.insert(note_changeset)
|
||||||
|
|
||||||
create = ActivityPub.create(note)
|
create = ActivityPub.create(note)
|
||||||
create_changeset = Activity.changeset_for_creating(create, true)
|
{:ok, activity} = ActivityPub.Helper.save_and_federate(create, current_user.actor)
|
||||||
{:ok, activity} = Repo.insert(create_changeset)
|
|
||||||
|
|
||||||
%{id: activity.id, actor_id: current_user.actor.id}
|
|
||||||
|> Clacks.Worker.Federate.new()
|
|
||||||
|> Oban.insert()
|
|
||||||
|
|
||||||
path = Map.get(params, "continue", Routes.frontend_path(Endpoint, :status, activity.id))
|
path = Map.get(params, "continue", Routes.frontend_path(Endpoint, :status, activity.id))
|
||||||
redirect(conn, to: path)
|
redirect(conn, to: path)
|
||||||
|
@ -238,4 +257,24 @@ defmodule ClacksWeb.FrontendController do
|
||||||
defp note_for_posting(current_user, %{"content" => content}) do
|
defp note_for_posting(current_user, %{"content" => content}) do
|
||||||
ActivityPub.note(current_user.actor.ap_id, content)
|
ActivityPub.note(current_user.actor.ap_id, content)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec following_state(follower :: Actor.t(), followee :: Actor.t()) :: boolean()
|
||||||
|
defp following_state(follower, followee) do
|
||||||
|
query =
|
||||||
|
Activity
|
||||||
|
|> where([a], fragment("?->>'type'", a.data) == "Follow")
|
||||||
|
|> where([a], fragment("?->>'actor'", a.data) == ^follower.ap_id)
|
||||||
|
|> where([a], fragment("?->>'object'", a.data) == ^followee.ap_id)
|
||||||
|
|
||||||
|
case Repo.one(query) do
|
||||||
|
nil ->
|
||||||
|
:not_following
|
||||||
|
|
||||||
|
%Activity{data: %{"state" => "pending"}} ->
|
||||||
|
:pending
|
||||||
|
|
||||||
|
%Activity{data: %{"state" => "accepted"}} ->
|
||||||
|
:following
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -61,6 +61,8 @@ defmodule ClacksWeb.Router do
|
||||||
get "/status/:id/reply", FrontendController, :reply
|
get "/status/:id/reply", FrontendController, :reply
|
||||||
get "/search", FrontendController, :search
|
get "/search", FrontendController, :search
|
||||||
get "/actors/:id", FrontendController, :actor
|
get "/actors/:id", FrontendController, :actor
|
||||||
|
post "/actors/:id/follow", FrontendController, :follow
|
||||||
|
post "/actors/:id/unfollow", FrontendController, :unfollow
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", ClacksWeb do
|
scope "/", ClacksWeb do
|
||||||
|
|
|
@ -10,4 +10,26 @@
|
||||||
</h2>
|
</h2>
|
||||||
<p><%= @actor.data["summary"] %></p>
|
<p><%= @actor.data["summary"] %></p>
|
||||||
|
|
||||||
|
<div class="actor-actions">
|
||||||
|
<%= unless @current_user.actor.ap_id == @actor.ap_id do %>
|
||||||
|
|
||||||
|
<%= case @following_state do %>
|
||||||
|
<% :not_following -> %>
|
||||||
|
<%= form_tag Routes.frontend_path(@conn, :follow, @actor.id), method: :post, class: "follow-form" do %>
|
||||||
|
<%= submit "Follow" %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% :following -> %>
|
||||||
|
<%= form_tag Routes.frontend_path(@conn, :unfollow, @actor.id), method: :post, class: "follow-form" do %>
|
||||||
|
<%= submit "Unfollow" %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% :pending -> %>
|
||||||
|
<!-- todo: cancel follow request -->
|
||||||
|
<button type="button">Pending</button>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
<%= render "_timeline.html", conn: @conn, statuses_with_authors: Enum.map(@statuses, &({&1, @actor})) %>
|
<%= render "_timeline.html", conn: @conn, statuses_with_authors: Enum.map(@statuses, &({&1, @actor})) %>
|
||||||
|
|
Loading…
Reference in New Issue