Files
ReadMeABook/src/lib/utils/ignored-audiobooks.ts
T
kikootwo 09cff5b68d Add per-user ignored audiobooks feature
Introduce a per-user "ignored audiobooks" feature to suppress auto-requests. Changes include:

- Database: add Prisma model IgnoredAudiobook and SQL migration to create ignored_audiobooks table with indexes and FK to users.
- Backend: new API routes to list, add, delete, and check ignored audiobooks (/api/user/ignored-audiobooks, /check/:asin, /:id). Add annotateWithIgnoreStatus utility and integrate it into multiple audiobook list endpoints (popular, new-releases, category, search, authors, series).
- Request creator: add ignore-list check (with sibling-ASIN expansion) and a bypassIgnore option for manual requests; return an 'ignored' reason when blocked.
- Frontend: hooks (useIsIgnored, useToggleIgnore, useIgnoredList) and UI updates — AudiobookCard shows an "Ignored" indicator and AudiobookDetailsModal adds an ignore toggle and propagates local state changes.
- Misc: adjust deduplication duration tolerance (to 5% / min 10 minutes), tweak SWR refresh intervals for shelves/syncing, and small logging/info updates.
- Tests: add unit tests for request-creator ignore logic and update existing tests/mocks to account for ignore annotation; extend prisma test helper with ignoredAudiobook mock.

This commit implements the ignore-list end-to-end (DB, server, client, and tests) so users can ignore specific ASINs and have auto-request flows respect that preference.
2026-03-11 11:56:35 -04:00

38 lines
1.2 KiB
TypeScript

/**
* Component: Ignored Audiobooks Utility
* Documentation: documentation/features/ignored-audiobooks.md
*
* Shared utility for annotating audiobook lists with per-user ignore status.
* Uses a single bulk query for the user's full ignore list, then annotates in-memory.
*/
import { prisma } from '@/lib/db';
/**
* Annotate an array of audiobook objects with `isIgnored: boolean`.
* Fetches the user's full ignore list in one query and matches by ASIN.
*
* If userId is undefined (unauthenticated), all books get `isIgnored: false`.
*/
export async function annotateWithIgnoreStatus<T extends { asin: string }>(
audiobooks: T[],
userId?: string
): Promise<(T & { isIgnored: boolean })[]> {
if (!userId || audiobooks.length === 0) {
return audiobooks.map((book) => ({ ...book, isIgnored: false }));
}
// Single query: get all ASINs this user has ignored
const ignoredEntries = await prisma.ignoredAudiobook.findMany({
where: { userId },
select: { asin: true },
});
const ignoredAsinSet = new Set(ignoredEntries.map((e) => e.asin));
return audiobooks.map((book) => ({
...book,
isIgnored: ignoredAsinSet.has(book.asin),
}));
}