v4: a playlist's dump count is of the dumps the viewer may see — it counted every dump in the playlist, so a card said "3 dumps" over a page listing two, and told everyone how many private dumps a playlist held. The lists (/playlists, a profile's playlists and followed ones, the search, a dump's playlists, the add-to-playlist panel) now count by isMemberVisibleTo's rule, in SQL as VISIBLE_DUMP_COUNT (lib/playlist-members.ts): the public dumps, and for the owner their own private ones. GET /api/playlists and /api/users/:key/follow/playlists read the optional sign-in for it. The community page and the digests count the public dumps, their figures being the same for every reader. Live, playlist_created and playlist_updated carry each socket's own count: broadcastChange builds the owner's message and the others' apart. playlist_dumps_updated loses its dumpCount, a client counting the dumps it is sent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -146,6 +146,72 @@ test("a public playlist hides its private dumps from everyone but their owner",
|
||||
);
|
||||
});
|
||||
|
||||
test("a playlist's dumpCount is of the dumps the viewer may see", async () => {
|
||||
const owner = await createUser();
|
||||
const author = await createUser();
|
||||
const pl = insertPlaylist({ userId: owner.id, title: "Counted banana" });
|
||||
const pub = insertDump({ userId: owner.id });
|
||||
const mine = insertDump({ userId: owner.id, isPrivate: true });
|
||||
// Someone else's, gone private after it was added: its owner no longer
|
||||
// sees it there, and neither does its poster.
|
||||
const theirs = insertDump({ userId: author.id, isPrivate: true });
|
||||
for (const [i, d] of [pub, mine, theirs].entries()) {
|
||||
db.prepare(
|
||||
`INSERT INTO playlist_dumps (playlist_id, dump_id, position, added_at)
|
||||
VALUES (?, ?, ?, ?);`,
|
||||
).run(pl.id, d.id, i, new Date().toISOString());
|
||||
}
|
||||
await put(`/api/me/follow/playlists/${pl.id}`, { auth: author.auth });
|
||||
|
||||
const lists = [
|
||||
"/api/playlists",
|
||||
`/api/users/@${owner.username}/playlists`,
|
||||
`/api/users/@${author.username}/follow/playlists`,
|
||||
];
|
||||
for (
|
||||
const [auth, expected] of [
|
||||
[owner.auth, 2],
|
||||
[author.auth, 1],
|
||||
[undefined, 1],
|
||||
] as const
|
||||
) {
|
||||
for (const url of lists) {
|
||||
const { items } = (await get<PaginatedData<Playlist>>(url, { auth }))
|
||||
.data;
|
||||
assertEquals(items.map((p) => p.dumpCount), [expected], url);
|
||||
}
|
||||
assertEquals(
|
||||
(await get<{ playlists: Playlist[] }>("/api/search?q=banana", { auth }))
|
||||
.data.playlists.map((p) => p.dumpCount),
|
||||
[expected],
|
||||
);
|
||||
assertEquals(
|
||||
(await get<Playlist[]>(`/api/dumps/${pub.id}/playlists`, { auth })).data
|
||||
.map((p) => p.dumpCount),
|
||||
[expected],
|
||||
);
|
||||
const page = await get<PlaylistWithDumps>(`/api/playlists/${pl.id}`, {
|
||||
auth,
|
||||
});
|
||||
assertEquals(page.data.dumpCount, expected);
|
||||
assertEquals(ids(page).length, expected);
|
||||
}
|
||||
assertEquals(
|
||||
(await get<PlaylistMembership[]>(`/api/me/playlists?contains=${pub.id}`, {
|
||||
auth: owner.auth,
|
||||
})).data.map((m) => [m.hasDump, m.playlist.dumpCount]),
|
||||
[[true, 2]],
|
||||
);
|
||||
// An edit answers with the editor's count.
|
||||
assertEquals(
|
||||
(await put<Playlist>(`/api/playlists/${pl.id}`, {
|
||||
auth: owner.auth,
|
||||
json: { title: "Counted" },
|
||||
})).data.dumpCount,
|
||||
2,
|
||||
);
|
||||
});
|
||||
|
||||
test("reorder: must name exactly the current members", async () => {
|
||||
const owner = await createUser();
|
||||
const { id } = insertPlaylist({ userId: owner.id });
|
||||
|
||||
@@ -709,8 +709,32 @@ wsTest(
|
||||
for (const c of [a, x, anon]) {
|
||||
const msg = await c.next("playlist_dumps_updated");
|
||||
assertEquals(msg.dumpIds, [added.id, pub.id]);
|
||||
// The count is the playlist's, as its dumpCount gives it to everyone.
|
||||
assertEquals(msg.dumpCount, 4);
|
||||
}
|
||||
|
||||
// A change to its details carries the count of what each may see, too.
|
||||
await put(`/api/playlists/${pl.id}`, {
|
||||
auth: owner.auth,
|
||||
json: { title: "Renamed" },
|
||||
});
|
||||
assertEquals((await o.next("playlist_updated")).playlist.dumpCount, 3);
|
||||
for (const c of [a, x, anon]) {
|
||||
assertEquals((await c.next("playlist_updated")).playlist.dumpCount, 2);
|
||||
}
|
||||
await put(`/api/playlists/${pl.id}`, {
|
||||
auth: owner.auth,
|
||||
json: { isPublic: false },
|
||||
});
|
||||
await put(`/api/playlists/${pl.id}`, {
|
||||
auth: owner.auth,
|
||||
json: { isPublic: true },
|
||||
});
|
||||
assertEquals(
|
||||
(await o.next("playlist_updated", (m) => m.playlist.isPublic)).playlist
|
||||
.dumpCount,
|
||||
3,
|
||||
);
|
||||
for (const c of [a, x, anon]) {
|
||||
assertEquals((await c.next("playlist_created")).playlist.dumpCount, 2);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -817,7 +841,6 @@ wsTest(
|
||||
});
|
||||
const hidden = await x.next("playlist_dumps_updated");
|
||||
assertEquals(hidden.dumpIds, [keep.id, gone.id]);
|
||||
assertEquals(hidden.dumpCount, 3);
|
||||
assertEquals(
|
||||
(await o.next("playlist_dumps_updated")).dumpIds,
|
||||
[keep.id, flip.id, gone.id],
|
||||
@@ -835,7 +858,7 @@ wsTest(
|
||||
await del(`/api/dumps/${gone.id}`, { auth: owner.auth });
|
||||
const after = await x.next(
|
||||
"playlist_dumps_updated",
|
||||
(m) => m.dumpCount === 2,
|
||||
(m) => m.dumpIds.length === 2,
|
||||
);
|
||||
assertEquals(after.dumpIds, [keep.id, flip.id]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user