Stories are archived, and what each account watched follows it
ClosedThis adds getArchive and markViewed. getArchive reuses the existing listed() and unblocked filters and the page and after cursor helpers. markViewed writes to the existing story_view table with INSERT OR IGNORE on the (did, subject) primary key. It skips URIs with no matching story and prunes views older than a week, all in one transaction. The input is validated before anything is written, and the service-token and cookie auth goes through viewer::writer. I found no bugs. Tests cover paging, expiry flags, labelled and taken-down accounts, idempotent marking, and the input bounds.
The minor point is the lib.rs module doc, where the rewrapped paragraph leaves one over-long line. I haven't read the checks, so I don't know whether they pass.
Carried forward: the change this pull makes at 32b56f7be8fa23e6e0d3e30eab0f2f2b3aed06d5 is the one reviewed at 92f7c8c5a258e9d6f009e7728d18ef13cccda4a5, so that review stands. What is new comes from its base.
The over-long line in lib.rs's module doc: rewrapped at 1e8d010.
The only change since my last review is the rewrap of the module doc in crates/grain-serve/src/lib.rs. That was the one point I raised, and it's done. The rest of the pull is the same getArchive and markViewed change I already reviewed and found no bugs in. The other changes in the head come from the merge of feature/rust, not from this pull. The four checks (test, rust, harness, check) were still queued when I looked, so I can't say they pass.
Landed on feature/rust at fc5f686 through the batch pull Seven reviewed pulls land on feature/rust together, checked once, checked there as a whole. This pull's head is in feature/rust's history.
What changes
The archive.
social.grain.story.getArchivelists an account's stories, expired or not, the latest first, paged by a cursor of the last story'screatedAtand URI,limit1 to 100. It leaves out what every list of stories does: an account not shown, a story with a label that hides (grain's or its author's), and an account that blocks, or is blocked by, the viewer. Each is astoryView, itsviewer.viewedandviewer.favfilled for the viewer.What was watched.
social.grain.story.markViewedkeeps the stories the caller watched in the index'sstory_view(index/stories.rs), whichgetStories,getStory,getArchiveandgetAuthorsalready read. The apps call it with a service token, the web with its cookie from a page of the site. Marking one twice changes nothing; a story the index does not hold, deleted since it was read, is passed over. Each call lets go of views more than a week old, as a story stands a day.One of the pulls closing what the clients call and grain-serve did not serve; the table of them all is in
gaps/graph's body (Accounts mute and unmute, and list whom they block and mute).Why
The web and both apps call both, and grain-serve answered 501. knotless has neither, so this follows grain's own reads: the list shape, cursor and filters of
read/story.rs.Differences from the old server
getStoriesdoes; the old archive left out only accounts gone at their host or taken down.Profile not found), as every query naming an actor is here; the old one answered an empty list.markViewedlets go of those a week old.storiesmust be at-URIs, 1 to 100, as before; a URI that is not one is refused before anything is kept, where the old server checked onlyat://.How it was checked
crates/grain-serve/tests/stories.rs, against the reference PDS for the service tokens: the archive in order, expired flagged, a labelled story left out, paged, empty for an account taken down, an unknown or missing actor refused; marking twice and a story not held,viewer.viewedingetStoriesandgetArchive,unviewedCountingetAuthors, nothing watched for no one, and the input's bounds refused.index/stories.rshas a unit test of keeping and letting go, and the plan check covers the archive's query. The rest is the runner's.Shortcomings
gaps/deletespurges them.🤖 Generated with Claude Code