feat: Ingest posts from followers
This commit is contained in:
parent
a4af27e5b7
commit
87c5ce66cd
7 changed files with 145 additions and 16 deletions
|
|
@ -3,6 +3,8 @@ defmodule Postland.Activities do
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
alias Ecto.Multi
|
||||||
|
|
||||||
alias Postland.Activity
|
alias Postland.Activity
|
||||||
alias Postland.Repo
|
alias Postland.Repo
|
||||||
alias Postland.Follows
|
alias Postland.Follows
|
||||||
|
|
@ -38,7 +40,37 @@ defmodule Postland.Activities do
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|> Postland.Activity.changeset()
|
|> Postland.Activity.changeset()
|
||||||
|> Repo.insert()
|
|> record_create_activity()
|
||||||
|
|
||||||
|
# TODO: ActivityPub out this activity (only for our own posts obviously)
|
||||||
|
end
|
||||||
|
|
||||||
|
def record_create_activity(changeset) do
|
||||||
|
Multi.new()
|
||||||
|
|> Multi.insert(:activity, changeset)
|
||||||
|
|> record_objects(changeset)
|
||||||
|
|> Repo.transaction()
|
||||||
|
end
|
||||||
|
|
||||||
|
def record_objects(multi, activity_changeset) do
|
||||||
|
activity_changeset
|
||||||
|
|> Ecto.Changeset.get_field(:data)
|
||||||
|
|> Map.get("object")
|
||||||
|
|> List.wrap()
|
||||||
|
|> Enum.group_by(fn object ->
|
||||||
|
{Map.get(object, "id"), Map.get(object, "type")}
|
||||||
|
end)
|
||||||
|
|> Enum.reduce(multi, fn {{id, type}, note_as_list_of_types}, multi ->
|
||||||
|
Multi.insert(
|
||||||
|
multi,
|
||||||
|
"#{id}.#{type}",
|
||||||
|
Postland.Object.changeset(%{
|
||||||
|
"id" => id,
|
||||||
|
"type" => type,
|
||||||
|
"items" => note_as_list_of_types
|
||||||
|
})
|
||||||
|
)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_activity(params) do
|
def process_activity(params) do
|
||||||
|
|
@ -57,6 +89,8 @@ defmodule Postland.Activities do
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: add effects for CRUD notes
|
||||||
|
|
||||||
def cause_effects(
|
def cause_effects(
|
||||||
%Activity{type: "Accept", data: %{"object" => %{"type" => "Follow", "id" => follow_id}}} =
|
%Activity{type: "Accept", data: %{"object" => %{"type" => "Follow", "id" => follow_id}}} =
|
||||||
activity
|
activity
|
||||||
|
|
@ -85,6 +119,14 @@ defmodule Postland.Activities do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def cause_effects(%Activity{type: "Create"} = activity) do
|
||||||
|
changeset = Activity.changeset(activity.data)
|
||||||
|
|
||||||
|
Multi.new()
|
||||||
|
|> record_objects(changeset)
|
||||||
|
|> Repo.transaction()
|
||||||
|
end
|
||||||
|
|
||||||
def cause_effects(
|
def cause_effects(
|
||||||
%Activity{actor_id: actor_id, type: "Follow", data: %{"object" => object}} = activity
|
%Activity{actor_id: actor_id, type: "Follow", data: %{"object" => object}} = activity
|
||||||
) do
|
) do
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,20 @@ defmodule Postland.Follows do
|
||||||
|
|
||||||
alias Ecto.Multi
|
alias Ecto.Multi
|
||||||
|
|
||||||
|
def all_following() do
|
||||||
|
my_actor_id = Postland.my_actor_id()
|
||||||
|
|
||||||
|
from(f in Follow, where: f.follower == ^my_actor_id, where: not is_nil(f.confirmed_at))
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
|
def all_followers() do
|
||||||
|
my_actor_id = Postland.my_actor_id()
|
||||||
|
|
||||||
|
from(f in Follow, where: f.followee == ^my_actor_id, where: not is_nil(f.confirmed_at))
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
def record_and_send_acceptance(request) do
|
def record_and_send_acceptance(request) do
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> Multi.run(:confirm_timestamp, fn _data, _repo -> confirm_request(request) end)
|
|> Multi.run(:confirm_timestamp, fn _data, _repo -> confirm_request(request) end)
|
||||||
|
|
|
||||||
27
lib/postland/object.ex
Normal file
27
lib/postland/object.ex
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
defmodule Postland.Object do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key {:id, :binary_id, autogenerate: false}
|
||||||
|
schema "objects" do
|
||||||
|
field :type, :string
|
||||||
|
field :data, :map
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(attrs) do
|
||||||
|
attrs = %{
|
||||||
|
"id" => Map.get(attrs, "id", Ecto.UUID.autogenerate()),
|
||||||
|
"type" => Map.get(attrs, "type"),
|
||||||
|
"data" => attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
%__MODULE__{}
|
||||||
|
|> cast(attrs, [:id, :type, :data])
|
||||||
|
|> validate_required(:id)
|
||||||
|
|> validate_required(:data)
|
||||||
|
|> validate_required(:type)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,17 +1,25 @@
|
||||||
defmodule Postland.Timeline do
|
defmodule Postland.Timeline do
|
||||||
alias Postland.Activity
|
alias Postland.Object
|
||||||
alias Postland.Repo
|
alias Postland.Repo
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
def html_content(activity) do
|
def html_content(activity) do
|
||||||
case Map.get(activity.data, "object") do
|
get_from_html_item(activity, "content")
|
||||||
|
end
|
||||||
|
|
||||||
|
def attribution(activity) do
|
||||||
|
get_from_html_item(activity, "attributedTo")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_from_html_item(activity, field_name) do
|
||||||
|
case Map.get(activity.data, "items") do
|
||||||
map when is_map(map) ->
|
map when is_map(map) ->
|
||||||
Map.get(map, "content")
|
Map.get(map, field_name)
|
||||||
|
|
||||||
list when is_list(list) ->
|
list when is_list(list) ->
|
||||||
Enum.find_value(list, fn map ->
|
Enum.find_value(list, fn map ->
|
||||||
Map.get(map, "mediaType") == "text/html" && Map.get(map, "content")
|
Map.get(map, "mediaType", "text/html") == "text/html" && Map.get(map, field_name)
|
||||||
end) || ""
|
end) || ""
|
||||||
|
|
||||||
nil ->
|
nil ->
|
||||||
|
|
@ -19,10 +27,12 @@ defmodule Postland.Timeline do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def attribution(map) do
|
||||||
|
end
|
||||||
|
|
||||||
def timeline do
|
def timeline do
|
||||||
from(a in Activity, where: a.type == "Create", order_by: [desc: a.inserted_at])
|
from(a in Object, where: a.type == "Note", order_by: [desc: a.inserted_at])
|
||||||
# Only accounts I'm following + myself
|
# TODO: Only accounts I'm following + myself
|
||||||
# Only notes
|
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -66,13 +66,21 @@ defmodule PostlandWeb.CoreComponents do
|
||||||
|
|
||||||
def post_card(assigns) do
|
def post_card(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div class="divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow">
|
<div class="flex items-start space-x-4 w-full">
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="flex-shrink-0">
|
||||||
<%= {:safe, Postland.Timeline.html_content(@post)} %>
|
<img class="inline-block h-10 w-10 rounded-full" alt="" src="/images/avatar.png" />
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-4 sm:px-6">
|
<div class="divide-y flex-grow divide-gray-200 overflow-hidden rounded-lg bg-white shadow">
|
||||||
<!-- Content goes here -->
|
<div class="px-4 py-4 sm:px-6">
|
||||||
<!-- We use less vertical padding on card footers at all sizes than on headers or body sections -->
|
<%= Postland.Timeline.attribution(@post) %>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<%= {:safe, Postland.Timeline.html_content(@post)} %>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-4 sm:px-6">
|
||||||
|
<!-- Content goes here -->
|
||||||
|
<!-- We use less vertical padding on card footers at all sizes than on headers or body sections -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,24 @@ defmodule PostlandWeb.TimelineLive do
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("create_post", %{"post" => post}, socket) do
|
def handle_event("create_post", %{"post" => post}, socket) do
|
||||||
{:ok, post} = Postland.Activities.record_markdown_post(post)
|
{:ok, results} = Postland.Activities.record_markdown_post(post)
|
||||||
|
|
||||||
{:noreply, socket |> assign(:post, "") |> stream_insert(:posts, post, at: 0)}
|
new_posts =
|
||||||
|
results
|
||||||
|
|> Enum.filter(fn {_key, value} ->
|
||||||
|
is_struct(value, Postland.Object)
|
||||||
|
end)
|
||||||
|
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> assign(:post, "")
|
||||||
|
|> then(fn socket ->
|
||||||
|
Enum.reduce(new_posts, socket, fn {_id, post}, socket ->
|
||||||
|
stream_insert(socket, :posts, post, at: 0)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_event("change_post", %{"post" => post}, socket) do
|
def handle_event("change_post", %{"post" => post}, socket) do
|
||||||
|
|
|
||||||
13
priv/repo/migrations/20241021231131_add_objects.exs
Normal file
13
priv/repo/migrations/20241021231131_add_objects.exs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
defmodule Postland.Repo.Migrations.AddObjects do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table("objects", primary_key: false) do
|
||||||
|
add :id, :text, primary_key: true
|
||||||
|
add :type, :text, null: false
|
||||||
|
add :data, :map
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue