Select the types of activity you want to include in your feed.
You can now actually create publications
Also added some conveniences around development workflow, loosened the restriction on multiple "sites" using the same URL, and added a skip verification button (dev only).
···2233A service for automatically publishing content to the Atmosphere, using the standard.site lexicon. Register your publication and a discovery mechanism, and your content is automatically published.
4455+Requires your site to have a `link rel=alternate` for a feed set, and that you're able to verify your ownership by serving a `/.well-known` file.
66+57## TODO
6879- [x] Sign up/sign in with Bluesky
+3
config/config.exs
···8282 tag: "AES.GCM.V1", key: Base.decode64!("8h8cn7yiPKyVKiCURGpw7/lUPqxEOahH2C+0CdGQIbI=")}
8383 ]
84848585+config :annot_at,
8686+ dev_routes: false
8787+8588# Import environment specific config. This must remain at the bottom
8689# of this file so it overrides the configuration defined above.
8790import_config "#{config_env()}.exs"
+3-3
lib/annot_at/atproto/standard_site.ex
···1717 @doc """
1818 Creates or updates the user's publication record.
1919 """
2020- @spec put_publication(integer(), Publication.t()) :: {:ok, map()} | {:error, term()}
2121- def put_publication(user_id, %Publication{} = publication) do
2020+ @spec put_publication(integer(), String.t(), Publication.t()) :: {:ok, map()} | {:error, term()}
2121+ def put_publication(user_id, rkey, %Publication{} = publication) do
2222 with {:ok, user} <- fetch_user(user_id),
2323 {:ok, icon} <- upload_image(user_id, publication.icon) do
2424- put_record(user, @publication, "self", publication_record(publication, icon))
2424+ put_record(user, @publication, rkey, publication_record(publication, icon))
2525 end
2626 end
2727
+11-9
lib/annot_at/publishing.ex
···1818 Repo.all(from s in Site, where: s.user_id == ^user_id, order_by: [desc: s.inserted_at])
1919 end
20202121+ def list_sites_for_url(%Scope{user: %User{id: user_id}}, url) do
2222+ Repo.all(
2323+ from s in Site,
2424+ where: s.user_id == ^user_id and s.url == ^url,
2525+ order_by: [desc: s.inserted_at]
2626+ )
2727+ end
2828+2129 def get_site!(%Scope{user: %User{id: user_id}}, id) do
2230 Repo.get_by!(Site, id: id, user_id: user_id)
2331 end
24322533 def create_site(%Scope{user: %User{id: user_id}}, url) do
2626- case Repo.get_by(Site, user_id: user_id, url: url) do
2727- nil ->
2828- %Site{user_id: user_id}
2929- |> Site.changeset(%{url: url})
3030- |> Repo.insert()
3131-3232- %Site{} = site ->
3333- {:ok, site}
3434- end
3434+ %Site{user_id: user_id}
3535+ |> Site.changeset(%{url: url})
3636+ |> Repo.insert()
3537 end
36383739 def use_new_publication(%Scope{user: %User{id: user_id}}, %Site{} = site) do
···11+defmodule AnnotAt.Repo.Migrations.AllowDuplicateSiteUrls do
22+ use Ecto.Migration
33+44+ def change do
55+ drop unique_index(:sites, [:user_id, :url])
66+ create index(:sites, [:user_id])
77+ end
88+end
+5-5
test/annot_at/atproto/standard_site_test.exs
···2020 assert user.id == user_id
2121 assert @did == body.repo
2222 assert "site.standard.publication" == body.collection
2323- assert "self" == body.rkey
2323+ assert "abc123" == body.rkey
2424 assert "site.standard.publication" == body.record["$type"]
2525 assert "jola.dev" == body.record["name"]
2626 {:ok, %{"uri" => "at://x"}}
2727 end)
28282929 pub = %Publication{name: "jola.dev", url: "https://jola.dev", description: "blog"}
3030- assert {:ok, %{"uri" => "at://x"}} = StandardSite.put_publication(user.id, pub)
3030+ assert {:ok, %{"uri" => "at://x"}} = StandardSite.put_publication(user.id, "abc123", pub)
3131 end
32323333 test "returns :no_session when the user does not exist" do
3434 reject(&Client.procedure/3)
35353636 assert {:error, :no_session} =
3737- StandardSite.put_publication(-1, %Publication{name: "x", url: "y"})
3737+ StandardSite.put_publication(-1, "abc123", %Publication{name: "x", url: "y"})
3838 end
39394040 test "uploads the icon and embeds the returned blob" do
···6363 icon: {<<1, 2, 3>>, "image/png"}
6464 }
65656666- assert {:ok, %{"uri" => "at://x"}} = StandardSite.put_publication(user.id, pub)
6666+ assert {:ok, %{"uri" => "at://x"}} = StandardSite.put_publication(user.id, "abc123", pub)
6767 end
68686969 test "omits optional fields that are nil" do
···7676 end)
77777878 assert {:ok, %{}} =
7979- StandardSite.put_publication(user.id, %Publication{
7979+ StandardSite.put_publication(user.id, "abc123", %Publication{
8080 name: "n",
8181 url: "https://n.example"
8282 })
+6-5
test/annot_at/publishing_test.exs
···4141 assert "3mope7jyypk22" == updated.rkey
4242 end
43434444- test "create_site/2 resumes the same site for a url", %{scope: scope} do
4545- {:ok, first} = Publishing.create_site(scope, "https://example.com/")
4646- {:ok, again} = Publishing.create_site(scope, "https://example.com/")
4747- assert first.id == again.id
4848- assert first.rkey == again.rkey
4444+ test "list_sites_for_url/2 returns the scope's sites for that url", %{scope: scope} do
4545+ {:ok, _} = Publishing.create_site(scope, "https://example.com")
4646+ {:ok, _} = Publishing.create_site(scope, "https://other.com")
4747+4848+ assert [%Site{url: "https://example.com"}] =
4949+ Publishing.list_sites_for_url(scope, "https://example.com")
4950 end
50515152 test "create_site/2 keeps distinct sites per url", %{scope: scope} do