mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(dashboard): exclude finished books from continue reading
The dashboard query now joins user book statuses to exclude books marked as 'read', 'skimmed', or 'abandoned' from the continue reading list. Closes #528
This commit is contained in:
@@ -64,18 +64,22 @@ describe('DashboardRepository', () => {
|
||||
expect(listChain.limit).toHaveBeenCalledWith(2);
|
||||
});
|
||||
|
||||
it('maps continue-reading rows to id list without joining audiobook progress', async () => {
|
||||
it('maps continue-reading rows to id list and excludes terminal read statuses', async () => {
|
||||
const listChain = makeLimitChain([{ id: 40 }, { id: 9 }]);
|
||||
const db = { select: vi.fn().mockReturnValue(listChain) };
|
||||
const repo = new DashboardRepository(db as never);
|
||||
|
||||
const result = await repo.findContinueReadingBookIds([8], 55, 10);
|
||||
const whereArg = listChain.where.mock.calls[0]?.[0];
|
||||
const whereValues = collectValues(whereArg);
|
||||
|
||||
expect(result).toEqual([40, 9]);
|
||||
expect(listChain.leftJoin).toHaveBeenCalledTimes(2);
|
||||
expect(listChain.leftJoin).toHaveBeenCalledTimes(3);
|
||||
expect(listChain.leftJoin.mock.calls[0]?.[0]).toBe(bookFiles);
|
||||
expect(listChain.leftJoin.mock.calls[1]?.[0]).toBe(readingProgress);
|
||||
expect(listChain.leftJoin.mock.calls[2]?.[0]).toBe(userBookStatus);
|
||||
expect(listChain.leftJoin.mock.calls.some((call) => call[0] === audiobookProgress)).toBe(false);
|
||||
expect(whereValues).toEqual(expect.arrayContaining(['read', 'skimmed', 'abandoned']));
|
||||
expect(listChain.innerJoin).not.toHaveBeenCalled();
|
||||
expect(listChain.orderBy).toHaveBeenCalledTimes(1);
|
||||
expect(listChain.limit).toHaveBeenCalledWith(10);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { buildContentFilterClauses } from '../../common/utils/content-filter-sql
|
||||
type Db = NodePgDatabase<typeof schema>;
|
||||
type UpNextInSeriesRow = { id: number };
|
||||
const AUDIO_FORMATS = BOOK_FORMATS.filter(isAudioFormat);
|
||||
const CONTINUE_READING_EXCLUDED_READ_STATUSES = ['read', 'skimmed', 'abandoned'] as const satisfies readonly ReadStatus[];
|
||||
const DISCOVERY_EXCLUDED_READ_STATUSES = ['reading', 'rereading', 'on_hold', 'read', 'skimmed', 'abandoned'] as const satisfies readonly ReadStatus[];
|
||||
|
||||
@Injectable()
|
||||
@@ -44,12 +45,14 @@ export class DashboardRepository {
|
||||
.from(books)
|
||||
.leftJoin(bookFiles, eq(bookFiles.id, books.primaryFileId))
|
||||
.leftJoin(readingProgress, and(eq(readingProgress.bookFileId, bookFiles.id), eq(readingProgress.userId, userId)))
|
||||
.leftJoin(userBookStatus, and(eq(userBookStatus.bookId, books.id), eq(userBookStatus.userId, userId)))
|
||||
.where(
|
||||
and(
|
||||
inArray(books.libraryId, accessibleLibraryIds),
|
||||
eq(books.status, 'present'),
|
||||
or(isNull(bookFiles.format), notInArray(bookFiles.format, AUDIO_FORMATS)),
|
||||
sql`${readingProgress.percentage} > 0 and ${readingProgress.percentage} < 100`,
|
||||
or(isNull(userBookStatus.bookId), notInArray(userBookStatus.status, [...CONTINUE_READING_EXCLUDED_READ_STATUSES])),
|
||||
...cfClauses,
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user