mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
db(library): add missing foreign key indexes for deletion
Add indexes on foreign key columns referencing books, book files, and libraries across user activity and integration tables to prevent table scans during cascade deletes. Closes #663
This commit is contained in:
@@ -20,6 +20,39 @@ function placeholders(message) {
|
||||
return [...new Set(message.match(/\{[^{}]+\}/g) ?? [])].sort()
|
||||
}
|
||||
|
||||
async function sourceFiles(directory) {
|
||||
const entries = await readdir(directory, { withFileTypes: true })
|
||||
const files = await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
const entryPath = path.join(directory, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === '__tests__') return []
|
||||
return sourceFiles(entryPath)
|
||||
}
|
||||
return entry.isFile() && /\.(ts|vue)$/.test(entry.name) && !/\.(spec|test)\.ts$/.test(entry.name) ? [entryPath] : []
|
||||
}),
|
||||
)
|
||||
return files.flat()
|
||||
}
|
||||
|
||||
async function sourceMessageKeys() {
|
||||
const sourceDirectory = path.join(clientRoot, 'src')
|
||||
const files = await sourceFiles(sourceDirectory)
|
||||
const keys = new Set()
|
||||
|
||||
for (const file of files) {
|
||||
const source = await readFile(file, 'utf8')
|
||||
for (const match of source.matchAll(/(?<![\w$])(?:\$?t|\$?tc|\$?te)\(\s*(['"])([A-Za-z0-9_.-]+)\1/g)) {
|
||||
keys.add(match[2])
|
||||
}
|
||||
for (const match of source.matchAll(/<i18n-t\b[^>]*\bkeypath\s*=\s*(['"])([A-Za-z0-9_.-]+)\1/g)) {
|
||||
keys.add(match[2])
|
||||
}
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
const localeTypes = await readFile(localeTypesPath, 'utf8')
|
||||
const supportedMatch = localeTypes.match(/SUPPORTED_LOCALES\s*=\s*(\[[\s\S]*?\])\s+as const/)
|
||||
if (!supportedMatch) throw new Error('Unable to read SUPPORTED_LOCALES from packages/types/src/locale.ts')
|
||||
@@ -41,6 +74,10 @@ const reference = catalogs.get('en')
|
||||
if (!reference) throw new Error('English reference catalog is required')
|
||||
|
||||
const errors = []
|
||||
for (const key of await sourceMessageKeys()) {
|
||||
if (!reference.has(key)) errors.push(`en: missing key referenced in source ${key}`)
|
||||
}
|
||||
|
||||
for (const [locale, catalog] of catalogs) {
|
||||
for (const [key, referenceMessage] of reference) {
|
||||
const message = catalog.get(key)
|
||||
|
||||
@@ -500,6 +500,7 @@
|
||||
"subtitle": "Erstellen Sie teilbare Anmeldelinks für gemeinsam genutzte Konten.",
|
||||
"createLink": "Link erstellen",
|
||||
"noSharedAccounts": "Keine gemeinsam genutzten Konten gefunden",
|
||||
"noSharedAccountsHint": "Erstellen Sie zuerst auf der {usersPage} ein gemeinsames Konto, um Magic Links zu erzeugen.",
|
||||
"activeLinks": "Aktive Links",
|
||||
"inactiveLinks": "Inaktive Links",
|
||||
"paused": "Pausiert",
|
||||
@@ -1841,11 +1842,13 @@
|
||||
"loadMore": "Weitere Aktivität laden"
|
||||
},
|
||||
"howBooksSynced": {
|
||||
"title": "Wie Bücher synchronisiert werden"
|
||||
"title": "Wie Bücher synchronisiert werden",
|
||||
"body": "Um Bücher an Ihr Kobo-Gerät zu senden, müssen Sie {syncToKobo} für die Sammlungen aktivieren, die diese Bücher enthalten. Öffnen Sie eine beliebige Sammlung über die Seitenleiste, klicken Sie auf das Bearbeiten-Symbol und aktivieren Sie {syncToKobo}. Bücher, die nicht Teil einer aktivierten Sammlung sind, werden nicht synchronisiert."
|
||||
},
|
||||
"nextSteps": {
|
||||
"title": "Nächste Schritte",
|
||||
"step1": "1. Richten Sie Ihr Kobo-Gerät so ein, dass es mit der obigen Sync-URL synchronisiert."
|
||||
"step1": "1. Richten Sie Ihr Kobo-Gerät so ein, dass es mit der obigen Sync-URL synchronisiert.",
|
||||
"step2": "2. Öffnen Sie in BookOrbit über die Seitenleiste eine beliebige Sammlung, klicken Sie auf das Bearbeiten-Symbol und aktivieren Sie {syncToKobo}. Nur Bücher in für die Synchronisierung aktivierten Sammlungen werden auf Ihr Gerät heruntergeladen."
|
||||
},
|
||||
"syncToKobo": "Mit Kobo synchronisieren",
|
||||
"tabs": {
|
||||
|
||||
@@ -494,6 +494,7 @@
|
||||
"subtitle": "Create shareable login links for shared accounts.",
|
||||
"createLink": "Create link",
|
||||
"noSharedAccounts": "No shared accounts found",
|
||||
"noSharedAccountsHint": "Create a shared account from the {usersPage} first to generate magic links.",
|
||||
"activeLinks": "Active links",
|
||||
"inactiveLinks": "Inactive links",
|
||||
"paused": "Paused",
|
||||
@@ -1835,11 +1836,13 @@
|
||||
"loadMore": "Load more activity"
|
||||
},
|
||||
"howBooksSynced": {
|
||||
"title": "How books are synced"
|
||||
"title": "How books are synced",
|
||||
"body": "To send books to your Kobo device, you must enable {syncToKobo} on the collections containing those books. Open any collection from the sidebar, click the Edit icon, and toggle {syncToKobo}. Books that are not part of an enabled collection will not sync."
|
||||
},
|
||||
"nextSteps": {
|
||||
"title": "Next Steps",
|
||||
"step1": "1. Configure your Kobo device to sync with the Sync URL above."
|
||||
"step1": "1. Configure your Kobo device to sync with the Sync URL above.",
|
||||
"step2": "2. In BookOrbit, go to any collection from the sidebar, click the Edit icon, and enable {syncToKobo}. Only books inside sync-enabled collections will download to your device."
|
||||
},
|
||||
"syncToKobo": "Sync to Kobo",
|
||||
"tabs": {
|
||||
|
||||
@@ -500,6 +500,7 @@
|
||||
"subtitle": "Maak deelbare inloglinks voor gedeelde accounts.",
|
||||
"createLink": "Link maken",
|
||||
"noSharedAccounts": "Geen gedeelde accounts gevonden",
|
||||
"noSharedAccountsHint": "Maak eerst een gedeeld account aan via de {usersPage} om magic links te genereren.",
|
||||
"activeLinks": "Actieve links",
|
||||
"inactiveLinks": "Inactieve links",
|
||||
"paused": "Gepauzeerd",
|
||||
@@ -1841,11 +1842,13 @@
|
||||
"loadMore": "Meer activiteit laden"
|
||||
},
|
||||
"howBooksSynced": {
|
||||
"title": "Hoe boeken worden gesynchroniseerd"
|
||||
"title": "Hoe boeken worden gesynchroniseerd",
|
||||
"body": "Om boeken naar je Kobo-apparaat te sturen, moet je {syncToKobo} inschakelen voor de collecties die deze boeken bevatten. Open een collectie via de zijbalk, klik op het bewerkingspictogram en schakel {syncToKobo} in. Boeken die geen deel uitmaken van een ingeschakelde collectie worden niet gesynchroniseerd."
|
||||
},
|
||||
"nextSteps": {
|
||||
"title": "Volgende stappen",
|
||||
"step1": "1. Configureer je Kobo-apparaat om te synchroniseren met de bovenstaande synchronisatie-URL."
|
||||
"step1": "1. Configureer je Kobo-apparaat om te synchroniseren met de bovenstaande synchronisatie-URL.",
|
||||
"step2": "2. Ga in BookOrbit via de zijbalk naar een collectie, klik op het bewerkingspictogram en schakel {syncToKobo} in. Alleen boeken in collecties waarvoor synchronisatie is ingeschakeld worden naar je apparaat gedownload."
|
||||
},
|
||||
"syncToKobo": "Synchroniseren naar Kobo",
|
||||
"tabs": {
|
||||
|
||||
@@ -497,6 +497,7 @@
|
||||
"subtitle": "Ustvarite povezave za prijavo, ki jih lahko delite za skupne račune.",
|
||||
"createLink": "Ustvari povezavo",
|
||||
"noSharedAccounts": "Ni najdenih skupnih računov",
|
||||
"noSharedAccountsHint": "Za ustvarjanje čarobnih povezav najprej ustvarite skupni račun na {usersPage}.",
|
||||
"activeLinks": "Aktivne povezave",
|
||||
"inactiveLinks": "Neaktivne povezave",
|
||||
"paused": "Ustavljeno",
|
||||
@@ -1838,11 +1839,13 @@
|
||||
"loadMore": "Naloži več dejavnosti"
|
||||
},
|
||||
"howBooksSynced": {
|
||||
"title": "Kako se knjige sinhronizirajo"
|
||||
"title": "Kako se knjige sinhronizirajo",
|
||||
"body": "Če želite poslati knjige v napravo Kobo, morate za zbirke, ki vsebujejo te knjige, omogočiti {syncToKobo}. V stranski vrstici odprite katero koli zbirko, kliknite ikono za urejanje in vklopite {syncToKobo}. Knjige, ki niso del omogočene zbirke, se ne bodo sinhronizirale."
|
||||
},
|
||||
"nextSteps": {
|
||||
"title": "Naslednji koraki",
|
||||
"step1": "1. Nastavite napravo Kobo za sinhronizacijo z zgornjim URL-jem za sinhronizacijo."
|
||||
"step1": "1. Nastavite napravo Kobo za sinhronizacijo z zgornjim URL-jem za sinhronizacijo.",
|
||||
"step2": "2. V BookOrbitu v stranski vrstici odprite katero koli zbirko, kliknite ikono za urejanje in omogočite {syncToKobo}. V napravo se bodo prenesle samo knjige iz zbirk z omogočeno sinhronizacijo."
|
||||
},
|
||||
"syncToKobo": "Sinhronizacija s Kobo",
|
||||
"tabs": {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
CREATE INDEX "annotation_positions_book_file_id_idx" ON "annotation_positions" USING btree ("book_file_id");--> statement-breakpoint
|
||||
CREATE INDEX "annotations_book_id_idx" ON "annotations" USING btree ("book_id");--> statement-breakpoint
|
||||
CREATE INDEX "audiobook_progress_book_id_idx" ON "audiobook_progress" USING btree ("book_id");--> statement-breakpoint
|
||||
CREATE INDEX "audiobook_progress_current_file_id_idx" ON "audiobook_progress" USING btree ("current_file_id");--> statement-breakpoint
|
||||
CREATE INDEX "bookmarks_book_id_idx" ON "bookmarks" USING btree ("book_id");--> statement-breakpoint
|
||||
CREATE INDEX "reader_preferences_book_file_id_idx" ON "reader_preferences" USING btree ("book_file_id");--> statement-breakpoint
|
||||
CREATE INDEX "reading_attempts_book_id_idx" ON "reading_attempts" USING btree ("book_id");--> statement-breakpoint
|
||||
CREATE INDEX "reading_sessions_book_id_idx" ON "reading_sessions" USING btree ("book_id");--> statement-breakpoint
|
||||
CREATE INDEX "user_reading_daily_stats_library_id_idx" ON "user_reading_daily_stats" USING btree ("library_id");--> statement-breakpoint
|
||||
CREATE INDEX "kobo_snapshot_books_book_id_idx" ON "kobo_snapshot_books" USING btree ("book_id");--> statement-breakpoint
|
||||
CREATE INDEX "kobo_reading_states_book_id_idx" ON "kobo_reading_states" USING btree ("book_id");--> statement-breakpoint
|
||||
CREATE INDEX "kobo_device_snapshot_books_book_id_idx" ON "kobo_device_snapshot_books" USING btree ("book_id");--> statement-breakpoint
|
||||
CREATE INDEX "book_dock_files_target_library_id_idx" ON "book_dock_files" USING btree ("target_library_id");--> statement-breakpoint
|
||||
CREATE INDEX "file_write_log_book_file_id_idx" ON "file_write_log" USING btree ("book_file_id");--> statement-breakpoint
|
||||
CREATE INDEX "email_send_log_book_id_idx" ON "email_send_log" USING btree ("book_id");--> statement-breakpoint
|
||||
CREATE INDEX "email_send_log_book_file_id_idx" ON "email_send_log" USING btree ("book_file_id");--> statement-breakpoint
|
||||
CREATE INDEX "koreader_page_stats_book_file_id_idx" ON "koreader_page_stats" USING btree ("book_file_id");--> statement-breakpoint
|
||||
CREATE INDEX "hardcover_book_state_book_id_idx" ON "hardcover_book_state" USING btree ("book_id");--> statement-breakpoint
|
||||
CREATE INDEX "storygraph_book_state_book_id_idx" ON "storygraph_book_state" USING btree ("book_id");
|
||||
File diff suppressed because it is too large
Load Diff
@@ -365,6 +365,13 @@
|
||||
"when": 1783916699394,
|
||||
"tag": "0051_add_account_activity_and_shared_insights",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 52,
|
||||
"version": "7",
|
||||
"when": 1783968441335,
|
||||
"tag": "0052_add_library_delete_fk_indexes",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ export const bookDockFiles = pgTable(
|
||||
},
|
||||
(t) => [
|
||||
index('book_dock_files_status_idx').on(t.status),
|
||||
index('book_dock_files_target_library_id_idx').on(t.targetLibraryId),
|
||||
index('book_dock_files_uploaded_by_idx').on(t.uploadedBy),
|
||||
check('book_dock_files_status_chk', sql`${t.status} in ('pending', 'extracting', 'fetching', 'ready', 'error')`),
|
||||
check('book_dock_files_confidence_range_chk', sql`${t.confidence} is null or (${t.confidence} >= 0 and ${t.confidence} <= 100)`),
|
||||
|
||||
@@ -33,6 +33,8 @@ export const emailSendLog = pgTable(
|
||||
},
|
||||
(t) => [
|
||||
index('email_send_log_user_created_at_idx').on(t.userId, t.createdAt),
|
||||
index('email_send_log_book_id_idx').on(t.bookId),
|
||||
index('email_send_log_book_file_id_idx').on(t.bookFileId),
|
||||
index('email_send_log_created_at_idx').on(t.createdAt),
|
||||
index('email_send_log_status_next_retry_idx').on(t.status, t.nextRetryAt),
|
||||
check('email_send_log_status_chk', sql`${t.status} in ('pending', 'sent', 'failed')`),
|
||||
|
||||
@@ -23,6 +23,7 @@ export const fileWriteLog = pgTable(
|
||||
},
|
||||
(t) => [
|
||||
index('fwl_book_id_written_at_idx').on(t.bookId, t.writtenAt),
|
||||
index('file_write_log_book_file_id_idx').on(t.bookFileId),
|
||||
check('file_write_log_status_chk', sql`${t.status} in ('success', 'skipped', 'failed')`),
|
||||
check('file_write_log_triggered_by_chk', sql`${t.triggeredBy} in ('auto', 'sync')`),
|
||||
check('file_write_log_duration_ms_nonnegative_chk', sql`${t.durationMs} is null or ${t.durationMs} >= 0`),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { boolean, integer, pgTable, real, serial, text, timestamp, unique, varchar } from 'drizzle-orm/pg-core';
|
||||
import { boolean, index, integer, pgTable, real, serial, text, timestamp, unique, varchar } from 'drizzle-orm/pg-core';
|
||||
|
||||
import { books } from './books';
|
||||
import { users } from './auth';
|
||||
@@ -55,7 +55,7 @@ export const hardcoverBookState = pgTable(
|
||||
.notNull()
|
||||
.$onUpdateFn(() => new Date()),
|
||||
},
|
||||
(t) => [unique('hardcover_book_state_user_book_uidx').on(t.userId, t.bookId)],
|
||||
(t) => [unique('hardcover_book_state_user_book_uidx').on(t.userId, t.bookId), index('hardcover_book_state_book_id_idx').on(t.bookId)],
|
||||
);
|
||||
|
||||
export type HardcoverUserSetting = typeof hardcoverUserSettings.$inferSelect;
|
||||
|
||||
@@ -100,6 +100,7 @@ export const koboLegacySnapshotBooks = pgTable(
|
||||
},
|
||||
(t) => [
|
||||
primaryKey({ columns: [t.snapshotId, t.bookId] }),
|
||||
index('kobo_snapshot_books_book_id_idx').on(t.bookId),
|
||||
index('kobo_snapshot_books_snapshot_synced_book_idx').on(t.snapshotId, t.synced, t.bookId),
|
||||
],
|
||||
);
|
||||
@@ -149,6 +150,7 @@ export const koboSnapshotBooks = pgTable(
|
||||
},
|
||||
(t) => [
|
||||
primaryKey({ columns: [t.snapshotId, t.bookId] }),
|
||||
index('kobo_device_snapshot_books_book_id_idx').on(t.bookId),
|
||||
index('kobo_device_snapshot_books_snapshot_synced_book_idx').on(t.snapshotId, t.synced, t.bookId),
|
||||
],
|
||||
);
|
||||
@@ -203,7 +205,7 @@ export const koboReadingStates = pgTable(
|
||||
.notNull()
|
||||
.$onUpdateFn(() => new Date()),
|
||||
},
|
||||
(t) => [unique().on(t.userId, t.bookId)],
|
||||
(t) => [unique().on(t.userId, t.bookId), index('kobo_reading_states_book_id_idx').on(t.bookId)],
|
||||
);
|
||||
|
||||
export const koboSyncHistory = pgTable(
|
||||
|
||||
@@ -231,6 +231,7 @@ export const koreaderPageStats = pgTable(
|
||||
uniqueIndex('kps_user_file_device_page_start_uidx').on(t.userId, t.bookFileId, t.deviceId, t.page, t.startTime),
|
||||
index('kps_user_file_device_start_idx').on(t.userId, t.bookFileId, t.deviceId, t.startTime),
|
||||
index('kps_user_id_idx').on(t.userId),
|
||||
index('koreader_page_stats_book_file_id_idx').on(t.bookFileId),
|
||||
check('koreader_page_stats_duration_nonnegative_chk', sql`${t.durationSeconds} >= 0`),
|
||||
check('koreader_page_stats_page_nonnegative_chk', sql`${t.page} >= 0`),
|
||||
check('koreader_page_stats_total_pages_positive_chk', sql`${t.totalPages} > 0`),
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { getTableConfig } from 'drizzle-orm/pg-core';
|
||||
|
||||
import { bookDockFiles } from './book-dock';
|
||||
import { emailSendLog } from './email-send-log';
|
||||
import { fileWriteLog } from './file-write';
|
||||
import { hardcoverBookState } from './hardcover';
|
||||
import { koboLegacySnapshotBooks, koboReadingStates, koboSnapshotBooks } from './kobo';
|
||||
import { koreaderPageStats } from './koreader';
|
||||
import {
|
||||
annotationPositions,
|
||||
annotations,
|
||||
audiobookProgress,
|
||||
bookmarks,
|
||||
readerPreferences,
|
||||
readingAttempts,
|
||||
readingSessions,
|
||||
userReadingDailyStats,
|
||||
} from './reader';
|
||||
import { storygraphBookState } from './storygraph';
|
||||
|
||||
const cascadeIndexCases = [
|
||||
['book dock target library', bookDockFiles, ['target_library_id']],
|
||||
['email log book', emailSendLog, ['book_id']],
|
||||
['email log book file', emailSendLog, ['book_file_id']],
|
||||
['file write log book file', fileWriteLog, ['book_file_id']],
|
||||
['Hardcover state book', hardcoverBookState, ['book_id']],
|
||||
['legacy Kobo snapshot book', koboLegacySnapshotBooks, ['book_id']],
|
||||
['Kobo device snapshot book', koboSnapshotBooks, ['book_id']],
|
||||
['Kobo reading state book', koboReadingStates, ['book_id']],
|
||||
['KOReader page stats book file', koreaderPageStats, ['book_file_id']],
|
||||
['reading attempt book', readingAttempts, ['book_id']],
|
||||
['reading session book', readingSessions, ['book_id']],
|
||||
['daily reading statistics library', userReadingDailyStats, ['library_id']],
|
||||
['audiobook progress book', audiobookProgress, ['book_id']],
|
||||
['audiobook progress current file', audiobookProgress, ['current_file_id']],
|
||||
['bookmark book', bookmarks, ['book_id']],
|
||||
['annotation book', annotations, ['book_id']],
|
||||
['annotation position book file', annotationPositions, ['book_file_id']],
|
||||
['reader preferences book file', readerPreferences, ['book_file_id']],
|
||||
['StoryGraph state book', storygraphBookState, ['book_id']],
|
||||
] as const;
|
||||
|
||||
describe('library deletion cascade indexes', () => {
|
||||
it.each(cascadeIndexCases)('indexes the %s foreign key with the referenced column first', (_label, table, expectedColumns) => {
|
||||
const config = getTableConfig(table as never);
|
||||
const foreignKeyColumns = config.foreignKeys.map((foreignKey) => foreignKey.reference().columns.map((column) => column.name));
|
||||
const leadingIndexColumns = config.indexes.map((tableIndex) =>
|
||||
tableIndex.config.columns.slice(0, expectedColumns.length).map((column) => column.name),
|
||||
);
|
||||
|
||||
expect(foreignKeyColumns).toContainEqual([...expectedColumns]);
|
||||
expect(leadingIndexColumns).toContainEqual([...expectedColumns]);
|
||||
});
|
||||
});
|
||||
@@ -68,6 +68,7 @@ export const readingAttempts = pgTable(
|
||||
},
|
||||
(t) => [
|
||||
index('reading_attempts_user_book_idx').on(t.userId, t.bookId, t.id),
|
||||
index('reading_attempts_book_id_idx').on(t.bookId),
|
||||
index('reading_attempts_user_outcome_ended_idx').on(t.userId, t.outcome, t.endedOn),
|
||||
uniqueIndex('reading_attempts_one_active_uidx')
|
||||
.on(t.userId, t.bookId)
|
||||
@@ -217,6 +218,7 @@ export const readingSessions = pgTable(
|
||||
index('rs_book_file_started_at_idx').on(t.bookFileId, t.startedAt),
|
||||
index('rs_user_book_file_idx').on(t.userId, t.bookFileId),
|
||||
index('rs_user_book_started_at_idx').on(t.userId, t.bookId, t.startedAt),
|
||||
index('reading_sessions_book_id_idx').on(t.bookId),
|
||||
index('rs_attempt_started_at_idx').on(t.attemptId, t.startedAt),
|
||||
check('reading_sessions_source_chk', sql`${t.source} in ('web', 'koreader', 'manual', 'kobo')`),
|
||||
check('reading_sessions_duration_seconds_nonnegative_chk', sql`${t.durationSeconds} >= 0`),
|
||||
@@ -249,6 +251,7 @@ export const userReadingDailyStats = pgTable(
|
||||
(t) => [
|
||||
primaryKey({ columns: [t.userId, t.libraryId, t.day] }),
|
||||
index('urds_user_day_idx').on(t.userId, t.day),
|
||||
index('user_reading_daily_stats_library_id_idx').on(t.libraryId),
|
||||
check('user_reading_daily_stats_reading_seconds_nonnegative_chk', sql`${t.readingSeconds} >= 0`),
|
||||
check('user_reading_daily_stats_sessions_count_nonnegative_chk', sql`${t.sessionsCount} >= 0`),
|
||||
],
|
||||
@@ -279,6 +282,8 @@ export const audiobookProgress = pgTable(
|
||||
(t) => [
|
||||
primaryKey({ columns: [t.userId, t.bookId] }),
|
||||
index('abp_user_id_idx').on(t.userId),
|
||||
index('audiobook_progress_book_id_idx').on(t.bookId),
|
||||
index('audiobook_progress_current_file_id_idx').on(t.currentFileId),
|
||||
check('audiobook_progress_percentage_range_chk', sql`${t.percentage} >= 0 and ${t.percentage} <= 100`),
|
||||
check('audiobook_progress_position_seconds_nonnegative_chk', sql`${t.positionSeconds} >= 0`),
|
||||
],
|
||||
@@ -306,6 +311,7 @@ export const bookmarks = pgTable(
|
||||
},
|
||||
(t) => [
|
||||
index('bookmarks_user_book_idx').on(t.userId, t.bookId),
|
||||
index('bookmarks_book_id_idx').on(t.bookId),
|
||||
uniqueIndex('bookmarks_user_book_cfi_uidx')
|
||||
.on(t.userId, t.bookId, t.cfi)
|
||||
.where(sql`${t.cfi} is not null`),
|
||||
@@ -352,6 +358,7 @@ export const annotations = pgTable(
|
||||
index('annotations_user_id_idx').on(t.userId),
|
||||
index('annotations_user_id_id_idx').on(t.userId, t.id),
|
||||
index('annotations_user_book_idx').on(t.userId, t.bookId),
|
||||
index('annotations_book_id_idx').on(t.bookId),
|
||||
index('annotations_user_book_active_idx')
|
||||
.on(t.userId, t.bookId)
|
||||
.where(sql`${t.deletedAt} is null`),
|
||||
@@ -391,6 +398,7 @@ export const annotationPositions = pgTable(
|
||||
(t) => [
|
||||
uniqueIndex('annotation_positions_annotation_format_uidx').on(t.annotationId, t.format),
|
||||
index('annotation_positions_user_idx').on(t.userId),
|
||||
index('annotation_positions_book_file_id_idx').on(t.bookFileId),
|
||||
index('annotation_positions_format_status_idx').on(t.format, t.status),
|
||||
check('annotation_positions_format_chk', sql`${t.format} in ('cfi', 'xpointer', 'pdf', 'kobo_span')`),
|
||||
check('annotation_positions_status_chk', sql`${t.status} in ('exact', 'repaired', 'failed', 'pending')`),
|
||||
@@ -479,7 +487,7 @@ export const readerPreferences = pgTable(
|
||||
.defaultNow()
|
||||
.$onUpdateFn(() => new Date()),
|
||||
},
|
||||
(t) => [uniqueIndex('rp_user_file_idx').on(t.userId, t.bookFileId)],
|
||||
(t) => [uniqueIndex('rp_user_file_idx').on(t.userId, t.bookFileId), index('reader_preferences_book_file_id_idx').on(t.bookFileId)],
|
||||
);
|
||||
|
||||
export type ReaderPreference = typeof readerPreferences.$inferSelect;
|
||||
|
||||
@@ -50,6 +50,7 @@ export const storygraphBookState = pgTable(
|
||||
},
|
||||
(t) => [
|
||||
unique('storygraph_book_state_user_book_uidx').on(t.userId, t.bookId),
|
||||
index('storygraph_book_state_book_id_idx').on(t.bookId),
|
||||
index('storygraph_book_state_user_sync_override_idx').on(t.userId, t.syncOverride, t.bookId),
|
||||
index('storygraph_book_state_user_sync_error_idx')
|
||||
.on(t.userId, t.bookId)
|
||||
|
||||
Reference in New Issue
Block a user