feat(core,server): log feed, titles on changes, work titles on copies

GET log (engagements and listens, keyset over at/kind/id), listChanges
title and intoTitle, listCopies workTitle, all via batched lookups.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-30 22:11:53 +01:00
co-authored by Claude Opus 5.5
parent 5fa59310f3
commit c2e8c83f86
12 changed files with 561 additions and 7 deletions
+19
View File
@@ -329,6 +329,25 @@ forEachDialect("api", (getDb) => {
expect((await call(app, "PATCH", "me/preferences", { cookie: ana, body: { ratingScale: "5" }, noCsrf: true })).status).toBe(403);
});
test("log: engagements newest first with keyset paging; copies carry workTitle", async () => {
const ana = await signUp(app, "ana");
const e = (await (await call(app, "POST", "entries/quick", { cookie: ana, body: { title: "Dune" } })).json()) as { id: string; workId: string };
for (const value of ["2026-01-02", "2026-03-04"]) {
const r = await call(app, "POST", `entries/${e.id}/engagements`, { cookie: ana, body: { finishedAt: { value, precision: "day" } } });
expect(r.status).toBe(201);
}
const page1 = (await (await call(app, "GET", "log?limit=1", { cookie: ana })).json()) as { items: { at: string; title: string; kind: string }[]; nextCursor: string };
expect(page1.items).toEqual([expect.objectContaining({ kind: "engagement", at: "2026-03-04", title: "Dune" })]);
const page2 = (await (await call(app, "GET", `log?limit=1&cursor=${page1.nextCursor}`, { cookie: ana })).json()) as { items: { at: string }[]; nextCursor: string | null };
expect(page2.items.map((i) => i.at)).toEqual(["2026-01-02"]);
expect(page2.nextCursor).toBeNull();
expect((await call(app, "GET", "log?cursor=bad", { cookie: ana })).status).toBe(400);
expect((await call(app, "GET", "log")).status).toBe(401);
await call(app, "POST", "copies", { cookie: ana, body: { workId: e.workId, format: "Paperback" } });
expect(await (await call(app, "GET", "copies", { cookie: ana })).json()).toEqual([expect.objectContaining({ workTitle: "Dune", format: "Paperback" })]);
});
test("JSON bodies need Content-Type application/json (415)", async () => {
const res = await app.fetch(
new Request(`${BASE}/auth/register`, {