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:
khannurien
2026-10-10 08:25:24 +00:00
parent ffc071af4c
commit 28f910b00e
18 changed files with 273 additions and 94 deletions

View File

@@ -23,6 +23,7 @@ import {
} from "../model/dump.ts";
import { isFollowRow } from "../model/follow.ts";
import { isPlaylistRow, playlistRowToApi } from "../model/playlist.ts";
import { VISIBLE_DUMP_COUNT } from "../lib/playlist-members.ts";
import { type Page, pageOf } from "../lib/pagination.ts";
import { attachCategoryIds, categoryJoin } from "./category-service.ts";
import { sendToUser } from "./ws-service.ts";
@@ -232,12 +233,14 @@ export function getFollowedPlaylistsDumpFeed(
/**
* Returns a page of the public playlists `userId` follows, most recently
* followed first, for their profile and playlists pages.
* followed first, for their profile and playlists pages. Their dumps are
* counted for `requestingUserId`, null being a signed-out viewer.
*
* @throws {APIException} 500 when a row doesn't parse as a playlist.
*/
export function getFollowedPlaylistsByUser(
userId: string,
requestingUserId: string | null,
page: number,
limit: number,
): Page<Playlist> {
@@ -245,7 +248,7 @@ export function getFollowedPlaylistsByUser(
const rawRows = db.prepare(
`SELECT p.*, u.username as owner_username,
(SELECT COUNT(*) FROM playlist_dumps pd WHERE pd.playlist_id = p.id) as dump_count
${VISIBLE_DUMP_COUNT}
FROM playlists p
LEFT JOIN users u ON u.id = p.user_id
INNER JOIN follows f ON f.followed_playlist_id = p.id
@@ -253,7 +256,7 @@ export function getFollowedPlaylistsByUser(
AND p.is_public = 1
ORDER BY f.created_at DESC
LIMIT ? OFFSET ?;`,
).all(userId, limit + 1, offset);
).all(requestingUserId ?? "", userId, limit + 1, offset);
if (!rawRows.every(isPlaylistRow)) {
throw new APIException(500, "Malformed playlist data");