mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
db(annotations): let a deleted audio bookmark position be bookmarked again
Audio bookmark deletes are soft, but the unique index on (user, book, position_seconds) still covered tombstones, so the insert conflicted and the audiobook bookmark route answered 409 for any position that had ever been bookmarked and deleted. Limit the index to live rows. The CFI index is left as it is because KOReader bookmark revival relies on tombstones keeping their CFI. The new predicate only narrows the index, so existing rows cannot violate it.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
DROP INDEX "bookmarks_user_book_pos_uidx";--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "bookmarks_user_book_pos_uidx" ON "bookmarks" USING btree ("user_id","book_id","position_seconds") WHERE "bookmarks"."position_seconds" is not null and "bookmarks"."cfi" is null and "bookmarks"."deleted_at" is null;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -701,6 +701,13 @@
|
||||
"when": 1790446233976,
|
||||
"tag": "0099_fractional_finished_threshold",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 100,
|
||||
"version": "7",
|
||||
"when": 1790634252132,
|
||||
"tag": "0100_audio_bookmark_position_ignores_tombstones",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getTableConfig } from 'drizzle-orm/pg-core';
|
||||
import { PgDialect, getTableConfig } from 'drizzle-orm/pg-core';
|
||||
|
||||
import {
|
||||
annotations,
|
||||
@@ -105,6 +105,16 @@ describe('reader schema', () => {
|
||||
expect(annotations.style.default).toBe('highlight');
|
||||
});
|
||||
|
||||
it('frees a deleted audio bookmark position but keeps CFI tombstones unique for KOReader revival', () => {
|
||||
const dialect = new PgDialect();
|
||||
const predicates = new Map(getTableConfig(bookmarks).indexes.map((idx) => [idx.config.name, idx.config.where]));
|
||||
const positionPredicate = dialect.sqlToQuery(predicates.get('bookmarks_user_book_pos_uidx')!).sql;
|
||||
const cfiPredicate = dialect.sqlToQuery(predicates.get('bookmarks_user_book_cfi_uidx')!).sql;
|
||||
|
||||
expect(positionPredicate).toContain('"deleted_at" is null');
|
||||
expect(cfiPredicate).not.toContain('deleted_at');
|
||||
});
|
||||
|
||||
it('keeps reader preference uniqueness scoped to user and format/file', () => {
|
||||
const defaultConfig = getTableConfig(readerDefaultPreferences);
|
||||
const perBookConfig = getTableConfig(readerPreferences);
|
||||
|
||||
@@ -447,7 +447,7 @@ export const bookmarks = pgTable(
|
||||
.where(sql`${t.cfi} is not null`),
|
||||
uniqueIndex('bookmarks_user_book_pos_uidx')
|
||||
.on(t.userId, t.bookId, t.positionSeconds)
|
||||
.where(sql`${t.positionSeconds} is not null and ${t.cfi} is null`),
|
||||
.where(sql`${t.positionSeconds} is not null and ${t.cfi} is null and ${t.deletedAt} is null`),
|
||||
check('bookmarks_origin_chk', sql`${t.origin} in ('web', 'koreader')`),
|
||||
],
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user