v3: added site-wide categories, added admin category management, various visual fixes
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 2m52s

This commit is contained in:
khannurien
2026-06-28 16:23:44 +00:00
parent c8c7b05c25
commit fae25f3e6c
44 changed files with 2144 additions and 399 deletions

View File

@@ -21,6 +21,7 @@ import {
playlistRowToApi,
userRowToApi,
} from "../model/db.ts";
import { attachCategoryIds } from "./category-service.ts";
// ── Follow / unfollow a user ──────────────────────────────────────────────────
@@ -139,26 +140,35 @@ export function getFollowedUsersDumpFeed(
followerId: string,
page: number,
limit: number,
categoryId?: string,
): { items: Dump[]; total: number } {
const offset = (page - 1) * limit;
// Optional category filter: join the membership table and constrain to it.
const catJoin = categoryId
? "INNER JOIN dump_categories dc ON dc.dump_id = d.id AND dc.category_id = ?"
: "";
const catParams = categoryId ? [categoryId] : [];
const rawRows = db.prepare(
`SELECT ${SELECT_COLS_ALIASED}
FROM dumps d
INNER JOIN follows f ON f.followed_user_id = d.user_id
${catJoin}
WHERE f.follower_id = ?
AND d.is_private = 0
ORDER BY d.created_at DESC
LIMIT ? OFFSET ?;`,
).all(followerId, limit, offset);
).all(...catParams, followerId, limit, offset);
const totalRow = db.prepare(
`SELECT COUNT(*) as count
FROM dumps d
INNER JOIN follows f ON f.followed_user_id = d.user_id
${catJoin}
WHERE f.follower_id = ?
AND d.is_private = 0;`,
).get(followerId) as { count: number } | undefined;
).get(...catParams, followerId) as { count: number } | undefined;
const userFeedRows = rawRows as Parameters<typeof isDumpRow>[0][];
if (!userFeedRows.every(isDumpRow)) {
@@ -168,7 +178,9 @@ export function getFollowedUsersDumpFeed(
"Malformed dump data",
);
}
return { items: userFeedRows.map(dumpRowToApi), total: totalRow?.count ?? 0 };
const items = userFeedRows.map(dumpRowToApi);
attachCategoryIds(items);
return { items, total: totalRow?.count ?? 0 };
}
// ── Followed-playlists dump feed ──────────────────────────────────────────────
@@ -177,22 +189,29 @@ export function getFollowedPlaylistsDumpFeed(
followerId: string,
page: number,
limit: number,
categoryId?: string,
): { items: Dump[]; total: number } {
const offset = (page - 1) * limit;
const catJoin = categoryId
? "INNER JOIN dump_categories dc ON dc.dump_id = d.id AND dc.category_id = ?"
: "";
const catParams = categoryId ? [categoryId] : [];
const rawRows = db.prepare(
`SELECT ${SELECT_COLS_ALIASED}
FROM dumps d
INNER JOIN playlist_dumps pd ON pd.dump_id = d.id
INNER JOIN playlists p ON p.id = pd.playlist_id
INNER JOIN follows f ON f.followed_playlist_id = p.id
${catJoin}
WHERE f.follower_id = ?
AND p.is_public = 1
AND d.is_private = 0
GROUP BY d.id
ORDER BY MAX(pd.added_at) DESC
LIMIT ? OFFSET ?;`,
).all(followerId, limit, offset);
).all(...catParams, followerId, limit, offset);
const totalRow = db.prepare(
`SELECT COUNT(DISTINCT d.id) as count
@@ -200,10 +219,11 @@ export function getFollowedPlaylistsDumpFeed(
INNER JOIN playlist_dumps pd ON pd.dump_id = d.id
INNER JOIN playlists p ON p.id = pd.playlist_id
INNER JOIN follows f ON f.followed_playlist_id = p.id
${catJoin}
WHERE f.follower_id = ?
AND p.is_public = 1
AND d.is_private = 0;`,
).get(followerId) as { count: number } | undefined;
).get(...catParams, followerId) as { count: number } | undefined;
if (!rawRows.every(isDumpRow)) {
throw new APIException(
@@ -212,10 +232,9 @@ export function getFollowedPlaylistsDumpFeed(
"Malformed dump data",
);
}
return {
items: rawRows.map(dumpRowToApi),
total: totalRow?.count ?? 0,
};
const items = rawRows.map(dumpRowToApi);
attachCategoryIds(items);
return { items, total: totalRow?.count ?? 0 };
}
// ── Followed playlists (as playlist objects) ──────────────────────────────────