From 4c12500b267fc5377cac3844d76d9666c541edb4 Mon Sep 17 00:00:00 2001 From: Johanna Larsson Date: Sat, 27 Jun 2026 22:36:06 +0100 Subject: [PATCH] Adopt existing documents Now that we fetch the aturi from the page links we can "discover" the documents for them. For now we just assume it's correct and the user can choose to republish if they wish, but in the future we could also fix broken records and stuff. Neat. --- lib/annot_at/publishing.ex | 13 ++++++++++++ .../controllers/live/posts_live.ex | 20 +++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/annot_at/publishing.ex b/lib/annot_at/publishing.ex index 9b9bb47..f0c8b2d 100644 --- a/lib/annot_at/publishing.ex +++ b/lib/annot_at/publishing.ex @@ -10,6 +10,7 @@ defmodule AnnotAt.Publishing do alias AnnotAt.Accounts.Scope alias AnnotAt.Accounts.User alias AnnotAt.Atproto.TID + alias AnnotAt.Feeds.Entry alias AnnotAt.Publishing.Post alias AnnotAt.Publishing.Site alias AnnotAt.Repo @@ -98,6 +99,18 @@ defmodule AnnotAt.Publishing do |> Repo.update() end + def adopt(%Site{} = site, entries) do + tracked = MapSet.new(list_posts(site), & &1.guid) + + entries + |> Enum.filter(fn entry -> is_binary(entry.rkey) and entry.id not in tracked end) + |> Enum.each(fn entry -> + create_post(site, %{guid: entry.id, rkey: entry.rkey, content_hash: Entry.hash(entry)}) + end) + + Map.new(list_posts(site), &{&1.guid, &1}) + end + defp verify_user_ownership!(%Site{user_id: user_id}, user_id), do: :ok defp verify_user_ownership!(%Site{}, _user_id), do: raise(Ecto.NoResultsError, queryable: Site) end diff --git a/lib/annot_at_web/controllers/live/posts_live.ex b/lib/annot_at_web/controllers/live/posts_live.ex index 611e517..66d6914 100644 --- a/lib/annot_at_web/controllers/live/posts_live.ex +++ b/lib/annot_at_web/controllers/live/posts_live.ex @@ -307,14 +307,30 @@ defmodule AnnotAtWeb.PostsLive do {:noreply, socket} end + def handle_async(:load_feed, {:ok, {:ok, %{feed: feed, posts: posts}}}, socket) do + {:noreply, + assign(socket, + feed: AsyncResult.ok(socket.assigns.feed, feed), + posts: posts + )} + end + + def handle_async(:load_feed, {:ok, reason}, socket) do + Logger.warning("PostsLive: failed to load feed", reason: inspect(reason)) + {:noreply, assign(socket, feed: AsyncResult.failed(socket.assigns.feed, reason))} + end + defp load_feed(socket, site) do if connected?(socket) do user_did = socket.assigns.current_scope.user.did - assign_async(socket, :feed, fn -> + socket + |> assign(feed: AsyncResult.loading()) + |> start_async(:load_feed, fn -> with {:ok, feed} <- Client.load(site.feed_url) do feed = Client.resolve_documents(feed, user_did) - {:ok, %{feed: feed}} + posts = Publishing.adopt(site, feed.entries) + {:ok, %{feed: feed, posts: posts}} end end) else -- 2.51.2