Commits
Using `npm ci` for a clean install
Not sure about the span stripping, feels a bit janky and not super performant.
Mostly for fun, but since we're rendering docs in the app, we can render any doc from public routes too!
Will need to add rate limits and stuff won't I.
If a document is using org.wordpress.html, like the ones we create, we can render them in-app. Haven't put much effort into the design, but it works!
Disconnecting, or deleting, a site removes our local state of the site without touching any publications or documents.
Also fixed the broken filter buttons on sites
Also puts your did on the bottom right. Just trying to make it clearer that this is your data, without me having to recreate a whole atproto viewer.
Using the new microvm engine to run postgres, with some postgres config to run over tcp instead of a unix socket to match the default Phoenix project setup.
The steps match `mix precommit` but with the flags that make them fail instead of just fixing, eg `mix format --check-formatted` instead of just `mix format`.
Using the org.wordpress.html type in the hopes that it's compatible with some existing apps.
Also fixes the missing tags from the actual publishing step
Unfortunately RSS does not have updated at really, but we can try to get it from atom:updated.
If tags change that's a trigger for resync
Nothing much to say here, basically the same as the RSS parser except the name sits as an attribute on the category
I realized I was passing the HTML string to textContent and it feels more accurate to extract the text instead.
Also, adds feed tag extraction so the standard.site documents get nice tags!
With this commit we now grab the OG image on the post and reupload it as a blob on publish, attaching it to the document.
Verified against my own post. Cool.
Follows the existing pattern from the RSS parser, just slight changes to match the Atom format. Tested on a single blog, looks like it works!
Can probably drop it again soon but it feels better to be clear about this
Uses typeahead from typeahead.waow.tech and fancy avatars and a combobox and stuff. Supports up and down arrow keys and whatever else you might want.
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.
Spec says authorization_servers has to be a single item.
Also avatar should be avatar_url
So turns out document rkeys having to be TIDs is really annoying for us. Because the blog owner has to add `<link rel="site.standard.document" href="<aturi>" /> with the correct aturi in it, and it contains the rkey, and the rkey has to be a TID, it means either we generate it and the blog owner then has to copy paste and redeploy their blog, or we use a deterministic TID implementation based on eg post publish date, which means they have to use the exact same one. We just can't generate the rkey for the user.
It would be much easier if standard.site allowed other rkeys, so that you could eg use the RSS feed post guid. That would make it trivial, blog owner uses their guid (eg slug) and we do the same.
Of course, all this led to me realizing that the cleanest way to build this is to rely on the blow owner to set the link aturi and we just use that. So the blog owner generates a TID when they post their blog post, eg deterministically from publishing date, but we don't have to match the exact implementation of the algorithm, or they just get a random one and store it on their side. They can deploy once and we discover the aturi from the post.
Problem solved!
This shaves off a few hundred milliseconds from logging in by avoiding fetching the validated token endpoint and issuer again unnecessarily at the end of the oauth flow.
Also adds a little spinner so the login page doesn't just sit there while we're working.
Posts page loaded with feed entries, can publish single or all, also republish existing.
Also some mixed cleanup and improvements.
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).
Feeling a big saturated with tailwind classes but it's ok I guess
Ticked off some milestones too. Tons left to do but it's shaping up nicely I think
This commit is way too big. It is a very nice milestone though, with a full integrated site adding flow/wizard.
You can now add sites, get RSS feed, validate well-known, and everything up to creating a publication. Still can use a lot of polish but it's working.
Also includes TID generation and lots of other improvements I bumped into while I was pulling everything together.
Feed discovery is limited to link rel=alternate, which is probably plenty? It could be extended with some "guessing", checking if `/rss.xml` returns a valid feed, but that can be a future thing.
Probably going to want to make it possible to enable/disable feed polling, which might live on site, but we'll figure that out when we get to it
Includes a Saxy parser based on gluttony, some feed "discovery" logic, and some random fixes
Yeah, it's just a letter but it looks ok and that's the extent of my Figma skills.
and added some random meta tags while I was at it, it's nice.
Ran `mix phx.gen.release --docker` to get a release and dockerfile and added some docs on how to set up the env vars
Will want to iterate on this but it matches the design aesthetic and it's a decent starting point. Not sure about the stats cards.
Also styles the login page
I experimented a bit with prototypes and this is just lifting the design into the Phoenix app. Could probably extract some things into components.
Hooks it all up and I've verified it works end 2 end. Sick.
Adds some basic stuff to the Accounts context, including the session upsert
Covers Discovery, PAR, session, etc.
```
iex(8)> {:ok, server} = AnnotAt.Atproto.OAuth.Discovery.discover(id.pds_endpoint)
{:ok,
%AnnotAt.Atproto.OAuth.ServerMetadata{
issuer: "https://bsky.social",
authorization_endpoint: "https://bsky.social/oauth/authorize",
token_endpoint: "https://bsky.social/oauth/token",
par_endpoint: "https://bsky.social/oauth/par",
scopes_supported: ["atproto", "transition:email", "transition:generic",
"transition:chat.bsky"],
revocation_endpoint: "https://bsky.social/oauth/revoke"
}}
```
Might've diverged a bit from my original plan. Verified in IEx
```
iex(2)> AnnotAt.Atproto.Identity.resolve_handle("atproto.com")
{:ok,
%AnnotAt.Atproto.Identity{
did: "did:plc:ewvi7nxzyoun6zhxrhs64oiz",
handle: "atproto.com",
pds_endpoint: "https://enoki.us-east.host.bsky.network"
}}
iex(3)> AnnotAt.Atproto.Identity.resolve_handle("jola.dev")
{:ok,
%AnnotAt.Atproto.Identity{
did: "did:plc:bvraa6gajy4tfr3eh2sisdkr",
handle: "jola.dev",
pds_endpoint: "https://shaggymane.us-west.host.bsky.network"
}}
```
Avoided using Ecto.Changeset to make it easier to move this out at a later point
First batch of atproto related code. Roughly inspired by `atex` and lots of digging. Going to want to go over this code again properly cause it's all scary crypto stuff. Wouldn't normally want that kind of code "handrolled" but it's all fairly standard and eg assent doesn't support atproto, so here we are.
Using the new microvm engine to run postgres, with some postgres config to run over tcp instead of a unix socket to match the default Phoenix project setup.
The steps match `mix precommit` but with the flags that make them fail instead of just fixing, eg `mix format --check-formatted` instead of just `mix format`.
So turns out document rkeys having to be TIDs is really annoying for us. Because the blog owner has to add `<link rel="site.standard.document" href="<aturi>" /> with the correct aturi in it, and it contains the rkey, and the rkey has to be a TID, it means either we generate it and the blog owner then has to copy paste and redeploy their blog, or we use a deterministic TID implementation based on eg post publish date, which means they have to use the exact same one. We just can't generate the rkey for the user.
It would be much easier if standard.site allowed other rkeys, so that you could eg use the RSS feed post guid. That would make it trivial, blog owner uses their guid (eg slug) and we do the same.
Of course, all this led to me realizing that the cleanest way to build this is to rely on the blow owner to set the link aturi and we just use that. So the blog owner generates a TID when they post their blog post, eg deterministically from publishing date, but we don't have to match the exact implementation of the algorithm, or they just get a random one and store it on their side. They can deploy once and we discover the aturi from the post.
Problem solved!
This commit is way too big. It is a very nice milestone though, with a full integrated site adding flow/wizard.
You can now add sites, get RSS feed, validate well-known, and everything up to creating a publication. Still can use a lot of polish but it's working.
Also includes TID generation and lots of other improvements I bumped into while I was pulling everything together.
Feed discovery is limited to link rel=alternate, which is probably plenty? It could be extended with some "guessing", checking if `/rss.xml` returns a valid feed, but that can be a future thing.
Probably going to want to make it possible to enable/disable feed polling, which might live on site, but we'll figure that out when we get to it
Covers Discovery, PAR, session, etc.
```
iex(8)> {:ok, server} = AnnotAt.Atproto.OAuth.Discovery.discover(id.pds_endpoint)
{:ok,
%AnnotAt.Atproto.OAuth.ServerMetadata{
issuer: "https://bsky.social",
authorization_endpoint: "https://bsky.social/oauth/authorize",
token_endpoint: "https://bsky.social/oauth/token",
par_endpoint: "https://bsky.social/oauth/par",
scopes_supported: ["atproto", "transition:email", "transition:generic",
"transition:chat.bsky"],
revocation_endpoint: "https://bsky.social/oauth/revoke"
}}
```
Might've diverged a bit from my original plan. Verified in IEx
```
iex(2)> AnnotAt.Atproto.Identity.resolve_handle("atproto.com")
{:ok,
%AnnotAt.Atproto.Identity{
did: "did:plc:ewvi7nxzyoun6zhxrhs64oiz",
handle: "atproto.com",
pds_endpoint: "https://enoki.us-east.host.bsky.network"
}}
iex(3)> AnnotAt.Atproto.Identity.resolve_handle("jola.dev")
{:ok,
%AnnotAt.Atproto.Identity{
did: "did:plc:bvraa6gajy4tfr3eh2sisdkr",
handle: "jola.dev",
pds_endpoint: "https://shaggymane.us-west.host.bsky.network"
}}
```
First batch of atproto related code. Roughly inspired by `atex` and lots of digging. Going to want to go over this code again properly cause it's all scary crypto stuff. Wouldn't normally want that kind of code "handrolled" but it's all fairly standard and eg assent doesn't support atproto, so here we are.