diff --git a/lib/postland/activities.ex b/lib/postland/activities.ex index 8589617..10056cb 100644 --- a/lib/postland/activities.ex +++ b/lib/postland/activities.ex @@ -3,6 +3,8 @@ defmodule Postland.Activities do require Logger + alias Ecto.Multi + alias Postland.Activity alias Postland.Repo alias Postland.Follows @@ -38,7 +40,37 @@ defmodule Postland.Activities do ] } |> 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 def process_activity(params) do @@ -57,6 +89,8 @@ defmodule Postland.Activities do |> Repo.insert() end + # TODO: add effects for CRUD notes + def cause_effects( %Activity{type: "Accept", data: %{"object" => %{"type" => "Follow", "id" => follow_id}}} = activity @@ -85,6 +119,14 @@ defmodule Postland.Activities do {:ok, activity} 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( %Activity{actor_id: actor_id, type: "Follow", data: %{"object" => object}} = activity ) do diff --git a/lib/postland/follows.ex b/lib/postland/follows.ex index 404539b..69563ce 100644 --- a/lib/postland/follows.ex +++ b/lib/postland/follows.ex @@ -11,6 +11,20 @@ defmodule Postland.Follows do 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 Multi.new() |> Multi.run(:confirm_timestamp, fn _data, _repo -> confirm_request(request) end) diff --git a/lib/postland/object.ex b/lib/postland/object.ex new file mode 100644 index 0000000..383301c --- /dev/null +++ b/lib/postland/object.ex @@ -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 diff --git a/lib/postland/timeline.ex b/lib/postland/timeline.ex index 0b565cf..775e6d6 100644 --- a/lib/postland/timeline.ex +++ b/lib/postland/timeline.ex @@ -1,17 +1,25 @@ defmodule Postland.Timeline do - alias Postland.Activity + alias Postland.Object alias Postland.Repo import Ecto.Query 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.get(map, "content") + Map.get(map, field_name) list when is_list(list) -> 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) || "" nil -> @@ -19,10 +27,12 @@ defmodule Postland.Timeline do end end + def attribution(map) do + end + def timeline do - from(a in Activity, where: a.type == "Create", order_by: [desc: a.inserted_at]) - # Only accounts I'm following + myself - # Only notes + from(a in Object, where: a.type == "Note", order_by: [desc: a.inserted_at]) + # TODO: Only accounts I'm following + myself |> Repo.all() end end diff --git a/lib/postland_web/components/core_components.ex b/lib/postland_web/components/core_components.ex index 1af7895..d045485 100644 --- a/lib/postland_web/components/core_components.ex +++ b/lib/postland_web/components/core_components.ex @@ -66,13 +66,21 @@ defmodule PostlandWeb.CoreComponents do def post_card(assigns) do ~H""" -
-
- <%= {:safe, Postland.Timeline.html_content(@post)} %> +
+
+
-
- - +
+
+ <%= Postland.Timeline.attribution(@post) %> +
+
+ <%= {:safe, Postland.Timeline.html_content(@post)} %> +
+
+ + +
""" diff --git a/lib/postland_web/live/timeline_live.ex b/lib/postland_web/live/timeline_live.ex index 2fe2938..57c4dc8 100644 --- a/lib/postland_web/live/timeline_live.ex +++ b/lib/postland_web/live/timeline_live.ex @@ -17,9 +17,24 @@ defmodule PostlandWeb.TimelineLive do end 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 def handle_event("change_post", %{"post" => post}, socket) do diff --git a/priv/repo/migrations/20241021231131_add_objects.exs b/priv/repo/migrations/20241021231131_add_objects.exs new file mode 100644 index 0000000..8778db2 --- /dev/null +++ b/priv/repo/migrations/20241021231131_add_objects.exs @@ -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