diff --git a/client/src/features/book/components/detail/tabs/DetailsTab.vue b/client/src/features/book/components/detail/tabs/DetailsTab.vue index bd37ebd56..900d13b43 100644 --- a/client/src/features/book/components/detail/tabs/DetailsTab.vue +++ b/client/src/features/book/components/detail/tabs/DetailsTab.vue @@ -117,7 +117,11 @@ function togglePersonalReview() { } const { weights: scoreWeights, fetchWeights } = useMetadataScoreWeights() -const { bookProgress: koreaderBookProgress, fetchBookProgress: fetchKoreaderProgress } = useKoreaderBookProgress() +const { + bookProgress: koreaderBookProgress, + fetchBookProgress: fetchKoreaderProgress, + releaseResetHold: releaseKoreaderResetHold, +} = useKoreaderBookProgress() onMounted(() => { void fetchWeights() @@ -867,6 +871,31 @@ function formatKoboDeviceNames(snapshots: BookKoboState['snapshots']): string { return `${snapshots.length} devices` } +const resetHeldDevices = computed(() => (canViewKoreader.value ? (koreaderBookProgress.value?.heldByReset ?? []) : [])) +const releasingDeviceIds = ref([]) +const failedReleaseDeviceIds = ref([]) + +function isReleasingHold(deviceId: string): boolean { + return releasingDeviceIds.value.includes(deviceId) +} + +function hasReleaseFailed(deviceId: string): boolean { + return failedReleaseDeviceIds.value.includes(deviceId) +} + +async function handleReleaseResetHold(deviceId: string) { + if (isReleasingHold(deviceId)) return + releasingDeviceIds.value = [...releasingDeviceIds.value, deviceId] + failedReleaseDeviceIds.value = failedReleaseDeviceIds.value.filter((id) => id !== deviceId) + try { + const released = await releaseKoreaderResetHold(props.book.id, deviceId) + // A button that re-enables with the hold still showing reads as nothing having happened. + if (!released) failedReleaseDeviceIds.value = [...failedReleaseDeviceIds.value, deviceId] + } finally { + releasingDeviceIds.value = releasingDeviceIds.value.filter((id) => id !== deviceId) + } +} + const koboAnomaly = computed(() => { if (!canViewKobo.value) return null const snapshots = koboState.value?.snapshots ?? [] @@ -1710,6 +1739,24 @@ watch( {{ t('book.detail.details.moreCount', { count: leftColumnProgressOverflow }) }}

+
+
@@ -2281,7 +2328,10 @@ watch( -
+
+
+
diff --git a/client/src/features/koreader/composables/__tests__/useKoreaderBookProgress.spec.ts b/client/src/features/koreader/composables/__tests__/useKoreaderBookProgress.spec.ts index 992528cc1..ed6169b92 100644 --- a/client/src/features/koreader/composables/__tests__/useKoreaderBookProgress.spec.ts +++ b/client/src/features/koreader/composables/__tests__/useKoreaderBookProgress.spec.ts @@ -27,6 +27,7 @@ function makeBookProgress(overrides: Partial = {}): Koread }, ], fileModifiedSinceLastSync: false, + heldByReset: [], ...overrides, } } @@ -116,4 +117,27 @@ describe('useKoreaderBookProgress', () => { expect(bookProgress.value).toBeNull() expect(loading.value).toBe(false) }) + + it('posts the device id when releasing a reset hold and refreshes the book', async () => { + apiMock.mockResolvedValue(makeResponse(makeBookProgress())) + const { useKoreaderBookProgress } = await import('../useKoreaderBookProgress') + const { releaseResetHold } = useKoreaderBookProgress() + + await expect(releaseResetHold(42, 'device-1')).resolves.toBe(true) + + expect(apiMock).toHaveBeenCalledWith( + '/api/v1/koreader/books/42/reset-hold/release', + expect.objectContaining({ method: 'POST', body: JSON.stringify({ deviceId: 'device-1' }) }), + ) + // The hold disappears from the book page only if the refreshed state is pulled back in. + expect(apiMock).toHaveBeenCalledWith('/api/v1/koreader/books/42/progress') + }) + + it('reports a failed release rather than pretending the hold is gone', async () => { + apiMock.mockResolvedValue(makeResponse(null, { ok: false, status: 404 })) + const { useKoreaderBookProgress } = await import('../useKoreaderBookProgress') + const { releaseResetHold } = useKoreaderBookProgress() + + await expect(releaseResetHold(42, 'device-1')).resolves.toBe(false) + }) }) diff --git a/client/src/features/koreader/composables/useKoreaderBookProgress.ts b/client/src/features/koreader/composables/useKoreaderBookProgress.ts index 5d06da40b..c6e276237 100644 --- a/client/src/features/koreader/composables/useKoreaderBookProgress.ts +++ b/client/src/features/koreader/composables/useKoreaderBookProgress.ts @@ -19,5 +19,16 @@ export function useKoreaderBookProgress() { } } - return { bookProgress, loading, fetchBookProgress } + async function releaseResetHold(bookId: number, deviceId: string): Promise { + const res = await api(`/api/v1/koreader/books/${bookId}/reset-hold/release`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ deviceId }), + }) + if (!res.ok) return false + await fetchBookProgress(bookId) + return true + } + + return { bookProgress, loading, fetchBookProgress, releaseResetHold } } diff --git a/client/src/locales/en.json b/client/src/locales/en.json index ba004cefe..e93bc312f 100644 --- a/client/src/locales/en.json +++ b/client/src/locales/en.json @@ -3908,7 +3908,11 @@ "koboNotSynced": "Not synced", "koboNotSyncedTooltip": "Queued for next Kobo sync.", "filesNotFound": "Files not found", - "filesNotFoundDescription": "The file(s) for this book can no longer be found on disk. Metadata is still available. Run a library scan to confirm, or remove the record." + "filesNotFoundDescription": "The file(s) for this book can no longer be found on disk. Metadata is still available. Run a library scan to confirm, or remove the record.", + "resetHoldNotice": "{device} still has the old position ({percent})", + "resetHoldRelease": "Use this device's position", + "resetHoldReleasing": "Releasing...", + "resetHoldReleaseFailed": "Could not use this position. Try again." }, "editMetadata": { "fieldCount": "{count, plural, =0 {no fields} one {one field} other {# fields}}", diff --git a/koreader-plugin/spec/bookorbit_progress_reset_test.lua b/koreader-plugin/spec/bookorbit_progress_reset_test.lua new file mode 100644 index 000000000..b6e8c3841 --- /dev/null +++ b/koreader-plugin/spec/bookorbit_progress_reset_test.lua @@ -0,0 +1,172 @@ +-- The server can only ask for a reset; the client decides whether to act on it. These cases +-- drive that decision, because a server-side test passes on a body the reader would discard. + +package.loaded["gettext"] = function(text) + return text +end + +package.loaded["ffi/util"] = { + template = function(text, ...) + local values = { ... } + return (text:gsub("%%(%d+)", function(index) + return tostring(values[tonumber(index)]) + end)) + end, +} + +local confirm_boxes = {} +package.loaded["ui/widget/confirmbox"] = { + new = function(_, opts) + table.insert(confirm_boxes, opts) + return opts + end, +} +package.loaded["device"] = { model = "Kobo Libra" } + +local events = {} +package.loaded["ui/event"] = { + new = function(_, name, value) + local event = { name = name, value = value } + table.insert(events, event) + return event + end, +} +package.loaded["ui/widget/infomessage"] = { new = function(_, opts) return opts end } +package.loaded["optmath"] = { + roundPercent = function(value) return value end, + round = function(value) return value end, +} +package.loaded["ui/network/manager"] = { + willRerunWhenConnected = function() return false end, +} + +local shown_texts = {} +package.loaded["ui/uimanager"] = { + show = function(_, message) + table.insert(shown_texts, message.text) + end, + scheduleIn = function() end, + unschedule = function() end, + getElapsedTimeSinceBoot = function() return 10000 end, +} +package.loaded["logger"] = { dbg = function() end } +package.loaded["ui/time"] = { s = function(value) return value end } +package.loaded["util"] = { partialMD5 = function(file) return "digest:" .. tostring(file) end } + +package.path = "koreader-plugin/bookorbit.koplugin/?.lua;" .. package.path + +local ProgressSync = require("bookorbit_progress_sync") + +local function assertEqual(actual, expected, label) + if actual ~= expected then + error(string.format("%s: expected %s, got %s", label, tostring(expected), tostring(actual))) + end +end + +local function assertTrue(value, label) + if not value then error(label .. ": expected truthy, got " .. tostring(value)) end +end + +--- Builds a plugin sitting at `local_percent` in a book, ready to pull `body` from the server. +local function makePlugin(body, opts) + opts = opts or {} + confirm_boxes = {} + events = {} + shown_texts = {} + + local plugin = { + SYNC_STRATEGY = { PROMPT = 1, SILENT = 2, DISABLE = 3 }, + } + ProgressSync.install(plugin) + + plugin.pull_timestamp = 0 + plugin.push_timestamp = 0 + plugin.device_id = "this-device" + -- Defaults as shipped: forward prompts, backward is disabled. + plugin.settings = { sync_forward = 1, sync_backward = 3, auto_sync = true } + plugin.last_page_turn_timestamp = opts.last_page_turn_timestamp or 0 + plugin.ui = { + document = { + file = "/tmp/book" .. (opts.paged and ".cbz" or ".epub"), + info = { has_pages = opts.paged == true }, + }, + handleEvent = function() end, + } + plugin.isLoggedIn = function() return true end + plugin.getLastProgress = function() return opts.local_progress or "/body/DocFragment[6]/body" end + plugin.getLastPercent = function() return opts.local_percent or 0.42 end + plugin.requestUpdateCheck = function() end + plugin.recordSyncSuccess = function() end + plugin.recordSyncError = function() end + plugin.newClient = function() + return { getProgress = function() return body end } + end + return plugin +end + +local RESET_BODY_REFLOWABLE = { + percentage = 0, + progress = "/body/DocFragment[1]/body", + device = "web", + device_id = "bookorbit-web", + timestamp = 5000, +} + +-- 1. The reset must not be mistaken for this device's own progress, or it is skipped outright. +local plugin = makePlugin(RESET_BODY_REFLOWABLE) +plugin:getProgress(false, false) +assertEqual(#confirm_boxes, 1, "a reset from BookOrbit reaches the conflict prompt") + +-- 2. Accepting it has to actually move the reader to the start. +confirm_boxes[1].ok_callback() +assertEqual(events[1].name, "GotoXPointer", "reflowable reset navigates by xpointer") +assertEqual(events[1].value, "/body/DocFragment[1]/body", "reflowable reset targets the first fragment") + +-- 3. Paged books take a page number, and an empty position would arrive as nil. +local paged = makePlugin({ + percentage = 0, + progress = "1", + device = "web", + device_id = "bookorbit-web", + timestamp = 5000, +}, { paged = true, local_progress = "117" }) +paged:getProgress(false, false) +assertEqual(#confirm_boxes, 1, "a paged reset reaches the conflict prompt") +confirm_boxes[1].ok_callback() +assertEqual(events[1].name, "GotoPage", "paged reset navigates by page") +assertEqual(events[1].value, 1, "paged reset targets page one") + +-- 4. The classification is what decides whether the reader is ever asked. A reset stamped +-- behind the last page turn is read as a backward sync, and backward defaults to disabled, +-- so it is dropped in silence. This is why the server stamps each delivery as current. +local stale = makePlugin(RESET_BODY_REFLOWABLE, { last_page_turn_timestamp = 9000 }) +stale:getProgress(false, false) +assertEqual(#confirm_boxes, 0, "a reset older than the last page turn is silently discarded") + +local fresh = makePlugin({ + percentage = 0, + progress = "/body/DocFragment[1]/body", + device = "web", + device_id = "bookorbit-web", + timestamp = 9500, +}, { last_page_turn_timestamp = 9000 }) +fresh:getProgress(false, false) +assertEqual(#confirm_boxes, 1, "a reset stamped after the last page turn still prompts") + +-- 5. A device already at the start has nothing to do, and must not be nagged. +local converged = makePlugin(RESET_BODY_REFLOWABLE, { local_percent = 0 }) +converged:getProgress(false, false) +assertEqual(#confirm_boxes, 0, "a device already at the start is not prompted") + +-- 6. The reset must never be attributed to the pulling device itself, or the guard skips it. +local own = makePlugin({ + percentage = 0, + progress = "/body/DocFragment[1]/body", + device = "Kobo Libra", + device_id = "this-device", + timestamp = 5000, +}) +own:getProgress(false, false) +assertEqual(#confirm_boxes, 0, "progress from this device is skipped, which is why the reset is not attributed to it") + +print("bookorbit_progress_reset_test.lua: ok") diff --git a/packages/types/src/koreader.ts b/packages/types/src/koreader.ts index de124d587..81a823aff 100644 --- a/packages/types/src/koreader.ts +++ b/packages/types/src/koreader.ts @@ -79,6 +79,19 @@ export interface KoreaderBookSyncInfo { canonicalUpdatedAt: string; devices: KoreaderBookProgress[]; fileModifiedSinceLastSync: boolean; + /** + * Devices sitting on a position the user reset away from. Their pushes are recorded but do + * not move the book until the device takes the reset, so this is the state behind a device + * and a book that visibly disagree. + */ + heldByReset: KoreaderResetHeldDevice[]; +} + +export interface KoreaderResetHeldDevice { + device: string; + deviceId: string; + percentage: number; + updatedAt: string; } export interface KoreaderUnmatchedBook { diff --git a/scripts/e2e/suite-registry.mjs b/scripts/e2e/suite-registry.mjs index 968f4e3a8..81ade68a2 100644 --- a/scripts/e2e/suite-registry.mjs +++ b/scripts/e2e/suite-registry.mjs @@ -551,6 +551,26 @@ export const E2E_SUITES = Object.freeze({ ...SHARED_DB_AND_HELPER_PATHS, ], }, + "koreader-progress-reset": { + id: "koreader-progress-reset", + name: "KOReader Progress Reset Propagation", + timeout: 45, + lane: "full", + description: "KOReader reset propagation: a cleared position survives a device replaying its old one", + vitestTarget: "test/koreader-progress-reset.e2e-spec.ts", + junitOutput: `${TEST_RESULTS_DIR}/koreader-progress-reset-e2e-junit.xml`, + prepareDedicatedDatabase: true, + useDedicatedDatabase: true, + changedPaths: [ + "server/src/modules/koreader/**", + "server/src/modules/book/book.repository.ts", + "server/src/modules/reading-state/**", + "server/src/db/schema/koreader.ts", + "server/test/koreader-progress-reset.e2e-spec.ts", + "server/test/e2e/reader-state-isolation/**", + ...SHARED_DB_AND_HELPER_PATHS, + ], + }, "koreader-progress-position": { id: "koreader-progress-position", name: "KOReader Progress Position Routing", diff --git a/server/src/db/migrations/0070_add_koreader_progress_resets.sql b/server/src/db/migrations/0070_add_koreader_progress_resets.sql new file mode 100644 index 000000000..ee90f3ba3 --- /dev/null +++ b/server/src/db/migrations/0070_add_koreader_progress_resets.sql @@ -0,0 +1,10 @@ +CREATE TABLE "koreader_progress_resets" ( + "user_id" integer NOT NULL, + "book_file_id" integer NOT NULL, + "reset_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "koreader_progress_resets_user_id_book_file_id_pk" PRIMARY KEY("user_id","book_file_id") +); +--> statement-breakpoint +ALTER TABLE "koreader_progress_resets" ADD CONSTRAINT "koreader_progress_resets_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "koreader_progress_resets" ADD CONSTRAINT "koreader_progress_resets_book_file_id_book_files_id_fk" FOREIGN KEY ("book_file_id") REFERENCES "public"."book_files"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "koreader_progress_resets_book_file_id_idx" ON "koreader_progress_resets" USING btree ("book_file_id"); \ No newline at end of file diff --git a/server/src/db/migrations/0071_add_koreader_progress_reset_devices.sql b/server/src/db/migrations/0071_add_koreader_progress_reset_devices.sql new file mode 100644 index 000000000..ae1bbfdac --- /dev/null +++ b/server/src/db/migrations/0071_add_koreader_progress_reset_devices.sql @@ -0,0 +1,9 @@ +CREATE TABLE "koreader_progress_reset_devices" ( + "user_id" integer NOT NULL, + "book_file_id" integer NOT NULL, + "device_id" varchar(100) NOT NULL, + "converged_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "koreader_progress_reset_devices_user_id_book_file_id_device_id_pk" PRIMARY KEY("user_id","book_file_id","device_id") +); +--> statement-breakpoint +ALTER TABLE "koreader_progress_reset_devices" ADD CONSTRAINT "koreader_progress_reset_devices_reset_fk" FOREIGN KEY ("user_id","book_file_id") REFERENCES "public"."koreader_progress_resets"("user_id","book_file_id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/server/src/db/migrations/meta/0070_snapshot.json b/server/src/db/migrations/meta/0070_snapshot.json new file mode 100644 index 000000000..97d4015a1 --- /dev/null +++ b/server/src/db/migrations/meta/0070_snapshot.json @@ -0,0 +1,16231 @@ +{ + "id": "bbb24e2e-c4ed-4518-86ea-41c30a01c4a5", + "prevId": "9c949bea-0fce-4003-bc75-a2853bfd02bd", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "actor_username": { + "name": "actor_username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "resource_id": { + "name": "resource_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_user_id": { + "name": "idx_audit_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_actor_username": { + "name": "idx_audit_actor_username", + "columns": [ + { + "expression": "actor_username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_actor_username_trgm": { + "name": "idx_audit_actor_username_trgm", + "columns": [ + { + "expression": "actor_username", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_audit_resource": { + "name": "idx_audit_resource", + "columns": [ + { + "expression": "resource", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_resource_id": { + "name": "idx_audit_resource_id", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_resource_trgm": { + "name": "idx_audit_resource_trgm", + "columns": [ + { + "expression": "resource", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_audit_action": { + "name": "idx_audit_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_action_trgm": { + "name": "idx_audit_action_trgm", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_audit_description_trgm": { + "name": "idx_audit_description_trgm", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_audit_meta_text_trgm": { + "name": "idx_audit_meta_text_trgm", + "columns": [ + { + "expression": "(\"meta\"::text) gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_audit_ip": { + "name": "idx_audit_ip", + "columns": [ + { + "expression": "ip", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_created_at": { + "name": "idx_audit_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_reading_insights_access": { + "name": "idx_audit_reading_insights_access", + "columns": [ + { + "expression": "resource", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_user_id_users_id_fk": { + "name": "audit_log_user_id_users_id_fk", + "tableFrom": "audit_log", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_settings": { + "name": "app_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "app_settings_key_unique": { + "name": "app_settings_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.magic_access_tokens": { + "name": "magic_access_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "raw_token": { + "name": "raw_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "use_count": { + "name": "use_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "magic_access_tokens_user_id_idx": { + "name": "magic_access_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "magic_access_tokens_user_id_users_id_fk": { + "name": "magic_access_tokens_user_id_users_id_fk", + "tableFrom": "magic_access_tokens", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "magic_access_tokens_created_by_users_id_fk": { + "name": "magic_access_tokens_created_by_users_id_fk", + "tableFrom": "magic_access_tokens", + "tableTo": "users", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "magic_access_tokens_token_hash_unique": { + "name": "magic_access_tokens_token_hash_unique", + "nullsNotDistinct": false, + "columns": ["token_hash"] + } + }, + "policies": {}, + "checkConstraints": { + "magic_access_tokens_use_count_nonneg_chk": { + "name": "magic_access_tokens_use_count_nonneg_chk", + "value": "\"magic_access_tokens\".\"use_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "password_reset_tokens_user_id_idx": { + "name": "password_reset_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "password_reset_tokens_expires_at_idx": { + "name": "password_reset_tokens_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_tokens_token_hash_unique": { + "name": "password_reset_tokens_token_hash_unique", + "nullsNotDistinct": false, + "columns": ["token_hash"] + } + }, + "policies": {}, + "checkConstraints": { + "password_reset_tokens_expires_after_created_chk": { + "name": "password_reset_tokens_expires_after_created_chk", + "value": "\"password_reset_tokens\".\"expires_at\" > \"password_reset_tokens\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.refresh_tokens": { + "name": "refresh_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "replaced_by_token_hash": { + "name": "replaced_by_token_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "refresh_tokens_user_id_idx": { + "name": "refresh_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "refresh_tokens_expires_at_idx": { + "name": "refresh_tokens_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "refresh_tokens_user_id_users_id_fk": { + "name": "refresh_tokens_user_id_users_id_fk", + "tableFrom": "refresh_tokens", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "refresh_tokens_token_hash_unique": { + "name": "refresh_tokens_token_hash_unique", + "nullsNotDistinct": false, + "columns": ["token_hash"] + } + }, + "policies": {}, + "checkConstraints": { + "refresh_tokens_expires_after_created_chk": { + "name": "refresh_tokens_expires_after_created_chk", + "value": "\"refresh_tokens\".\"expires_at\" > \"refresh_tokens\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.user_content_filter_genres": { + "name": "user_content_filter_genres", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "filter_type": { + "name": "filter_type", + "type": "content_filter_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "genre_id": { + "name": "genre_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "user_content_filter_genres_genre_id_idx": { + "name": "user_content_filter_genres_genre_id_idx", + "columns": [ + { + "expression": "genre_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_content_filter_genres_user_id_users_id_fk": { + "name": "user_content_filter_genres_user_id_users_id_fk", + "tableFrom": "user_content_filter_genres", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_content_filter_genres_genre_id_genres_id_fk": { + "name": "user_content_filter_genres_genre_id_genres_id_fk", + "tableFrom": "user_content_filter_genres", + "tableTo": "genres", + "columnsFrom": ["genre_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_content_filter_genres_user_id_filter_type_genre_id_pk": { + "name": "user_content_filter_genres_user_id_filter_type_genre_id_pk", + "columns": ["user_id", "filter_type", "genre_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_content_filter_tags": { + "name": "user_content_filter_tags", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "filter_type": { + "name": "filter_type", + "type": "content_filter_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "user_content_filter_tags_tag_id_idx": { + "name": "user_content_filter_tags_tag_id_idx", + "columns": [ + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_content_filter_tags_user_id_users_id_fk": { + "name": "user_content_filter_tags_user_id_users_id_fk", + "tableFrom": "user_content_filter_tags", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_content_filter_tags_tag_id_tags_id_fk": { + "name": "user_content_filter_tags_tag_id_tags_id_fk", + "tableFrom": "user_content_filter_tags", + "tableTo": "tags", + "columnsFrom": ["tag_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_content_filter_tags_user_id_filter_type_tag_id_pk": { + "name": "user_content_filter_tags_user_id_filter_type_tag_id_pk", + "columns": ["user_id", "filter_type", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library_access": { + "name": "user_library_access", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "access_level": { + "name": "access_level", + "type": "library_access_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'viewer'" + } + }, + "indexes": { + "user_library_access_library_user_idx": { + "name": "user_library_access_library_user_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_access_user_id_users_id_fk": { + "name": "user_library_access_user_id_users_id_fk", + "tableFrom": "user_library_access", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_access_library_id_libraries_id_fk": { + "name": "user_library_access_library_id_libraries_id_fk", + "tableFrom": "user_library_access", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_library_access_user_id_library_id_pk": { + "name": "user_library_access_user_id_library_id_pk", + "columns": ["user_id", "library_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_permissions": { + "name": "user_permissions", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "permission_name": { + "name": "permission_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_permissions_user_id_users_id_fk": { + "name": "user_permissions_user_id_users_id_fk", + "tableFrom": "user_permissions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_permissions_user_id_permission_name_pk": { + "name": "user_permissions_user_id_permission_name_pk", + "columns": ["user_id", "permission_name"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_superuser": { + "name": "is_superuser", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default_password": { + "name": "is_default_password", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "token_version": { + "name": "token_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "failed_login_attempts": { + "name": "failed_login_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "locked_until": { + "name": "locked_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "oidc_subject": { + "name": "oidc_subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oidc_issuer": { + "name": "oidc_issuer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_source": { + "name": "avatar_source", + "type": "user_avatar_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "avatar_version": { + "name": "avatar_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "provisioning_method": { + "name": "provisioning_method", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'local'" + }, + "last_login_at": { + "name": "last_login_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_authenticated_at": { + "name": "last_authenticated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reading_insights_sharing_level": { + "name": "reading_insights_sharing_level", + "type": "reading_insights_sharing_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'private'" + }, + "reading_insights_consented_at": { + "name": "reading_insights_consented_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_username_lower_uidx": { + "name": "users_username_lower_uidx", + "columns": [ + { + "expression": "lower(\"username\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_email_lower_uidx": { + "name": "users_email_lower_uidx", + "columns": [ + { + "expression": "lower(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"users\".\"email\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_oidc_subject_issuer_uidx": { + "name": "users_oidc_subject_issuer_uidx", + "columns": [ + { + "expression": "oidc_subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "oidc_issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"users\".\"oidc_subject\" is not null and \"users\".\"oidc_issuer\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_last_authenticated_at_idx": { + "name": "users_last_authenticated_at_idx", + "columns": [ + { + "expression": "last_authenticated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": ["username"] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": { + "users_provisioning_method_chk": { + "name": "users_provisioning_method_chk", + "value": "\"users\".\"provisioning_method\" in ('local', 'manual', 'oidc', 'shared')" + }, + "users_token_version_nonnegative_chk": { + "name": "users_token_version_nonnegative_chk", + "value": "\"users\".\"token_version\" >= 0" + }, + "users_failed_login_attempts_nonnegative_chk": { + "name": "users_failed_login_attempts_nonnegative_chk", + "value": "\"users\".\"failed_login_attempts\" >= 0" + }, + "users_avatar_version_nonnegative_chk": { + "name": "users_avatar_version_nonnegative_chk", + "value": "\"users\".\"avatar_version\" >= 0" + }, + "users_reading_insights_consent_chk": { + "name": "users_reading_insights_consent_chk", + "value": "(\"users\".\"reading_insights_sharing_level\" = 'private' and \"users\".\"reading_insights_consented_at\" is null) or (\"users\".\"reading_insights_sharing_level\" <> 'private' and \"users\".\"reading_insights_consented_at\" is not null)" + } + }, + "isRLSEnabled": false + }, + "public.comic_metadata": { + "name": "comic_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "issue_number": { + "name": "issue_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volume_name": { + "name": "volume_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pencillers": { + "name": "pencillers", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "inkers": { + "name": "inkers", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "colorists": { + "name": "colorists", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "letterers": { + "name": "letterers", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "cover_artists": { + "name": "cover_artists", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "characters": { + "name": "characters", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "teams": { + "name": "teams", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "locations": { + "name": "locations", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "story_arcs": { + "name": "story_arcs", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "comic_metadata_book_id_books_id_fk": { + "name": "comic_metadata_book_id_books_id_fk", + "tableFrom": "comic_metadata", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "comic_metadata_book_id_unique": { + "name": "comic_metadata_book_id_unique", + "nullsNotDistinct": false, + "columns": ["book_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_custom_metadata_values": { + "name": "book_custom_metadata_values", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "field_id": { + "name": "field_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value_text": { + "name": "value_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value_number": { + "name": "value_number", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "value_date": { + "name": "value_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "value_boolean": { + "name": "value_boolean", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_custom_metadata_values_field_idx": { + "name": "book_custom_metadata_values_field_idx", + "columns": [ + { + "expression": "field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_custom_metadata_values_book_id_books_id_fk": { + "name": "book_custom_metadata_values_book_id_books_id_fk", + "tableFrom": "book_custom_metadata_values", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_custom_metadata_values_field_id_custom_metadata_fields_id_fk": { + "name": "book_custom_metadata_values_field_id_custom_metadata_fields_id_fk", + "tableFrom": "book_custom_metadata_values", + "tableTo": "custom_metadata_fields", + "columnsFrom": ["field_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_custom_metadata_values_book_id_field_id_pk": { + "name": "book_custom_metadata_values_book_id_field_id_pk", + "columns": ["book_id", "field_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_custom_metadata_values_single_value_chk": { + "name": "book_custom_metadata_values_single_value_chk", + "value": "(\n (\"book_custom_metadata_values\".\"value_text\" is not null)::int +\n (\"book_custom_metadata_values\".\"value_number\" is not null)::int +\n (\"book_custom_metadata_values\".\"value_date\" is not null)::int +\n (\"book_custom_metadata_values\".\"value_boolean\" is not null)::int\n ) <= 1" + } + }, + "isRLSEnabled": false + }, + "public.custom_metadata_fields": { + "name": "custom_metadata_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_metadata_fields_key_uidx": { + "name": "custom_metadata_fields_key_uidx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_metadata_fields_active_order_idx": { + "name": "custom_metadata_fields_active_order_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "label", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "custom_metadata_fields_type_chk": { + "name": "custom_metadata_fields_type_chk", + "value": "\"custom_metadata_fields\".\"type\" in ('text', 'url', 'number', 'date', 'boolean')" + }, + "custom_metadata_fields_key_format_chk": { + "name": "custom_metadata_fields_key_format_chk", + "value": "\"custom_metadata_fields\".\"key\" ~ '^[a-z0-9][a-z0-9_]{0,99}$'" + }, + "custom_metadata_fields_label_not_blank_chk": { + "name": "custom_metadata_fields_label_not_blank_chk", + "value": "length(btrim(\"custom_metadata_fields\".\"label\")) > 0" + }, + "custom_metadata_fields_display_order_nonnegative_chk": { + "name": "custom_metadata_fields_display_order_nonnegative_chk", + "value": "\"custom_metadata_fields\".\"display_order\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.custom_metadata_library_fields": { + "name": "custom_metadata_library_fields", + "schema": "", + "columns": { + "field_id": { + "name": "field_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_metadata_library_fields_field_idx": { + "name": "custom_metadata_library_fields_field_idx", + "columns": [ + { + "expression": "field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_metadata_library_fields_library_order_idx": { + "name": "custom_metadata_library_fields_library_order_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_metadata_library_fields_field_id_custom_metadata_fields_id_fk": { + "name": "custom_metadata_library_fields_field_id_custom_metadata_fields_id_fk", + "tableFrom": "custom_metadata_library_fields", + "tableTo": "custom_metadata_fields", + "columnsFrom": ["field_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_metadata_library_fields_library_id_libraries_id_fk": { + "name": "custom_metadata_library_fields_library_id_libraries_id_fk", + "tableFrom": "custom_metadata_library_fields", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "custom_metadata_library_fields_library_id_field_id_pk": { + "name": "custom_metadata_library_fields_library_id_field_id_pk", + "columns": ["library_id", "field_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "custom_metadata_library_fields_display_order_nonnegative_chk": { + "name": "custom_metadata_library_fields_display_order_nonnegative_chk", + "value": "\"custom_metadata_library_fields\".\"display_order\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.custom_icons": { + "name": "custom_icons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(80)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(120)", + "primaryKey": false, + "notNull": true + }, + "original_file_name": { + "name": "original_file_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "stored_file_name": { + "name": "stored_file_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_icons_slug_uidx": { + "name": "custom_icons_slug_uidx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "custom_icons_slug_chk": { + "name": "custom_icons_slug_chk", + "value": "\"custom_icons\".\"slug\" ~ '^[a-z0-9][a-z0-9-]*[a-z0-9]$' or \"custom_icons\".\"slug\" ~ '^[a-z0-9]$'" + }, + "custom_icons_file_size_positive_chk": { + "name": "custom_icons_file_size_positive_chk", + "value": "\"custom_icons\".\"file_size\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.migration_plan_artifacts": { + "name": "migration_plan_artifacts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_snapshot_hash": { + "name": "source_snapshot_hash", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "profile_hash": { + "name": "profile_hash", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "plan_hash": { + "name": "plan_hash", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source_data": { + "name": "source_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "migration_plan_artifacts_plan_hash_uidx": { + "name": "migration_plan_artifacts_plan_hash_uidx", + "columns": [ + { + "expression": "plan_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_plan_artifacts_source_id_idx": { + "name": "migration_plan_artifacts_source_id_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_plan_artifacts_profile_id_idx": { + "name": "migration_plan_artifacts_profile_id_idx", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_plan_artifacts_source_id_migration_sources_id_fk": { + "name": "migration_plan_artifacts_source_id_migration_sources_id_fk", + "tableFrom": "migration_plan_artifacts", + "tableTo": "migration_sources", + "columnsFrom": ["source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_plan_artifacts_profile_id_migration_profiles_id_fk": { + "name": "migration_plan_artifacts_profile_id_migration_profiles_id_fk", + "tableFrom": "migration_plan_artifacts", + "tableTo": "migration_profiles", + "columnsFrom": ["profile_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_plan_artifacts_created_by_user_id_users_id_fk": { + "name": "migration_plan_artifacts_created_by_user_id_users_id_fk", + "tableFrom": "migration_plan_artifacts", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "migration_plan_artifacts_profile_source_fk": { + "name": "migration_plan_artifacts_profile_source_fk", + "tableFrom": "migration_plan_artifacts", + "tableTo": "migration_profiles", + "columnsFrom": ["profile_id", "source_id"], + "columnsTo": ["id", "source_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "migration_plan_artifacts_id_source_profile_unique": { + "name": "migration_plan_artifacts_id_source_profile_unique", + "nullsNotDistinct": false, + "columns": ["id", "source_id", "profile_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_profiles": { + "name": "migration_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "user_mappings": { + "name": "user_mappings", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "path_mappings": { + "name": "path_mappings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "migration_profiles_source_name_version_uidx": { + "name": "migration_profiles_source_name_version_uidx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_profiles_source_id_idx": { + "name": "migration_profiles_source_id_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_profiles_source_id_migration_sources_id_fk": { + "name": "migration_profiles_source_id_migration_sources_id_fk", + "tableFrom": "migration_profiles", + "tableTo": "migration_sources", + "columnsFrom": ["source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_profiles_created_by_user_id_users_id_fk": { + "name": "migration_profiles_created_by_user_id_users_id_fk", + "tableFrom": "migration_profiles", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "migration_profiles_id_source_id_unique": { + "name": "migration_profiles_id_source_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "source_id"] + } + }, + "policies": {}, + "checkConstraints": { + "migration_profiles_version_positive_chk": { + "name": "migration_profiles_version_positive_chk", + "value": "\"migration_profiles\".\"version\" >= 1" + } + }, + "isRLSEnabled": false + }, + "public.migration_run_metrics": { + "name": "migration_run_metrics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "stage": { + "name": "stage", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "processed": { + "name": "processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "imported": { + "name": "imported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped": { + "name": "skipped", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "unresolved": { + "name": "unresolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed": { + "name": "failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "migration_run_metrics_run_stage_entity_uidx": { + "name": "migration_run_metrics_run_stage_entity_uidx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_run_metrics_run_id_idx": { + "name": "migration_run_metrics_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_run_metrics_run_id_migration_runs_id_fk": { + "name": "migration_run_metrics_run_id_migration_runs_id_fk", + "tableFrom": "migration_run_metrics", + "tableTo": "migration_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "migration_run_metrics_processed_nonnegative_chk": { + "name": "migration_run_metrics_processed_nonnegative_chk", + "value": "\"migration_run_metrics\".\"processed\" >= 0" + }, + "migration_run_metrics_imported_nonnegative_chk": { + "name": "migration_run_metrics_imported_nonnegative_chk", + "value": "\"migration_run_metrics\".\"imported\" >= 0" + }, + "migration_run_metrics_skipped_nonnegative_chk": { + "name": "migration_run_metrics_skipped_nonnegative_chk", + "value": "\"migration_run_metrics\".\"skipped\" >= 0" + }, + "migration_run_metrics_unresolved_nonnegative_chk": { + "name": "migration_run_metrics_unresolved_nonnegative_chk", + "value": "\"migration_run_metrics\".\"unresolved\" >= 0" + }, + "migration_run_metrics_failed_nonnegative_chk": { + "name": "migration_run_metrics_failed_nonnegative_chk", + "value": "\"migration_run_metrics\".\"failed\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.migration_runs": { + "name": "migration_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "plan_artifact_id": { + "name": "plan_artifact_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "target_key": { + "name": "target_key", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'bookorbit'" + }, + "state": { + "name": "state", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "current_stage": { + "name": "current_stage", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "triggered_by_user_id": { + "name": "triggered_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "migration_runs_source_target_state_idx": { + "name": "migration_runs_source_target_state_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_runs_state_idx": { + "name": "migration_runs_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_runs_source_id_migration_sources_id_fk": { + "name": "migration_runs_source_id_migration_sources_id_fk", + "tableFrom": "migration_runs", + "tableTo": "migration_sources", + "columnsFrom": ["source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_runs_profile_id_migration_profiles_id_fk": { + "name": "migration_runs_profile_id_migration_profiles_id_fk", + "tableFrom": "migration_runs", + "tableTo": "migration_profiles", + "columnsFrom": ["profile_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_runs_plan_artifact_id_migration_plan_artifacts_id_fk": { + "name": "migration_runs_plan_artifact_id_migration_plan_artifacts_id_fk", + "tableFrom": "migration_runs", + "tableTo": "migration_plan_artifacts", + "columnsFrom": ["plan_artifact_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "migration_runs_triggered_by_user_id_users_id_fk": { + "name": "migration_runs_triggered_by_user_id_users_id_fk", + "tableFrom": "migration_runs", + "tableTo": "users", + "columnsFrom": ["triggered_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "migration_runs_profile_source_fk": { + "name": "migration_runs_profile_source_fk", + "tableFrom": "migration_runs", + "tableTo": "migration_profiles", + "columnsFrom": ["profile_id", "source_id"], + "columnsTo": ["id", "source_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "migration_runs_plan_artifact_source_profile_fk": { + "name": "migration_runs_plan_artifact_source_profile_fk", + "tableFrom": "migration_runs", + "tableTo": "migration_plan_artifacts", + "columnsFrom": ["plan_artifact_id", "source_id", "profile_id"], + "columnsTo": ["id", "source_id", "profile_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "migration_runs_state_chk": { + "name": "migration_runs_state_chk", + "value": "\"state\" IN ('draft', 'preflight_failed', 'dry_run_ready', 'running', 'failed', 'completed')" + }, + "migration_runs_started_before_ended_chk": { + "name": "migration_runs_started_before_ended_chk", + "value": "\"migration_runs\".\"ended_at\" is null or \"migration_runs\".\"started_at\" is null or \"migration_runs\".\"ended_at\" >= \"migration_runs\".\"started_at\"" + } + }, + "isRLSEnabled": false + }, + "public.migration_sources": { + "name": "migration_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "connection_config": { + "name": "connection_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_validated_at": { + "name": "last_validated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "migration_sources_type_name_uidx": { + "name": "migration_sources_type_name_uidx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_sources_created_by_user_id_idx": { + "name": "migration_sources_created_by_user_id_idx", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_sources_created_by_user_id_users_id_fk": { + "name": "migration_sources_created_by_user_id_users_id_fk", + "tableFrom": "migration_sources", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.libraries": { + "name": "libraries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cover_aspect_ratio": { + "name": "cover_aspect_ratio", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'2/3'" + }, + "watch": { + "name": "watch", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auto_scan_cron_expression": { + "name": "auto_scan_cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_precedence": { + "name": "metadata_precedence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"folderStructure\",\"embedded\",\"nfoFile\",\"opfFile\",\"sidecar\"]'::jsonb" + }, + "format_priority": { + "name": "format_priority", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"epub\",\"pdf\",\"cbz\",\"cbr\",\"cb7\",\"mobi\",\"azw3\",\"azw\",\"fb2\",\"m4b\",\"mp3\",\"m4a\",\"opus\",\"ogg\",\"flac\"]'::jsonb" + }, + "allowed_formats": { + "name": "allowed_formats", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "organization_mode": { + "name": "organization_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'book_per_folder'" + }, + "exclude_patterns": { + "name": "exclude_patterns", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reading_threshold": { + "name": "reading_threshold", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0.25 + }, + "mark_as_finished_percent_complete": { + "name": "mark_as_finished_percent_complete", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 98 + }, + "file_write_enabled": { + "name": "file_write_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_write_write_cover": { + "name": "file_write_write_cover", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "file_write_epub_enabled": { + "name": "file_write_epub_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "file_write_epub_max_file_size_mb": { + "name": "file_write_epub_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "file_write_fb2_enabled": { + "name": "file_write_fb2_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_write_fb2_max_file_size_mb": { + "name": "file_write_fb2_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "file_write_pdf_enabled": { + "name": "file_write_pdf_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "file_write_pdf_max_file_size_mb": { + "name": "file_write_pdf_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "file_write_cbx_enabled": { + "name": "file_write_cbx_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_write_cbx_max_file_size_mb": { + "name": "file_write_cbx_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "file_write_kindle_enabled": { + "name": "file_write_kindle_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_write_kindle_max_file_size_mb": { + "name": "file_write_kindle_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "file_write_audio_enabled": { + "name": "file_write_audio_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_write_audio_max_file_size_mb": { + "name": "file_write_audio_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "file_rename_enabled": { + "name": "file_rename_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_naming_pattern": { + "name": "file_naming_pattern", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "metadata_fetch_preferences": { + "name": "metadata_fetch_preferences", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "book_metadata_fetch_config": { + "name": "book_metadata_fetch_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "book_metadata_fetch_last_run_at": { + "name": "book_metadata_fetch_last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "book_metadata_fetch_last_queued_count": { + "name": "book_metadata_fetch_last_queued_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "scan_mode": { + "name": "scan_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'auto'" + }, + "poll_interval_seconds": { + "name": "poll_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 300 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "libraries_name_lower_uidx": { + "name": "libraries_name_lower_uidx", + "columns": [ + { + "expression": "lower(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "libraries_display_order_nonnegative_chk": { + "name": "libraries_display_order_nonnegative_chk", + "value": "\"libraries\".\"display_order\" >= 0" + }, + "libraries_organization_mode_chk": { + "name": "libraries_organization_mode_chk", + "value": "\"libraries\".\"organization_mode\" in ('book_per_folder', 'book_per_file')" + }, + "libraries_reading_threshold_range_chk": { + "name": "libraries_reading_threshold_range_chk", + "value": "\"libraries\".\"reading_threshold\" >= 0 and \"libraries\".\"reading_threshold\" <= 100" + }, + "libraries_mark_finished_percent_range_chk": { + "name": "libraries_mark_finished_percent_range_chk", + "value": "\"libraries\".\"mark_as_finished_percent_complete\" >= 0 and \"libraries\".\"mark_as_finished_percent_complete\" <= 100" + }, + "libraries_scan_mode_chk": { + "name": "libraries_scan_mode_chk", + "value": "\"libraries\".\"scan_mode\" in ('auto', 'manual')" + }, + "libraries_poll_interval_nonnegative_chk": { + "name": "libraries_poll_interval_nonnegative_chk", + "value": "\"libraries\".\"poll_interval_seconds\" is null or \"libraries\".\"poll_interval_seconds\" >= 0" + }, + "libraries_file_write_epub_max_size_chk": { + "name": "libraries_file_write_epub_max_size_chk", + "value": "\"libraries\".\"file_write_epub_max_file_size_mb\" >= 1" + }, + "libraries_file_write_pdf_max_size_chk": { + "name": "libraries_file_write_pdf_max_size_chk", + "value": "\"libraries\".\"file_write_pdf_max_file_size_mb\" >= 1" + }, + "libraries_file_write_cbx_max_size_chk": { + "name": "libraries_file_write_cbx_max_size_chk", + "value": "\"libraries\".\"file_write_cbx_max_file_size_mb\" >= 1" + }, + "libraries_file_write_kindle_max_size_chk": { + "name": "libraries_file_write_kindle_max_size_chk", + "value": "\"libraries\".\"file_write_kindle_max_file_size_mb\" >= 1" + }, + "libraries_file_write_audio_max_size_chk": { + "name": "libraries_file_write_audio_max_size_chk", + "value": "\"libraries\".\"file_write_audio_max_file_size_mb\" >= 1" + } + }, + "isRLSEnabled": false + }, + "public.library_folders": { + "name": "library_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "library_folders_library_id_idx": { + "name": "library_folders_library_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "library_folders_library_path_uidx": { + "name": "library_folders_library_path_uidx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "library_folders_library_id_libraries_id_fk": { + "name": "library_folders_library_id_libraries_id_fk", + "tableFrom": "library_folders", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "library_folders_id_library_id_unique": { + "name": "library_folders_id_library_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "library_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_files": { + "name": "book_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_folder_id": { + "name": "library_folder_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "absolute_path": { + "name": "absolute_path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "rel_path": { + "name": "rel_path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": false + }, + "ino": { + "name": "ino", + "type": "numeric(20, 0)", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "mtime": { + "name": "mtime", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'content'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_files_absolute_path_uidx": { + "name": "book_files_absolute_path_uidx", + "columns": [ + { + "expression": "absolute_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_book_id_idx": { + "name": "book_files_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_library_folder_id_idx": { + "name": "book_files_library_folder_id_idx", + "columns": [ + { + "expression": "library_folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_file_hash_idx": { + "name": "book_files_file_hash_idx", + "columns": [ + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_ino_idx": { + "name": "book_files_ino_idx", + "columns": [ + { + "expression": "ino", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_format_idx": { + "name": "book_files_format_idx", + "columns": [ + { + "expression": "format", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_library_folder_file_hash_idx": { + "name": "book_files_library_folder_file_hash_idx", + "columns": [ + { + "expression": "library_folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_library_folder_ino_idx": { + "name": "book_files_library_folder_ino_idx", + "columns": [ + { + "expression": "library_folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ino", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_files_book_id_books_id_fk": { + "name": "book_files_book_id_books_id_fk", + "tableFrom": "book_files", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_files_library_folder_id_library_folders_id_fk": { + "name": "book_files_library_folder_id_library_folders_id_fk", + "tableFrom": "book_files", + "tableTo": "library_folders", + "columnsFrom": ["library_folder_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_files_book_folder_consistency_fk": { + "name": "book_files_book_folder_consistency_fk", + "tableFrom": "book_files", + "tableTo": "books", + "columnsFrom": ["book_id", "library_folder_id"], + "columnsTo": ["id", "library_folder_id"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_files_role_chk": { + "name": "book_files_role_chk", + "value": "\"book_files\".\"role\" in ('content', 'cover', 'metadata', 'supplement')" + }, + "book_files_size_bytes_nonnegative_chk": { + "name": "book_files_size_bytes_nonnegative_chk", + "value": "\"book_files\".\"size_bytes\" is null or \"book_files\".\"size_bytes\" >= 0" + }, + "book_files_duration_seconds_nonnegative_chk": { + "name": "book_files_duration_seconds_nonnegative_chk", + "value": "\"book_files\".\"duration_seconds\" is null or \"book_files\".\"duration_seconds\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.books": { + "name": "books", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_folder_id": { + "name": "library_folder_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "primary_file_id": { + "name": "primary_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "primary_author_sort_name": { + "name": "primary_author_sort_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "folder_path": { + "name": "folder_path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'present'" + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "books_library_id_folder_path_idx": { + "name": "books_library_id_folder_path_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "books_primary_file_id_idx": { + "name": "books_primary_file_id_idx", + "columns": [ + { + "expression": "primary_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "books_library_status_id_idx": { + "name": "books_library_status_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "books_library_added_at_idx": { + "name": "books_library_added_at_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"added_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "books_library_visible_added_id_idx": { + "name": "books_library_visible_added_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"added_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"books\".\"status\" <> 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "books_library_author_sort_id_idx": { + "name": "books_library_author_sort_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"primary_author_sort_name\" asc nulls last", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"books\".\"status\" <> 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "books_library_author_sort_desc_id_idx": { + "name": "books_library_author_sort_desc_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"primary_author_sort_name\" desc nulls last", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"books\".\"status\" <> 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "books_library_id_libraries_id_fk": { + "name": "books_library_id_libraries_id_fk", + "tableFrom": "books", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "books_library_folder_id_library_folders_id_fk": { + "name": "books_library_folder_id_library_folders_id_fk", + "tableFrom": "books", + "tableTo": "library_folders", + "columnsFrom": ["library_folder_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "books_primary_file_id_book_files_id_fk": { + "name": "books_primary_file_id_book_files_id_fk", + "tableFrom": "books", + "tableTo": "book_files", + "columnsFrom": ["primary_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "books_library_folder_library_fk": { + "name": "books_library_folder_library_fk", + "tableFrom": "books", + "tableTo": "library_folders", + "columnsFrom": ["library_folder_id", "library_id"], + "columnsTo": ["id", "library_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "books_id_library_folder_id_unique": { + "name": "books_id_library_folder_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "library_folder_id"] + } + }, + "policies": {}, + "checkConstraints": { + "books_status_chk": { + "name": "books_status_chk", + "value": "\"books\".\"status\" in ('present', 'missing', 'processing')" + } + }, + "isRLSEnabled": false + }, + "public.book_duplicate_group_members": { + "name": "book_duplicate_group_members", + "schema": "", + "columns": { + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scan_id": { + "name": "scan_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "book_duplicate_group_members_group_idx": { + "name": "book_duplicate_group_members_group_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_duplicate_group_members_group_id_book_duplicate_groups_id_fk": { + "name": "book_duplicate_group_members_group_id_book_duplicate_groups_id_fk", + "tableFrom": "book_duplicate_group_members", + "tableTo": "book_duplicate_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_group_members_scan_id_book_duplicate_scans_id_fk": { + "name": "book_duplicate_group_members_scan_id_book_duplicate_scans_id_fk", + "tableFrom": "book_duplicate_group_members", + "tableTo": "book_duplicate_scans", + "columnsFrom": ["scan_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_group_members_book_id_books_id_fk": { + "name": "book_duplicate_group_members_book_id_books_id_fk", + "tableFrom": "book_duplicate_group_members", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_duplicate_group_members_scan_id_book_id_pk": { + "name": "book_duplicate_group_members_scan_id_book_id_pk", + "columns": ["scan_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_duplicate_groups": { + "name": "book_duplicate_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "scan_id": { + "name": "scan_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "root_book_id": { + "name": "root_book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reasons": { + "name": "reasons", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "max_title_similarity": { + "name": "max_title_similarity", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "member_count": { + "name": "member_count", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "book_duplicate_groups_scan_root_uidx": { + "name": "book_duplicate_groups_scan_root_uidx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_duplicate_groups_scan_id_idx": { + "name": "book_duplicate_groups_scan_id_idx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_duplicate_groups_scan_member_count_idx": { + "name": "book_duplicate_groups_scan_member_count_idx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "member_count", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_duplicate_groups_scan_id_book_duplicate_scans_id_fk": { + "name": "book_duplicate_groups_scan_id_book_duplicate_scans_id_fk", + "tableFrom": "book_duplicate_groups", + "tableTo": "book_duplicate_scans", + "columnsFrom": ["scan_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_groups_root_book_id_books_id_fk": { + "name": "book_duplicate_groups_root_book_id_books_id_fk", + "tableFrom": "book_duplicate_groups", + "tableTo": "books", + "columnsFrom": ["root_book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_duplicate_pairs": { + "name": "book_duplicate_pairs", + "schema": "", + "columns": { + "scan_id": { + "name": "scan_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "book_id_a": { + "name": "book_id_a", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id_b": { + "name": "book_id_b", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reasons": { + "name": "reasons", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "title_similarity": { + "name": "title_similarity", + "type": "real", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "book_duplicate_pairs_group_idx": { + "name": "book_duplicate_pairs_group_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_duplicate_pairs_scan_a_idx": { + "name": "book_duplicate_pairs_scan_a_idx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id_a", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_duplicate_pairs_scan_b_idx": { + "name": "book_duplicate_pairs_scan_b_idx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id_b", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_duplicate_pairs_scan_id_book_duplicate_scans_id_fk": { + "name": "book_duplicate_pairs_scan_id_book_duplicate_scans_id_fk", + "tableFrom": "book_duplicate_pairs", + "tableTo": "book_duplicate_scans", + "columnsFrom": ["scan_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_pairs_group_id_book_duplicate_groups_id_fk": { + "name": "book_duplicate_pairs_group_id_book_duplicate_groups_id_fk", + "tableFrom": "book_duplicate_pairs", + "tableTo": "book_duplicate_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_pairs_book_id_a_books_id_fk": { + "name": "book_duplicate_pairs_book_id_a_books_id_fk", + "tableFrom": "book_duplicate_pairs", + "tableTo": "books", + "columnsFrom": ["book_id_a"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_pairs_book_id_b_books_id_fk": { + "name": "book_duplicate_pairs_book_id_b_books_id_fk", + "tableFrom": "book_duplicate_pairs", + "tableTo": "books", + "columnsFrom": ["book_id_b"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_duplicate_pairs_scan_id_book_id_a_book_id_b_pk": { + "name": "book_duplicate_pairs_scan_id_book_id_a_book_id_b_pk", + "columns": ["scan_id", "book_id_a", "book_id_b"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_duplicate_pairs_order_chk": { + "name": "book_duplicate_pairs_order_chk", + "value": "\"book_duplicate_pairs\".\"book_id_a\" < \"book_duplicate_pairs\".\"book_id_b\"" + } + }, + "isRLSEnabled": false + }, + "public.book_duplicate_scan_keys": { + "name": "book_duplicate_scan_keys", + "schema": "", + "columns": { + "scan_id": { + "name": "scan_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "book_duplicate_scan_keys_lookup_idx": { + "name": "book_duplicate_scan_keys_lookup_idx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "value", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_duplicate_scan_keys_scan_id_book_duplicate_scans_id_fk": { + "name": "book_duplicate_scan_keys_scan_id_book_duplicate_scans_id_fk", + "tableFrom": "book_duplicate_scan_keys", + "tableTo": "book_duplicate_scans", + "columnsFrom": ["scan_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_scan_keys_book_id_books_id_fk": { + "name": "book_duplicate_scan_keys_book_id_books_id_fk", + "tableFrom": "book_duplicate_scan_keys", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_duplicate_scan_keys_scan_id_book_id_kind_value_pk": { + "name": "book_duplicate_scan_keys_scan_id_book_id_kind_value_pk", + "columns": ["scan_id", "book_id", "kind", "value"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_duplicate_scan_keys_kind_chk": { + "name": "book_duplicate_scan_keys_kind_chk", + "value": "\"book_duplicate_scan_keys\".\"kind\" in ('file_hash', 'isbn', 'exact_metadata')" + } + }, + "isRLSEnabled": false + }, + "public.book_duplicate_scans": { + "name": "book_duplicate_scans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_ids": { + "name": "library_ids", + "type": "integer[]", + "primaryKey": false, + "notNull": true + }, + "requested_library_id": { + "name": "requested_library_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "similarity_percent": { + "name": "similarity_percent", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "processed_books": { + "name": "processed_books", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_books": { + "name": "total_books", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_groups": { + "name": "total_groups", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "book_duplicate_scans_user_created_idx": { + "name": "book_duplicate_scans_user_created_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_duplicate_scans_status_idx": { + "name": "book_duplicate_scans_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_duplicate_scans_user_id_users_id_fk": { + "name": "book_duplicate_scans_user_id_users_id_fk", + "tableFrom": "book_duplicate_scans", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_duplicate_scans_similarity_chk": { + "name": "book_duplicate_scans_similarity_chk", + "value": "\"book_duplicate_scans\".\"similarity_percent\" between 70 and 100" + }, + "book_duplicate_scans_status_chk": { + "name": "book_duplicate_scans_status_chk", + "value": "\"book_duplicate_scans\".\"status\" in ('queued', 'running', 'completed', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.book_move_jobs": { + "name": "book_move_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "started_by": { + "name": "started_by", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_library_id": { + "name": "target_library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_folder_id": { + "name": "target_folder_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_library_ids": { + "name": "source_library_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "total_books": { + "name": "total_books", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "succeeded": { + "name": "succeeded", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "merged": { + "name": "merged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed": { + "name": "failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped": { + "name": "skipped", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "book_move_jobs_status_idx": { + "name": "book_move_jobs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_move_jobs_started_at_idx": { + "name": "book_move_jobs_started_at_idx", + "columns": [ + { + "expression": "\"started_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_move_jobs_started_by_users_id_fk": { + "name": "book_move_jobs_started_by_users_id_fk", + "tableFrom": "book_move_jobs", + "tableTo": "users", + "columnsFrom": ["started_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_move_jobs_target_library_id_libraries_id_fk": { + "name": "book_move_jobs_target_library_id_libraries_id_fk", + "tableFrom": "book_move_jobs", + "tableTo": "libraries", + "columnsFrom": ["target_library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_move_jobs_target_folder_id_library_folders_id_fk": { + "name": "book_move_jobs_target_folder_id_library_folders_id_fk", + "tableFrom": "book_move_jobs", + "tableTo": "library_folders", + "columnsFrom": ["target_folder_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_move_jobs_status_chk": { + "name": "book_move_jobs_status_chk", + "value": "\"book_move_jobs\".\"status\" in ('running', 'completed', 'failed', 'interrupted')" + }, + "book_move_jobs_counts_nonnegative_chk": { + "name": "book_move_jobs_counts_nonnegative_chk", + "value": "\"book_move_jobs\".\"total_books\" >= 0 and \"book_move_jobs\".\"succeeded\" >= 0 and \"book_move_jobs\".\"merged\" >= 0 and \"book_move_jobs\".\"failed\" >= 0 and \"book_move_jobs\".\"skipped\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.collection_books": { + "name": "collection_books", + "schema": "", + "columns": { + "collection_id": { + "name": "collection_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "position": { + "name": "position", + "type": "bigserial", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "collection_books_book_id_idx": { + "name": "collection_books_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "collection_books_collection_position_idx": { + "name": "collection_books_collection_position_idx", + "columns": [ + { + "expression": "collection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "collection_books_collection_id_collections_id_fk": { + "name": "collection_books_collection_id_collections_id_fk", + "tableFrom": "collection_books", + "tableTo": "collections", + "columnsFrom": ["collection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collection_books_book_id_books_id_fk": { + "name": "collection_books_book_id_books_id_fk", + "tableFrom": "collection_books", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "collection_books_collection_id_book_id_pk": { + "name": "collection_books_collection_id_book_id_pk", + "columns": ["collection_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_to_kobo": { + "name": "sync_to_kobo", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "collections_user_name_uidx": { + "name": "collections_user_name_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "collections_user_display_name_idx": { + "name": "collections_user_display_name_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "collections_user_id_users_id_fk": { + "name": "collections_user_id_users_id_fk", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smart_scope_kobo_subscriptions": { + "name": "smart_scope_kobo_subscriptions", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "smart_scope_id": { + "name": "smart_scope_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smart_scope_kobo_subscriptions_smart_scope_id_idx": { + "name": "smart_scope_kobo_subscriptions_smart_scope_id_idx", + "columns": [ + { + "expression": "smart_scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smart_scope_kobo_subscriptions_user_id_users_id_fk": { + "name": "smart_scope_kobo_subscriptions_user_id_users_id_fk", + "tableFrom": "smart_scope_kobo_subscriptions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "smart_scope_kobo_subscriptions_smart_scope_id_smart_scopes_id_fk": { + "name": "smart_scope_kobo_subscriptions_smart_scope_id_smart_scopes_id_fk", + "tableFrom": "smart_scope_kobo_subscriptions", + "tableTo": "smart_scopes", + "columnsFrom": ["smart_scope_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "smart_scope_kobo_subscriptions_user_id_smart_scope_id_pk": { + "name": "smart_scope_kobo_subscriptions_user_id_smart_scope_id_pk", + "columns": ["user_id", "smart_scope_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smart_scopes": { + "name": "smart_scopes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "filter": { + "name": "filter", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "default_sort": { + "name": "default_sort", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sync_to_kobo": { + "name": "sync_to_kobo", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "smart_scopes_user_id_users_id_fk": { + "name": "smart_scopes_user_id_users_id_fk", + "tableFrom": "smart_scopes", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "smart_scopes_user_id_name_unique": { + "name": "smart_scopes_user_id_name_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_series": { + "name": "book_series", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "normalized_name": { + "name": "normalized_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "expected_book_count": { + "name": "expected_book_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expected_book_count_source": { + "name": "expected_book_count_source", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "expected_book_count_updated_at": { + "name": "expected_book_count_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_series_normalized_name_uidx": { + "name": "book_series_normalized_name_uidx", + "columns": [ + { + "expression": "normalized_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_series_name_trgm_idx": { + "name": "book_series_name_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "book_series_name_unaccent_trgm_idx": { + "name": "book_series_name_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"name\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "book_series_name_lower_idx": { + "name": "book_series_name_lower_idx", + "columns": [ + { + "expression": "lower(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_series_expected_book_count_range_chk": { + "name": "book_series_expected_book_count_range_chk", + "value": "\"book_series\".\"expected_book_count\" IS NULL OR (\"book_series\".\"expected_book_count\" >= 1 AND \"book_series\".\"expected_book_count\" <= 10000)" + } + }, + "isRLSEnabled": false + }, + "public.book_series_memberships": { + "name": "book_series_memberships", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "series_id": { + "name": "series_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "series_index": { + "name": "series_index", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_series_memberships_book_display_uidx": { + "name": "book_series_memberships_book_display_uidx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_series_memberships_series_index_book_idx": { + "name": "book_series_memberships_series_index_book_idx", + "columns": [ + { + "expression": "series_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "series_index", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_series_memberships_book_display_idx": { + "name": "book_series_memberships_book_display_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_series_memberships_book_id_books_id_fk": { + "name": "book_series_memberships_book_id_books_id_fk", + "tableFrom": "book_series_memberships", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_series_memberships_series_id_book_series_id_fk": { + "name": "book_series_memberships_series_id_book_series_id_fk", + "tableFrom": "book_series_memberships", + "tableTo": "book_series", + "columnsFrom": ["series_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_series_memberships_book_id_series_id_pk": { + "name": "book_series_memberships_book_id_series_id_pk", + "columns": ["book_id", "series_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_series_memberships_display_order_nonnegative_chk": { + "name": "book_series_memberships_display_order_nonnegative_chk", + "value": "\"book_series_memberships\".\"display_order\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.author_enrichment_queue": { + "name": "author_enrichment_queue", + "schema": "", + "columns": { + "author_id": { + "name": "author_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "reason": { + "name": "reason", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_http_status": { + "name": "last_http_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "author_enrichment_queue_status_next_attempt_idx": { + "name": "author_enrichment_queue_status_next_attempt_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "author_enrichment_queue_next_attempt_idx": { + "name": "author_enrichment_queue_next_attempt_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "author_enrichment_queue_single_processing_idx": { + "name": "author_enrichment_queue_single_processing_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"author_enrichment_queue\".\"status\" = 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "author_enrichment_queue_author_id_authors_id_fk": { + "name": "author_enrichment_queue_author_id_authors_id_fk", + "tableFrom": "author_enrichment_queue", + "tableTo": "authors", + "columnsFrom": ["author_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "author_enrichment_queue_status_chk": { + "name": "author_enrichment_queue_status_chk", + "value": "\"author_enrichment_queue\".\"status\" in ('queued', 'processing', 'rate_limited', 'failed', 'done')" + }, + "author_enrichment_queue_reason_chk": { + "name": "author_enrichment_queue_reason_chk", + "value": "\"author_enrichment_queue\".\"reason\" in ('unknown', 'metadata_replace', 'manual_backfill', 'manual_backfill_all', 'author_rename', 'author_merge_target')" + }, + "author_enrichment_queue_attempt_count_nonnegative_chk": { + "name": "author_enrichment_queue_attempt_count_nonnegative_chk", + "value": "\"author_enrichment_queue\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.authors": { + "name": "authors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "sort_name": { + "name": "sort_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_photo": { + "name": "has_photo", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "birth_date": { + "name": "birth_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "birth_year": { + "name": "birth_year", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "death_date": { + "name": "death_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "death_year": { + "name": "death_year", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "website": { + "name": "website", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "influences": { + "name": "influences", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "metadata_provider": { + "name": "metadata_provider", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "metadata_provider_id": { + "name": "metadata_provider_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "last_enriched_at": { + "name": "last_enriched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "authors_name_trgm_idx": { + "name": "authors_name_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "authors_name_unaccent_trgm_idx": { + "name": "authors_name_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"name\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "authors_name_unique": { + "name": "authors_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_authors": { + "name": "book_authors", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "book_authors_author_id_idx": { + "name": "book_authors_author_id_idx", + "columns": [ + { + "expression": "author_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_authors_book_display_idx": { + "name": "book_authors_book_display_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_authors_book_display_author_idx": { + "name": "book_authors_book_display_author_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_authors_book_id_books_id_fk": { + "name": "book_authors_book_id_books_id_fk", + "tableFrom": "book_authors", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_authors_author_id_authors_id_fk": { + "name": "book_authors_author_id_authors_id_fk", + "tableFrom": "book_authors", + "tableTo": "authors", + "columnsFrom": ["author_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_authors_book_id_author_id_pk": { + "name": "book_authors_book_id_author_id_pk", + "columns": ["book_id", "author_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_community_ratings": { + "name": "book_community_ratings", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "rating": { + "name": "rating", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "rating_count": { + "name": "rating_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_community_ratings_provider_idx": { + "name": "book_community_ratings_provider_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_community_ratings_rating_book_idx": { + "name": "book_community_ratings_rating_book_idx", + "columns": [ + { + "expression": "rating", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_community_ratings_provider_rating_book_idx": { + "name": "book_community_ratings_provider_rating_book_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "rating", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_community_ratings_book_id_books_id_fk": { + "name": "book_community_ratings_book_id_books_id_fk", + "tableFrom": "book_community_ratings", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_community_ratings_book_id_provider_pk": { + "name": "book_community_ratings_book_id_provider_pk", + "columns": ["book_id", "provider"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_community_ratings_rating_range_chk": { + "name": "book_community_ratings_rating_range_chk", + "value": "\"book_community_ratings\".\"rating\" >= 0 and \"book_community_ratings\".\"rating\" <= 5" + }, + "book_community_ratings_count_nonnegative_chk": { + "name": "book_community_ratings_count_nonnegative_chk", + "value": "\"book_community_ratings\".\"rating_count\" is null or \"book_community_ratings\".\"rating_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.book_genres": { + "name": "book_genres", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "genre_id": { + "name": "genre_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "book_genres_genre_id_idx": { + "name": "book_genres_genre_id_idx", + "columns": [ + { + "expression": "genre_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_genres_book_id_books_id_fk": { + "name": "book_genres_book_id_books_id_fk", + "tableFrom": "book_genres", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_genres_genre_id_genres_id_fk": { + "name": "book_genres_genre_id_genres_id_fk", + "tableFrom": "book_genres", + "tableTo": "genres", + "columnsFrom": ["genre_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_genres_book_id_genre_id_pk": { + "name": "book_genres_book_id_genre_id_pk", + "columns": ["book_id", "genre_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_metadata": { + "name": "book_metadata", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isbn10": { + "name": "isbn10", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "isbn13": { + "name": "isbn13", + "type": "varchar(13)", + "primaryKey": false, + "notNull": false + }, + "publisher": { + "name": "publisher", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "published_date": { + "name": "published_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "published_year": { + "name": "published_year", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "page_count": { + "name": "page_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "series_id": { + "name": "series_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "series_name": { + "name": "series_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "series_index": { + "name": "series_index", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "rating": { + "name": "rating", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cover_source": { + "name": "cover_source", + "type": "varchar(9)", + "primaryKey": false, + "notNull": false + }, + "google_books_id": { + "name": "google_books_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "goodreads_id": { + "name": "goodreads_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "amazon_id": { + "name": "amazon_id", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "hardcover_id": { + "name": "hardcover_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "hardcover_edition_id": { + "name": "hardcover_edition_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "open_library_id": { + "name": "open_library_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "itunes_id": { + "name": "itunes_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "kobo_id": { + "name": "kobo_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "metadata_score": { + "name": "metadata_score", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_metadata_fetch_at": { + "name": "last_metadata_fetch_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "embedding": { + "name": "embedding", + "type": "vector(256)", + "primaryKey": false, + "notNull": false + }, + "last_written_at": { + "name": "last_written_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "abridged": { + "name": "abridged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "audible_id": { + "name": "audible_id", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "librofm_id": { + "name": "librofm_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "comicvine_id": { + "name": "comicvine_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "ranobedb_id": { + "name": "ranobedb_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "lubimyczytac_id": { + "name": "lubimyczytac_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aladin_id": { + "name": "aladin_id", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "chapters": { + "name": "chapters", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "locked_fields": { + "name": "locked_fields", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bm_title_trgm_idx": { + "name": "bm_title_trgm_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_title_unaccent_trgm_idx": { + "name": "bm_title_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"title\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_title_lower_idx": { + "name": "bm_title_lower_idx", + "columns": [ + { + "expression": "lower(\"title\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_title_book_id_idx": { + "name": "bm_title_book_id_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_series_trgm_idx": { + "name": "bm_series_trgm_idx", + "columns": [ + { + "expression": "series_name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_series_unaccent_trgm_idx": { + "name": "bm_series_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"series_name\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_series_id_idx": { + "name": "bm_series_id_idx", + "columns": [ + { + "expression": "series_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_series_id_index_book_id_idx": { + "name": "bm_series_id_index_book_id_idx", + "columns": [ + { + "expression": "series_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "series_index", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_series_name_lower_btrim_idx": { + "name": "bm_series_name_lower_btrim_idx", + "columns": [ + { + "expression": "lower(btrim(\"series_name\"))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_publisher_trgm_idx": { + "name": "bm_publisher_trgm_idx", + "columns": [ + { + "expression": "publisher", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_publisher_unaccent_trgm_idx": { + "name": "bm_publisher_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"publisher\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_language_idx": { + "name": "bm_language_idx", + "columns": [ + { + "expression": "language", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_language_trgm_idx": { + "name": "bm_language_trgm_idx", + "columns": [ + { + "expression": "language", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_language_unaccent_trgm_idx": { + "name": "bm_language_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"language\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_published_date_idx": { + "name": "bm_published_date_idx", + "columns": [ + { + "expression": "published_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_published_year_idx": { + "name": "bm_published_year_idx", + "columns": [ + { + "expression": "published_year", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_published_date_sort_idx": { + "name": "bm_published_date_sort_idx", + "columns": [ + { + "expression": "coalesce(\"published_date\", make_date(\"published_year\", 1, 1))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_series_name_index_idx": { + "name": "bm_series_name_index_idx", + "columns": [ + { + "expression": "series_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "series_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_isbn10_idx": { + "name": "bm_isbn10_idx", + "columns": [ + { + "expression": "isbn10", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_isbn13_idx": { + "name": "bm_isbn13_idx", + "columns": [ + { + "expression": "isbn13", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_embedding_hnsw_cosine_idx": { + "name": "bm_embedding_hnsw_cosine_idx", + "columns": [ + { + "expression": "\"embedding\" vector_cosine_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": {} + } + }, + "foreignKeys": { + "book_metadata_book_id_books_id_fk": { + "name": "book_metadata_book_id_books_id_fk", + "tableFrom": "book_metadata", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_metadata_series_id_book_series_id_fk": { + "name": "book_metadata_series_id_book_series_id_fk", + "tableFrom": "book_metadata", + "tableTo": "book_series", + "columnsFrom": ["series_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_metadata_rating_range_chk": { + "name": "book_metadata_rating_range_chk", + "value": "\"book_metadata\".\"rating\" is null or (\"book_metadata\".\"rating\" >= 1 and \"book_metadata\".\"rating\" <= 10)" + }, + "book_metadata_published_date_range_chk": { + "name": "book_metadata_published_date_range_chk", + "value": "\"book_metadata\".\"published_date\" is null or (extract(year from \"book_metadata\".\"published_date\") >= 1000 and extract(year from \"book_metadata\".\"published_date\") <= 2200)" + }, + "book_metadata_published_year_range_chk": { + "name": "book_metadata_published_year_range_chk", + "value": "\"book_metadata\".\"published_year\" is null or (\"book_metadata\".\"published_year\" >= 1000 and \"book_metadata\".\"published_year\" <= 2200)" + }, + "book_metadata_page_count_nonnegative_chk": { + "name": "book_metadata_page_count_nonnegative_chk", + "value": "\"book_metadata\".\"page_count\" is null or \"book_metadata\".\"page_count\" >= 0" + }, + "book_metadata_duration_seconds_nonnegative_chk": { + "name": "book_metadata_duration_seconds_nonnegative_chk", + "value": "\"book_metadata\".\"duration_seconds\" is null or \"book_metadata\".\"duration_seconds\" >= 0" + }, + "book_metadata_cover_source_chk": { + "name": "book_metadata_cover_source_chk", + "value": "\"book_metadata\".\"cover_source\" is null or \"book_metadata\".\"cover_source\" in ('extracted', 'custom')" + } + }, + "isRLSEnabled": false + }, + "public.book_metadata_fetch_queue": { + "name": "book_metadata_fetch_queue", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "reason": { + "name": "reason", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'manual_trigger'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_http_status": { + "name": "last_http_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bmfq_status_idx": { + "name": "bmfq_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bmfq_created_at_idx": { + "name": "bmfq_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bmfq_status_created_book_idx": { + "name": "bmfq_status_created_book_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_metadata_fetch_queue_book_id_books_id_fk": { + "name": "book_metadata_fetch_queue_book_id_books_id_fk", + "tableFrom": "book_metadata_fetch_queue", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_metadata_fetch_queue_status_chk": { + "name": "book_metadata_fetch_queue_status_chk", + "value": "\"book_metadata_fetch_queue\".\"status\" in ('queued', 'processing', 'failed')" + }, + "book_metadata_fetch_queue_reason_chk": { + "name": "book_metadata_fetch_queue_reason_chk", + "value": "\"book_metadata_fetch_queue\".\"reason\" in ('event_import', 'manual_trigger', 'manual_retry')" + }, + "book_metadata_fetch_queue_attempt_count_nonnegative_chk": { + "name": "book_metadata_fetch_queue_attempt_count_nonnegative_chk", + "value": "\"book_metadata_fetch_queue\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.book_tags": { + "name": "book_tags", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "book_tags_tag_id_idx": { + "name": "book_tags_tag_id_idx", + "columns": [ + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_tags_book_id_books_id_fk": { + "name": "book_tags_book_id_books_id_fk", + "tableFrom": "book_tags", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_tags_tag_id_tags_id_fk": { + "name": "book_tags_tag_id_tags_id_fk", + "tableFrom": "book_tags", + "tableTo": "tags", + "columnsFrom": ["tag_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_tags_book_id_tag_id_pk": { + "name": "book_tags_book_id_tag_id_pk", + "columns": ["book_id", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genres": { + "name": "genres", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "genres_name_trgm_idx": { + "name": "genres_name_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "genres_name_unaccent_trgm_idx": { + "name": "genres_name_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"name\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "genres_name_unique": { + "name": "genres_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tags": { + "name": "tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "tags_name_trgm_idx": { + "name": "tags_name_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "tags_name_unaccent_trgm_idx": { + "name": "tags_name_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"name\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tags_name_unique": { + "name": "tags_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_narrators": { + "name": "book_narrators", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "narrator_id": { + "name": "narrator_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "book_narrators_narrator_id_idx": { + "name": "book_narrators_narrator_id_idx", + "columns": [ + { + "expression": "narrator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_narrators_book_id_books_id_fk": { + "name": "book_narrators_book_id_books_id_fk", + "tableFrom": "book_narrators", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_narrators_narrator_id_narrators_id_fk": { + "name": "book_narrators_narrator_id_narrators_id_fk", + "tableFrom": "book_narrators", + "tableTo": "narrators", + "columnsFrom": ["narrator_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_narrators_book_id_narrator_id_pk": { + "name": "book_narrators_book_id_narrator_id_pk", + "columns": ["book_id", "narrator_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.narrators": { + "name": "narrators", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "sort_name": { + "name": "sort_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "narrators_name_trgm_idx": { + "name": "narrators_name_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "narrators_name_unaccent_trgm_idx": { + "name": "narrators_name_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"name\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "narrators_name_unique": { + "name": "narrators_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.library_dir_scan_state": { + "name": "library_dir_scan_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "library_folder_id": { + "name": "library_folder_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "dir_path": { + "name": "dir_path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "last_seen_mtime_ms": { + "name": "last_seen_mtime_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "library_dir_scan_state_folder_dir_uidx": { + "name": "library_dir_scan_state_folder_dir_uidx", + "columns": [ + { + "expression": "library_folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dir_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "library_dir_scan_state_folder_idx": { + "name": "library_dir_scan_state_folder_idx", + "columns": [ + { + "expression": "library_folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "library_dir_scan_state_library_folder_id_library_folders_id_fk": { + "name": "library_dir_scan_state_library_folder_id_library_folders_id_fk", + "tableFrom": "library_dir_scan_state", + "tableTo": "library_folders", + "columnsFrom": ["library_folder_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scan_jobs": { + "name": "scan_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "triggered_by": { + "name": "triggered_by", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "added_count": { + "name": "added_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_count": { + "name": "updated_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "missing_count": { + "name": "missing_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "scan_jobs_library_status_idx": { + "name": "scan_jobs_library_status_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scan_jobs_library_id_libraries_id_fk": { + "name": "scan_jobs_library_id_libraries_id_fk", + "tableFrom": "scan_jobs", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "scan_jobs_status_chk": { + "name": "scan_jobs_status_chk", + "value": "\"scan_jobs\".\"status\" in ('running', 'completed', 'failed')" + }, + "scan_jobs_triggered_by_chk": { + "name": "scan_jobs_triggered_by_chk", + "value": "\"scan_jobs\".\"triggered_by\" in ('manual', 'watcher', 'schedule')" + }, + "scan_jobs_added_count_nonnegative_chk": { + "name": "scan_jobs_added_count_nonnegative_chk", + "value": "\"scan_jobs\".\"added_count\" >= 0" + }, + "scan_jobs_updated_count_nonnegative_chk": { + "name": "scan_jobs_updated_count_nonnegative_chk", + "value": "\"scan_jobs\".\"updated_count\" >= 0" + }, + "scan_jobs_missing_count_nonnegative_chk": { + "name": "scan_jobs_missing_count_nonnegative_chk", + "value": "\"scan_jobs\".\"missing_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.annotation_positions": { + "name": "annotation_positions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "annotation_id": { + "name": "annotation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "pos0": { + "name": "pos0", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pos1": { + "name": "pos1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "converter_version": { + "name": "converter_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "extras": { + "name": "extras", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "annotation_positions_annotation_format_uidx": { + "name": "annotation_positions_annotation_format_uidx", + "columns": [ + { + "expression": "annotation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "format", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotation_positions_user_idx": { + "name": "annotation_positions_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotation_positions_book_file_id_idx": { + "name": "annotation_positions_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotation_positions_format_status_idx": { + "name": "annotation_positions_format_status_idx", + "columns": [ + { + "expression": "format", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "annotation_positions_annotation_id_annotations_id_fk": { + "name": "annotation_positions_annotation_id_annotations_id_fk", + "tableFrom": "annotation_positions", + "tableTo": "annotations", + "columnsFrom": ["annotation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "annotation_positions_user_id_users_id_fk": { + "name": "annotation_positions_user_id_users_id_fk", + "tableFrom": "annotation_positions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "annotation_positions_book_file_id_book_files_id_fk": { + "name": "annotation_positions_book_file_id_book_files_id_fk", + "tableFrom": "annotation_positions", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "annotation_positions_format_chk": { + "name": "annotation_positions_format_chk", + "value": "\"annotation_positions\".\"format\" in ('cfi', 'xpointer', 'pdf', 'kobo_span')" + }, + "annotation_positions_status_chk": { + "name": "annotation_positions_status_chk", + "value": "\"annotation_positions\".\"status\" in ('exact', 'repaired', 'failed', 'pending')" + }, + "annotation_positions_pos0_chk": { + "name": "annotation_positions_pos0_chk", + "value": "\"annotation_positions\".\"status\" in ('failed', 'pending') or \"annotation_positions\".\"pos0\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.annotation_sync_state": { + "name": "annotation_sync_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "annotation_id": { + "name": "annotation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "external_key": { + "name": "external_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "external_created_at": { + "name": "external_created_at", + "type": "varchar(19)", + "primaryKey": false, + "notNull": false + }, + "last_applied_version": { + "name": "last_applied_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "delete_acked_at": { + "name": "delete_acked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "first_synced_at": { + "name": "first_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "annotation_sync_state_annotation_source_device_uidx": { + "name": "annotation_sync_state_annotation_source_device_uidx", + "columns": [ + { + "expression": "annotation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotation_sync_state_user_source_device_key_idx": { + "name": "annotation_sync_state_user_source_device_key_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotation_sync_state_user_key_idx": { + "name": "annotation_sync_state_user_key_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotation_sync_state_annotation_id_idx": { + "name": "annotation_sync_state_annotation_id_idx", + "columns": [ + { + "expression": "annotation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "annotation_sync_state_annotation_id_annotations_id_fk": { + "name": "annotation_sync_state_annotation_id_annotations_id_fk", + "tableFrom": "annotation_sync_state", + "tableTo": "annotations", + "columnsFrom": ["annotation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "annotation_sync_state_user_id_users_id_fk": { + "name": "annotation_sync_state_user_id_users_id_fk", + "tableFrom": "annotation_sync_state", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "annotation_sync_state_source_chk": { + "name": "annotation_sync_state_source_chk", + "value": "\"annotation_sync_state\".\"source\" in ('koreader', 'kobo')" + } + }, + "isRLSEnabled": false + }, + "public.annotations": { + "name": "annotations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'yellow'" + }, + "style": { + "name": "style", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'highlight'" + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chapter_title": { + "name": "chapter_title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'web'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "device_created_at": { + "name": "device_created_at", + "type": "varchar(19)", + "primaryKey": false, + "notNull": false + }, + "device_updated_at": { + "name": "device_updated_at", + "type": "varchar(19)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "annotations_user_id_idx": { + "name": "annotations_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotations_user_id_id_idx": { + "name": "annotations_user_id_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotations_user_book_idx": { + "name": "annotations_user_book_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotations_book_id_idx": { + "name": "annotations_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotations_user_book_active_idx": { + "name": "annotations_user_book_active_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"annotations\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "annotations_user_id_users_id_fk": { + "name": "annotations_user_id_users_id_fk", + "tableFrom": "annotations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "annotations_book_id_books_id_fk": { + "name": "annotations_book_id_books_id_fk", + "tableFrom": "annotations", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "annotations_style_chk": { + "name": "annotations_style_chk", + "value": "\"annotations\".\"style\" in ('highlight', 'underline', 'strikethrough', 'squiggly', 'invert')" + }, + "annotations_origin_chk": { + "name": "annotations_origin_chk", + "value": "\"annotations\".\"origin\" in ('web', 'koreader', 'kobo')" + } + }, + "isRLSEnabled": false + }, + "public.audiobook_progress": { + "name": "audiobook_progress", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "percentage": { + "name": "percentage", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "current_file_id": { + "name": "current_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position_seconds": { + "name": "position_seconds", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "abp_user_id_idx": { + "name": "abp_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audiobook_progress_book_id_idx": { + "name": "audiobook_progress_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audiobook_progress_current_file_id_idx": { + "name": "audiobook_progress_current_file_id_idx", + "columns": [ + { + "expression": "current_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audiobook_progress_user_id_users_id_fk": { + "name": "audiobook_progress_user_id_users_id_fk", + "tableFrom": "audiobook_progress", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "audiobook_progress_book_id_books_id_fk": { + "name": "audiobook_progress_book_id_books_id_fk", + "tableFrom": "audiobook_progress", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "audiobook_progress_current_file_id_book_files_id_fk": { + "name": "audiobook_progress_current_file_id_book_files_id_fk", + "tableFrom": "audiobook_progress", + "tableTo": "book_files", + "columnsFrom": ["current_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "audiobook_progress_user_id_book_id_pk": { + "name": "audiobook_progress_user_id_book_id_pk", + "columns": ["user_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "audiobook_progress_percentage_range_chk": { + "name": "audiobook_progress_percentage_range_chk", + "value": "\"audiobook_progress\".\"percentage\" >= 0 and \"audiobook_progress\".\"percentage\" <= 100" + }, + "audiobook_progress_position_seconds_nonnegative_chk": { + "name": "audiobook_progress_position_seconds_nonnegative_chk", + "value": "\"audiobook_progress\".\"position_seconds\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.bookmarks": { + "name": "bookmarks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cfi": { + "name": "cfi", + "type": "varchar(2000)", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "position_seconds": { + "name": "position_seconds", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'web'" + }, + "device_pos": { + "name": "device_pos", + "type": "varchar(4000)", + "primaryKey": false, + "notNull": false + }, + "pageno": { + "name": "pageno", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "bookmarks_user_book_idx": { + "name": "bookmarks_user_book_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bookmarks_book_id_idx": { + "name": "bookmarks_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bookmarks_deleted_at_idx": { + "name": "bookmarks_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"bookmarks\".\"deleted_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "bookmarks_user_book_cfi_uidx": { + "name": "bookmarks_user_book_cfi_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cfi", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"bookmarks\".\"cfi\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "bookmarks_user_book_pos_uidx": { + "name": "bookmarks_user_book_pos_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position_seconds", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"bookmarks\".\"position_seconds\" is not null and \"bookmarks\".\"cfi\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bookmarks_user_id_users_id_fk": { + "name": "bookmarks_user_id_users_id_fk", + "tableFrom": "bookmarks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bookmarks_book_id_books_id_fk": { + "name": "bookmarks_book_id_books_id_fk", + "tableFrom": "bookmarks", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "bookmarks_origin_chk": { + "name": "bookmarks_origin_chk", + "value": "\"bookmarks\".\"origin\" in ('web', 'koreader')" + } + }, + "isRLSEnabled": false + }, + "public.reader_default_preferences": { + "name": "reader_default_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format_group": { + "name": "format_group", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "rdp_user_format_idx": { + "name": "rdp_user_format_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "format_group", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reader_default_preferences_user_id_users_id_fk": { + "name": "reader_default_preferences_user_id_users_id_fk", + "tableFrom": "reader_default_preferences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reader_default_preferences_format_group_chk": { + "name": "reader_default_preferences_format_group_chk", + "value": "\"reader_default_preferences\".\"format_group\" in ('epub', 'pdf', 'cbx', 'audio')" + } + }, + "isRLSEnabled": false + }, + "public.reader_preferences": { + "name": "reader_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "rp_user_file_idx": { + "name": "rp_user_file_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reader_preferences_book_file_id_idx": { + "name": "reader_preferences_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reader_preferences_user_id_users_id_fk": { + "name": "reader_preferences_user_id_users_id_fk", + "tableFrom": "reader_preferences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reader_preferences_book_file_id_book_files_id_fk": { + "name": "reader_preferences_book_file_id_book_files_id_fk", + "tableFrom": "reader_preferences", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reading_attempts": { + "name": "reading_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "started_on": { + "name": "started_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "ended_on": { + "name": "ended_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "external_provider": { + "name": "external_provider", + "type": "varchar(40)", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reading_attempts_user_book_idx": { + "name": "reading_attempts_user_book_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reading_attempts_book_id_idx": { + "name": "reading_attempts_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reading_attempts_user_outcome_ended_idx": { + "name": "reading_attempts_user_outcome_ended_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "outcome", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ended_on", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reading_attempts_one_active_uidx": { + "name": "reading_attempts_one_active_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"reading_attempts\".\"outcome\" is null and \"reading_attempts\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "reading_attempts_external_uidx": { + "name": "reading_attempts_external_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"reading_attempts\".\"external_provider\" is not null and \"reading_attempts\".\"external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reading_attempts_user_id_users_id_fk": { + "name": "reading_attempts_user_id_users_id_fk", + "tableFrom": "reading_attempts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reading_attempts_book_id_books_id_fk": { + "name": "reading_attempts_book_id_books_id_fk", + "tableFrom": "reading_attempts", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reading_attempts_outcome_chk": { + "name": "reading_attempts_outcome_chk", + "value": "\"reading_attempts\".\"outcome\" is null or \"reading_attempts\".\"outcome\" in ('completed', 'skimmed', 'abandoned')" + }, + "reading_attempts_origin_chk": { + "name": "reading_attempts_origin_chk", + "value": "\"reading_attempts\".\"origin\" in ('manual', 'bookorbit', 'kobo', 'koreader', 'hardcover', 'migration')" + }, + "reading_attempts_end_after_start_chk": { + "name": "reading_attempts_end_after_start_chk", + "value": "\"reading_attempts\".\"ended_on\" is null or \"reading_attempts\".\"started_on\" is null or \"reading_attempts\".\"ended_on\" >= \"reading_attempts\".\"started_on\"" + }, + "reading_attempts_closed_has_outcome_chk": { + "name": "reading_attempts_closed_has_outcome_chk", + "value": "\"reading_attempts\".\"ended_on\" is null or \"reading_attempts\".\"outcome\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.reading_progress": { + "name": "reading_progress", + "schema": "", + "columns": { + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "percentage": { + "name": "percentage", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cfi": { + "name": "cfi", + "type": "varchar(2000)", + "primaryKey": false, + "notNull": false + }, + "page_number": { + "name": "page_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "position_seconds": { + "name": "position_seconds", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "kobo_location_source": { + "name": "kobo_location_source", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": false + }, + "kobo_location_type": { + "name": "kobo_location_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "kobo_location_value": { + "name": "kobo_location_value", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "kobo_content_source_progress_percent": { + "name": "kobo_content_source_progress_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "koreader_progress": { + "name": "koreader_progress", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reading_progress_user_id_idx": { + "name": "reading_progress_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reading_progress_user_last_read_at_idx": { + "name": "reading_progress_user_last_read_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_read_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reading_progress_book_file_id_book_files_id_fk": { + "name": "reading_progress_book_file_id_book_files_id_fk", + "tableFrom": "reading_progress", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reading_progress_user_id_users_id_fk": { + "name": "reading_progress_user_id_users_id_fk", + "tableFrom": "reading_progress", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "reading_progress_book_file_id_user_id_pk": { + "name": "reading_progress_book_file_id_user_id_pk", + "columns": ["book_file_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reading_progress_percentage_range_chk": { + "name": "reading_progress_percentage_range_chk", + "value": "\"reading_progress\".\"percentage\" >= 0 and \"reading_progress\".\"percentage\" <= 100" + }, + "reading_progress_page_number_nonnegative_chk": { + "name": "reading_progress_page_number_nonnegative_chk", + "value": "\"reading_progress\".\"page_number\" is null or \"reading_progress\".\"page_number\" >= 0" + }, + "reading_progress_position_seconds_nonnegative_chk": { + "name": "reading_progress_position_seconds_nonnegative_chk", + "value": "\"reading_progress\".\"position_seconds\" is null or \"reading_progress\".\"position_seconds\" >= 0" + }, + "reading_progress_kobo_content_source_progress_range_chk": { + "name": "reading_progress_kobo_content_source_progress_range_chk", + "value": "\"reading_progress\".\"kobo_content_source_progress_percent\" is null or (\"reading_progress\".\"kobo_content_source_progress_percent\" >= 0 and \"reading_progress\".\"kobo_content_source_progress_percent\" <= 100)" + } + }, + "isRLSEnabled": false + }, + "public.reading_sessions": { + "name": "reading_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "attempt_id": { + "name": "attempt_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "progress_delta": { + "name": "progress_delta", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "end_progress": { + "name": "end_progress", + "type": "real", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "rs_user_session_id_uidx": { + "name": "rs_user_session_id_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rs_user_started_at_idx": { + "name": "rs_user_started_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rs_book_file_started_at_idx": { + "name": "rs_book_file_started_at_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rs_user_book_file_idx": { + "name": "rs_user_book_file_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rs_user_book_started_at_idx": { + "name": "rs_user_book_started_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reading_sessions_book_id_idx": { + "name": "reading_sessions_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rs_attempt_started_at_idx": { + "name": "rs_attempt_started_at_idx", + "columns": [ + { + "expression": "attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reading_sessions_user_id_users_id_fk": { + "name": "reading_sessions_user_id_users_id_fk", + "tableFrom": "reading_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reading_sessions_book_file_id_book_files_id_fk": { + "name": "reading_sessions_book_file_id_book_files_id_fk", + "tableFrom": "reading_sessions", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reading_sessions_book_id_books_id_fk": { + "name": "reading_sessions_book_id_books_id_fk", + "tableFrom": "reading_sessions", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reading_sessions_attempt_id_reading_attempts_id_fk": { + "name": "reading_sessions_attempt_id_reading_attempts_id_fk", + "tableFrom": "reading_sessions", + "tableTo": "reading_attempts", + "columnsFrom": ["attempt_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reading_sessions_source_chk": { + "name": "reading_sessions_source_chk", + "value": "\"reading_sessions\".\"source\" in ('web', 'koreader', 'manual', 'kobo')" + }, + "reading_sessions_duration_seconds_nonnegative_chk": { + "name": "reading_sessions_duration_seconds_nonnegative_chk", + "value": "\"reading_sessions\".\"duration_seconds\" >= 0" + }, + "reading_sessions_end_progress_range_chk": { + "name": "reading_sessions_end_progress_range_chk", + "value": "\"reading_sessions\".\"end_progress\" is null or (\"reading_sessions\".\"end_progress\" >= 0 and \"reading_sessions\".\"end_progress\" <= 100)" + }, + "reading_sessions_ended_after_started_chk": { + "name": "reading_sessions_ended_after_started_chk", + "value": "\"reading_sessions\".\"ended_at\" >= \"reading_sessions\".\"started_at\"" + } + }, + "isRLSEnabled": false + }, + "public.user_book_notes": { + "name": "user_book_notes", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ubn_user_id_idx": { + "name": "ubn_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ubn_book_id_idx": { + "name": "ubn_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ubn_book_user_idx": { + "name": "ubn_book_user_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_book_notes_user_id_users_id_fk": { + "name": "user_book_notes_user_id_users_id_fk", + "tableFrom": "user_book_notes", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_book_notes_book_id_books_id_fk": { + "name": "user_book_notes_book_id_books_id_fk", + "tableFrom": "user_book_notes", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_book_notes_user_id_book_id_pk": { + "name": "user_book_notes_user_id_book_id_pk", + "columns": ["user_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_book_notes_note_length_chk": { + "name": "user_book_notes_note_length_chk", + "value": "\"user_book_notes\".\"note\" is null or char_length(\"user_book_notes\".\"note\") <= 10000" + } + }, + "isRLSEnabled": false + }, + "public.user_book_ratings": { + "name": "user_book_ratings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "rating": { + "name": "rating", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ubr_user_id_idx": { + "name": "ubr_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ubr_book_id_idx": { + "name": "ubr_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ubr_book_user_rating_idx": { + "name": "ubr_book_user_rating_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_book_ratings_user_id_users_id_fk": { + "name": "user_book_ratings_user_id_users_id_fk", + "tableFrom": "user_book_ratings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_book_ratings_book_id_books_id_fk": { + "name": "user_book_ratings_book_id_books_id_fk", + "tableFrom": "user_book_ratings", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_book_ratings_user_id_book_id_pk": { + "name": "user_book_ratings_user_id_book_id_pk", + "columns": ["user_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_book_ratings_rating_range_chk": { + "name": "user_book_ratings_rating_range_chk", + "value": "\"user_book_ratings\".\"rating\" is null or (\"user_book_ratings\".\"rating\" >= 1 and \"user_book_ratings\".\"rating\" <= 5)" + } + }, + "isRLSEnabled": false + }, + "public.user_book_status": { + "name": "user_book_status", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'unread'" + }, + "source": { + "name": "source", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'auto'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ubs_user_id_idx": { + "name": "ubs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ubs_user_status_idx": { + "name": "ubs_user_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ubs_book_user_idx": { + "name": "ubs_book_user_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_book_status_user_id_users_id_fk": { + "name": "user_book_status_user_id_users_id_fk", + "tableFrom": "user_book_status", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_book_status_book_id_books_id_fk": { + "name": "user_book_status_book_id_books_id_fk", + "tableFrom": "user_book_status", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_book_status_user_id_book_id_pk": { + "name": "user_book_status_user_id_book_id_pk", + "columns": ["user_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_book_status_status_chk": { + "name": "user_book_status_status_chk", + "value": "\"user_book_status\".\"status\" in ('unread', 'want_to_read', 'reading', 'on_hold', 'rereading', 'read', 'skimmed', 'abandoned')" + }, + "user_book_status_source_chk": { + "name": "user_book_status_source_chk", + "value": "\"user_book_status\".\"source\" in ('auto', 'manual')" + }, + "user_book_status_finished_after_started_chk": { + "name": "user_book_status_finished_after_started_chk", + "value": "\"user_book_status\".\"finished_at\" is null or \"user_book_status\".\"started_at\" is null or \"user_book_status\".\"finished_at\" >= \"user_book_status\".\"started_at\"" + } + }, + "isRLSEnabled": false + }, + "public.user_reading_daily_stats": { + "name": "user_reading_daily_stats", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "day": { + "name": "day", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "reading_seconds": { + "name": "reading_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "progress_delta": { + "name": "progress_delta", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sessions_count": { + "name": "sessions_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "urds_user_day_idx": { + "name": "urds_user_day_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "day", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_reading_daily_stats_library_id_idx": { + "name": "user_reading_daily_stats_library_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_reading_daily_stats_user_id_users_id_fk": { + "name": "user_reading_daily_stats_user_id_users_id_fk", + "tableFrom": "user_reading_daily_stats", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_reading_daily_stats_library_id_libraries_id_fk": { + "name": "user_reading_daily_stats_library_id_libraries_id_fk", + "tableFrom": "user_reading_daily_stats", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_reading_daily_stats_user_id_library_id_day_pk": { + "name": "user_reading_daily_stats_user_id_library_id_day_pk", + "columns": ["user_id", "library_id", "day"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_reading_daily_stats_reading_seconds_nonnegative_chk": { + "name": "user_reading_daily_stats_reading_seconds_nonnegative_chk", + "value": "\"user_reading_daily_stats\".\"reading_seconds\" >= 0" + }, + "user_reading_daily_stats_sessions_count_nonnegative_chk": { + "name": "user_reading_daily_stats_sessions_count_nonnegative_chk", + "value": "\"user_reading_daily_stats\".\"sessions_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.oidc_group_mappings": { + "name": "oidc_group_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "oidc_group_claim": { + "name": "oidc_group_claim", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_name": { + "name": "permission_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oidc_group_mappings_provider_claim_uidx": { + "name": "oidc_group_mappings_provider_claim_uidx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "oidc_group_claim", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oidc_group_mappings_provider_id_oidc_providers_id_fk": { + "name": "oidc_group_mappings_provider_id_oidc_providers_id_fk", + "tableFrom": "oidc_group_mappings", + "tableTo": "oidc_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_identities": { + "name": "oidc_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "oidc_subject": { + "name": "oidc_subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_issuer": { + "name": "oidc_issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "linked_at": { + "name": "linked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oidc_identities_provider_subject_uidx": { + "name": "oidc_identities_provider_subject_uidx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "oidc_subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_identities_user_provider_uidx": { + "name": "oidc_identities_user_provider_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_identities_issuer_subject_uidx": { + "name": "oidc_identities_issuer_subject_uidx", + "columns": [ + { + "expression": "oidc_issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "oidc_subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_identities_user_id_idx": { + "name": "oidc_identities_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oidc_identities_user_id_users_id_fk": { + "name": "oidc_identities_user_id_users_id_fk", + "tableFrom": "oidc_identities", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_identities_provider_id_oidc_providers_id_fk": { + "name": "oidc_identities_provider_id_oidc_providers_id_fk", + "tableFrom": "oidc_identities", + "tableTo": "oidc_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_permission_grants": { + "name": "oidc_permission_grants", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "permission_name": { + "name": "permission_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oidc_permission_grants_user_id_users_id_fk": { + "name": "oidc_permission_grants_user_id_users_id_fk", + "tableFrom": "oidc_permission_grants", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_permission_grants_provider_id_oidc_providers_id_fk": { + "name": "oidc_permission_grants_provider_id_oidc_providers_id_fk", + "tableFrom": "oidc_permission_grants", + "tableTo": "oidc_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "oidc_permission_grants_user_id_provider_id_permission_name_pk": { + "name": "oidc_permission_grants_user_id_provider_id_permission_name_pk", + "columns": ["user_id", "provider_id", "permission_name"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_providers": { + "name": "oidc_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "issuer_uri": { + "name": "issuer_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'openid profile email'" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_mapping": { + "name": "claim_mapping", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"username\":\"preferred_username\",\"name\":\"name\",\"email\":\"email\",\"groups\":\"groups\"}'::jsonb" + }, + "auto_provision": { + "name": "auto_provision", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"enabled\":false,\"allowLocalLinking\":false,\"defaultPermissionNames\":[]}'::jsonb" + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oidc_providers_enabled_order_idx": { + "name": "oidc_providers_enabled_order_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oidc_providers_slug_unique": { + "name": "oidc_providers_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + }, + "oidc_providers_issuer_uri_unique": { + "name": "oidc_providers_issuer_uri_unique", + "nullsNotDistinct": false, + "columns": ["issuer_uri"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_sessions": { + "name": "oidc_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "oidc_subject": { + "name": "oidc_subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_issuer": { + "name": "oidc_issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_session_id": { + "name": "oidc_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token_hint": { + "name": "id_token_hint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idp_refresh_token": { + "name": "idp_refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "name": "revoked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oidc_sessions_user_active_idx": { + "name": "oidc_sessions_user_active_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"oidc_sessions\".\"revoked\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_sessions_subject_issuer_idx": { + "name": "oidc_sessions_subject_issuer_idx", + "columns": [ + { + "expression": "oidc_subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "oidc_issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_sessions_sid_idx": { + "name": "oidc_sessions_sid_idx", + "columns": [ + { + "expression": "oidc_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_sessions_expires_at_idx": { + "name": "oidc_sessions_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_sessions_provider_id_idx": { + "name": "oidc_sessions_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oidc_sessions_user_id_users_id_fk": { + "name": "oidc_sessions_user_id_users_id_fk", + "tableFrom": "oidc_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_sessions_provider_id_oidc_providers_id_fk": { + "name": "oidc_sessions_provider_id_oidc_providers_id_fk", + "tableFrom": "oidc_sessions", + "tableTo": "oidc_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_states": { + "name": "oidc_states", + "schema": "", + "columns": { + "state": { + "name": "state", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oidc_states_expires_at_idx": { + "name": "oidc_states_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oidc_states_provider_id_oidc_providers_id_fk": { + "name": "oidc_states_provider_id_oidc_providers_id_fk", + "tableFrom": "oidc_states", + "tableTo": "oidc_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_used_jtis": { + "name": "oidc_used_jtis", + "schema": "", + "columns": { + "jti": { + "name": "jti", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oidc_used_jtis_expires_at_idx": { + "name": "oidc_used_jtis_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.opds_users": { + "name": "opds_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "opds_sort_order", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'recent'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "opds_users_username_lower_uidx": { + "name": "opds_users_username_lower_uidx", + "columns": [ + { + "expression": "lower(\"username\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "opds_users_user_id_users_id_fk": { + "name": "opds_users_user_id_users_id_fk", + "tableFrom": "opds_users", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "opds_users_username_unique": { + "name": "opds_users_username_unique", + "nullsNotDistinct": false, + "columns": ["username"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_book_entitlements": { + "name": "kobo_book_entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cover_image_id": { + "name": "cover_image_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "needs_legacy_numeric_removal": { + "name": "needs_legacy_numeric_removal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kobo_book_entitlements_user_id_idx": { + "name": "kobo_book_entitlements_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kobo_book_entitlements_book_id_idx": { + "name": "kobo_book_entitlements_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_book_entitlements_user_id_users_id_fk": { + "name": "kobo_book_entitlements_user_id_users_id_fk", + "tableFrom": "kobo_book_entitlements", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_book_entitlements_book_id_books_id_fk": { + "name": "kobo_book_entitlements_book_id_books_id_fk", + "tableFrom": "kobo_book_entitlements", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kobo_book_entitlements_user_book_unique": { + "name": "kobo_book_entitlements_user_book_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "book_id"] + }, + "kobo_book_entitlements_user_entitlement_unique": { + "name": "kobo_book_entitlements_user_entitlement_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "entitlement_id"] + }, + "kobo_book_entitlements_user_cover_unique": { + "name": "kobo_book_entitlements_user_cover_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "cover_image_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_devices": { + "name": "kobo_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "client_device_id": { + "name": "client_device_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kobo_devices_user_id_idx": { + "name": "kobo_devices_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_devices_user_id_users_id_fk": { + "name": "kobo_devices_user_id_users_id_fk", + "tableFrom": "kobo_devices", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kobo_devices_token_unique": { + "name": "kobo_devices_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + }, + "kobo_devices_client_device_id_unique": { + "name": "kobo_devices_client_device_id_unique", + "nullsNotDistinct": false, + "columns": ["client_device_id"] + }, + "kobo_devices_id_user_id_unique": { + "name": "kobo_devices_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_library_snapshots": { + "name": "kobo_library_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "legacy_device_cutoff_at": { + "name": "legacy_device_cutoff_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kobo_library_snapshots_user_id_users_id_fk": { + "name": "kobo_library_snapshots_user_id_users_id_fk", + "tableFrom": "kobo_library_snapshots", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kobo_library_snapshots_user_id_unique": { + "name": "kobo_library_snapshots_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_snapshot_books": { + "name": "kobo_snapshot_books", + "schema": "", + "columns": { + "snapshot_id": { + "name": "snapshot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "synced": { + "name": "synced", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pending_delete": { + "name": "pending_delete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_new": { + "name": "is_new", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "removed_by_device": { + "name": "removed_by_device", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "delivery_hash": { + "name": "delivery_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "metadata_hash": { + "name": "metadata_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kobo_snapshot_books_book_id_idx": { + "name": "kobo_snapshot_books_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kobo_snapshot_books_snapshot_synced_book_idx": { + "name": "kobo_snapshot_books_snapshot_synced_book_idx", + "columns": [ + { + "expression": "snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "synced", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_snapshot_books_snapshot_id_kobo_library_snapshots_id_fk": { + "name": "kobo_snapshot_books_snapshot_id_kobo_library_snapshots_id_fk", + "tableFrom": "kobo_snapshot_books", + "tableTo": "kobo_library_snapshots", + "columnsFrom": ["snapshot_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_snapshot_books_book_id_books_id_fk": { + "name": "kobo_snapshot_books_book_id_books_id_fk", + "tableFrom": "kobo_snapshot_books", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kobo_snapshot_books_snapshot_id_book_id_pk": { + "name": "kobo_snapshot_books_snapshot_id_book_id_pk", + "columns": ["snapshot_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_device_library_snapshots": { + "name": "kobo_device_library_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kobo_device_library_snapshots_user_id_idx": { + "name": "kobo_device_library_snapshots_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_device_library_snapshots_user_id_users_id_fk": { + "name": "kobo_device_library_snapshots_user_id_users_id_fk", + "tableFrom": "kobo_device_library_snapshots", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_device_library_snapshots_device_owner_fk": { + "name": "kobo_device_library_snapshots_device_owner_fk", + "tableFrom": "kobo_device_library_snapshots", + "tableTo": "kobo_devices", + "columnsFrom": ["device_id", "user_id"], + "columnsTo": ["id", "user_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kobo_device_library_snapshots_device_id_unique": { + "name": "kobo_device_library_snapshots_device_id_unique", + "nullsNotDistinct": false, + "columns": ["device_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_reading_states": { + "name": "kobo_reading_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at_kobo": { + "name": "created_at_kobo", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "last_modified_kobo": { + "name": "last_modified_kobo", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "priority_timestamp": { + "name": "priority_timestamp", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "current_bookmark": { + "name": "current_bookmark", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "statistics": { + "name": "statistics", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_info": { + "name": "status_info", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kobo_reading_states_book_id_idx": { + "name": "kobo_reading_states_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_reading_states_user_id_users_id_fk": { + "name": "kobo_reading_states_user_id_users_id_fk", + "tableFrom": "kobo_reading_states", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_reading_states_book_id_books_id_fk": { + "name": "kobo_reading_states_book_id_books_id_fk", + "tableFrom": "kobo_reading_states", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kobo_reading_states_user_id_book_id_unique": { + "name": "kobo_reading_states_user_id_book_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "book_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_device_snapshot_books": { + "name": "kobo_device_snapshot_books", + "schema": "", + "columns": { + "snapshot_id": { + "name": "snapshot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "synced": { + "name": "synced", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pending_delete": { + "name": "pending_delete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_new": { + "name": "is_new", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "removed_by_device": { + "name": "removed_by_device", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "needs_legacy_numeric_removal": { + "name": "needs_legacy_numeric_removal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "delivery_hash": { + "name": "delivery_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "metadata_hash": { + "name": "metadata_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kobo_device_snapshot_books_book_id_idx": { + "name": "kobo_device_snapshot_books_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kobo_device_snapshot_books_snapshot_synced_book_idx": { + "name": "kobo_device_snapshot_books_snapshot_synced_book_idx", + "columns": [ + { + "expression": "snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "synced", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_device_snapshot_books_snapshot_id_kobo_device_library_snapshots_id_fk": { + "name": "kobo_device_snapshot_books_snapshot_id_kobo_device_library_snapshots_id_fk", + "tableFrom": "kobo_device_snapshot_books", + "tableTo": "kobo_device_library_snapshots", + "columnsFrom": ["snapshot_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_device_snapshot_books_book_id_books_id_fk": { + "name": "kobo_device_snapshot_books_book_id_books_id_fk", + "tableFrom": "kobo_device_snapshot_books", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kobo_device_snapshot_books_snapshot_id_book_id_pk": { + "name": "kobo_device_snapshot_books_snapshot_id_book_id_pk", + "columns": ["snapshot_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_sync_history": { + "name": "kobo_sync_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "event": { + "name": "event", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "counts": { + "name": "counts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "error_class": { + "name": "error_class", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kobo_sync_history_user_created_idx": { + "name": "kobo_sync_history_user_created_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kobo_sync_history_device_created_idx": { + "name": "kobo_sync_history_device_created_idx", + "columns": [ + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_sync_history_user_id_users_id_fk": { + "name": "kobo_sync_history_user_id_users_id_fk", + "tableFrom": "kobo_sync_history", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_sync_history_device_id_kobo_devices_id_fk": { + "name": "kobo_sync_history_device_id_kobo_devices_id_fk", + "tableFrom": "kobo_sync_history", + "tableTo": "kobo_devices", + "columnsFrom": ["device_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kobo_sync_history_event_chk": { + "name": "kobo_sync_history_event_chk", + "value": "\"kobo_sync_history\".\"event\" in ('library_sync', 'book_download', 'progress_update', 'annotations_pull', 'annotations_push')" + }, + "kobo_sync_history_status_chk": { + "name": "kobo_sync_history_status_chk", + "value": "\"kobo_sync_history\".\"status\" in ('success', 'failed')" + }, + "kobo_sync_history_duration_nonnegative_chk": { + "name": "kobo_sync_history_duration_nonnegative_chk", + "value": "\"kobo_sync_history\".\"duration_ms\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.kobo_sync_settings": { + "name": "kobo_sync_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reading_threshold": { + "name": "reading_threshold", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "finished_threshold": { + "name": "finished_threshold", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 99 + }, + "convert_to_kepub": { + "name": "convert_to_kepub", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "force_enable_hyphenation": { + "name": "force_enable_hyphenation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "kepub_conversion_limit_mb": { + "name": "kepub_conversion_limit_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "two_way_progress_sync": { + "name": "two_way_progress_sync", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sync_bookorbit_annotations_to_kobo": { + "name": "sync_bookorbit_annotations_to_kobo", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "store_sync": { + "name": "store_sync", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kobo_sync_settings_user_id_users_id_fk": { + "name": "kobo_sync_settings_user_id_users_id_fk", + "tableFrom": "kobo_sync_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kobo_sync_settings_user_id_unique": { + "name": "kobo_sync_settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": { + "kobo_sync_settings_reading_threshold_range_chk": { + "name": "kobo_sync_settings_reading_threshold_range_chk", + "value": "\"kobo_sync_settings\".\"reading_threshold\" >= 0 and \"kobo_sync_settings\".\"reading_threshold\" <= 100" + }, + "kobo_sync_settings_finished_threshold_range_chk": { + "name": "kobo_sync_settings_finished_threshold_range_chk", + "value": "\"kobo_sync_settings\".\"finished_threshold\" >= 0 and \"kobo_sync_settings\".\"finished_threshold\" <= 100" + }, + "kobo_sync_settings_conversion_limit_nonnegative_chk": { + "name": "kobo_sync_settings_conversion_limit_nonnegative_chk", + "value": "\"kobo_sync_settings\".\"kepub_conversion_limit_mb\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.book_dock_files": { + "name": "book_dock_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "absolute_path": { + "name": "absolute_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "embedded_metadata": { + "name": "embedded_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "selected_metadata": { + "name": "selected_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "fetched_metadata": { + "name": "fetched_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cover_path": { + "name": "cover_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_library_id": { + "name": "target_library_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "target_folder_id": { + "name": "target_folder_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fetched_metadata_sources": { + "name": "fetched_metadata_sources", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_edited_at": { + "name": "metadata_edited_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_dock_files_status_idx": { + "name": "book_dock_files_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_dock_files_target_library_id_idx": { + "name": "book_dock_files_target_library_id_idx", + "columns": [ + { + "expression": "target_library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_dock_files_uploaded_by_idx": { + "name": "book_dock_files_uploaded_by_idx", + "columns": [ + { + "expression": "uploaded_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_dock_files_target_library_id_libraries_id_fk": { + "name": "book_dock_files_target_library_id_libraries_id_fk", + "tableFrom": "book_dock_files", + "tableTo": "libraries", + "columnsFrom": ["target_library_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_dock_files_target_folder_id_library_folders_id_fk": { + "name": "book_dock_files_target_folder_id_library_folders_id_fk", + "tableFrom": "book_dock_files", + "tableTo": "library_folders", + "columnsFrom": ["target_folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_dock_files_uploaded_by_users_id_fk": { + "name": "book_dock_files_uploaded_by_users_id_fk", + "tableFrom": "book_dock_files", + "tableTo": "users", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "book_dock_files_absolute_path_unique": { + "name": "book_dock_files_absolute_path_unique", + "nullsNotDistinct": false, + "columns": ["absolute_path"] + } + }, + "policies": {}, + "checkConstraints": { + "book_dock_files_status_chk": { + "name": "book_dock_files_status_chk", + "value": "\"book_dock_files\".\"status\" in ('pending', 'extracting', 'fetching', 'ready', 'error')" + }, + "book_dock_files_confidence_range_chk": { + "name": "book_dock_files_confidence_range_chk", + "value": "\"book_dock_files\".\"confidence\" is null or (\"book_dock_files\".\"confidence\" >= 0 and \"book_dock_files\".\"confidence\" <= 100)" + } + }, + "isRLSEnabled": false + }, + "public.file_write_log": { + "name": "file_write_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "fields_written": { + "name": "fields_written", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "triggered_by": { + "name": "triggered_by", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "written_at": { + "name": "written_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "fwl_book_id_written_at_idx": { + "name": "fwl_book_id_written_at_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "written_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "file_write_log_book_file_id_idx": { + "name": "file_write_log_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "file_write_log_book_id_books_id_fk": { + "name": "file_write_log_book_id_books_id_fk", + "tableFrom": "file_write_log", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_write_log_book_file_id_book_files_id_fk": { + "name": "file_write_log_book_file_id_book_files_id_fk", + "tableFrom": "file_write_log", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "file_write_log_user_id_users_id_fk": { + "name": "file_write_log_user_id_users_id_fk", + "tableFrom": "file_write_log", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "file_write_log_status_chk": { + "name": "file_write_log_status_chk", + "value": "\"file_write_log\".\"status\" in ('success', 'skipped', 'failed')" + }, + "file_write_log_triggered_by_chk": { + "name": "file_write_log_triggered_by_chk", + "value": "\"file_write_log\".\"triggered_by\" in ('auto', 'sync')" + }, + "file_write_log_duration_ms_nonnegative_chk": { + "name": "file_write_log_duration_ms_nonnegative_chk", + "value": "\"file_write_log\".\"duration_ms\" is null or \"file_write_log\".\"duration_ms\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.email_templates": { + "name": "email_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_templates_one_default_per_user_uidx": { + "name": "email_templates_one_default_per_user_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"email_templates\".\"is_default\" = true and \"email_templates\".\"user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_templates_system_name_unique": { + "name": "email_templates_system_name_unique", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"email_templates\".\"user_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_templates_user_id_users_id_fk": { + "name": "email_templates_user_id_users_id_fk", + "tableFrom": "email_templates", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "email_templates_user_id_name_unique": { + "name": "email_templates_user_id_name_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_providers": { + "name": "email_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host": { + "name": "host", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "password_enc": { + "name": "password_enc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from_name": { + "name": "from_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "from_address": { + "name": "from_address", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "auth": { + "name": "auth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "ssl": { + "name": "ssl", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "start_tls": { + "name": "start_tls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_shared": { + "name": "is_shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_system_provider": { + "name": "is_system_provider", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tls_reject_unauthorized": { + "name": "tls_reject_unauthorized", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_providers_one_default_per_user_uidx": { + "name": "email_providers_one_default_per_user_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"email_providers\".\"is_default\" = true and \"email_providers\".\"user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_providers_one_system_provider_uidx": { + "name": "email_providers_one_system_provider_uidx", + "columns": [ + { + "expression": "is_system_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"email_providers\".\"is_system_provider\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_providers_user_id_users_id_fk": { + "name": "email_providers_user_id_users_id_fk", + "tableFrom": "email_providers", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "email_providers_user_id_name_unique": { + "name": "email_providers_user_id_name_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "name"] + }, + "email_providers_id_user_id_unique": { + "name": "email_providers_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": { + "email_providers_port_range_chk": { + "name": "email_providers_port_range_chk", + "value": "\"email_providers\".\"port\" >= 1 and \"email_providers\".\"port\" <= 65535" + } + }, + "isRLSEnabled": false + }, + "public.email_recipient_group_members": { + "name": "email_recipient_group_members", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "recipient_id": { + "name": "recipient_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "email_recipient_group_members_user_id_users_id_fk": { + "name": "email_recipient_group_members_user_id_users_id_fk", + "tableFrom": "email_recipient_group_members", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_recipient_group_members_group_id_email_recipient_groups_id_fk": { + "name": "email_recipient_group_members_group_id_email_recipient_groups_id_fk", + "tableFrom": "email_recipient_group_members", + "tableTo": "email_recipient_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_recipient_group_members_recipient_id_email_recipients_id_fk": { + "name": "email_recipient_group_members_recipient_id_email_recipients_id_fk", + "tableFrom": "email_recipient_group_members", + "tableTo": "email_recipients", + "columnsFrom": ["recipient_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_recipient_group_members_group_user_fk": { + "name": "email_recipient_group_members_group_user_fk", + "tableFrom": "email_recipient_group_members", + "tableTo": "email_recipient_groups", + "columnsFrom": ["group_id", "user_id"], + "columnsTo": ["id", "user_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "email_recipient_group_members_recipient_user_fk": { + "name": "email_recipient_group_members_recipient_user_fk", + "tableFrom": "email_recipient_group_members", + "tableTo": "email_recipients", + "columnsFrom": ["recipient_id", "user_id"], + "columnsTo": ["id", "user_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "email_recipient_group_members_group_id_recipient_id_pk": { + "name": "email_recipient_group_members_group_id_recipient_id_pk", + "columns": ["group_id", "recipient_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_recipient_groups": { + "name": "email_recipient_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "default_template_id": { + "name": "default_template_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "email_recipient_groups_user_id_users_id_fk": { + "name": "email_recipient_groups_user_id_users_id_fk", + "tableFrom": "email_recipient_groups", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_recipient_groups_default_template_id_email_templates_id_fk": { + "name": "email_recipient_groups_default_template_id_email_templates_id_fk", + "tableFrom": "email_recipient_groups", + "tableTo": "email_templates", + "columnsFrom": ["default_template_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "email_recipient_groups_user_id_name_unique": { + "name": "email_recipient_groups_user_id_name_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "name"] + }, + "email_recipient_groups_id_user_id_unique": { + "name": "email_recipient_groups_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_recipients": { + "name": "email_recipients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "device_type": { + "name": "device_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "preferred_format": { + "name": "preferred_format", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "default_template_id": { + "name": "default_template_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_recipients_user_email_lower_uidx": { + "name": "email_recipients_user_email_lower_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_recipients_one_default_per_user_uidx": { + "name": "email_recipients_one_default_per_user_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"email_recipients\".\"is_default\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_recipients_user_id_users_id_fk": { + "name": "email_recipients_user_id_users_id_fk", + "tableFrom": "email_recipients", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_recipients_default_template_id_email_templates_id_fk": { + "name": "email_recipients_default_template_id_email_templates_id_fk", + "tableFrom": "email_recipients", + "tableTo": "email_templates", + "columnsFrom": ["default_template_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "email_recipients_user_id_email_unique": { + "name": "email_recipients_user_id_email_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "email"] + }, + "email_recipients_id_user_id_unique": { + "name": "email_recipients_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": { + "email_recipients_device_type_chk": { + "name": "email_recipients_device_type_chk", + "value": "\"email_recipients\".\"device_type\" is null or \"email_recipients\".\"device_type\" in ('kindle', 'kobo', 'other')" + }, + "email_recipients_preferred_format_chk": { + "name": "email_recipients_preferred_format_chk", + "value": "\"email_recipients\".\"preferred_format\" is null or \"email_recipients\".\"preferred_format\" in ('epub', 'pdf', 'mobi', 'azw3', 'cbz', 'cbr')" + } + }, + "isRLSEnabled": false + }, + "public.email_preferences": { + "name": "email_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "default_provider_id": { + "name": "default_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "default_recipient_id": { + "name": "default_recipient_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "default_template_id": { + "name": "default_template_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "email_preferences_user_id_users_id_fk": { + "name": "email_preferences_user_id_users_id_fk", + "tableFrom": "email_preferences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_preferences_default_provider_id_email_providers_id_fk": { + "name": "email_preferences_default_provider_id_email_providers_id_fk", + "tableFrom": "email_preferences", + "tableTo": "email_providers", + "columnsFrom": ["default_provider_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "email_preferences_default_recipient_id_email_recipients_id_fk": { + "name": "email_preferences_default_recipient_id_email_recipients_id_fk", + "tableFrom": "email_preferences", + "tableTo": "email_recipients", + "columnsFrom": ["default_recipient_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "email_preferences_default_template_id_email_templates_id_fk": { + "name": "email_preferences_default_template_id_email_templates_id_fk", + "tableFrom": "email_preferences", + "tableTo": "email_templates", + "columnsFrom": ["default_template_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "email_preferences_default_recipient_owner_fk": { + "name": "email_preferences_default_recipient_owner_fk", + "tableFrom": "email_preferences", + "tableTo": "email_recipients", + "columnsFrom": ["default_recipient_id", "user_id"], + "columnsTo": ["id", "user_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "email_preferences_user_id_unique": { + "name": "email_preferences_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_send_log": { + "name": "email_send_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "template_id": { + "name": "template_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "to_email": { + "name": "to_email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "to_name": { + "name": "to_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_send_log_user_created_at_idx": { + "name": "email_send_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_send_log_book_id_idx": { + "name": "email_send_log_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_send_log_book_file_id_idx": { + "name": "email_send_log_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_send_log_created_at_idx": { + "name": "email_send_log_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_send_log_status_next_retry_idx": { + "name": "email_send_log_status_next_retry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_send_log_user_id_users_id_fk": { + "name": "email_send_log_user_id_users_id_fk", + "tableFrom": "email_send_log", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_send_log_book_id_books_id_fk": { + "name": "email_send_log_book_id_books_id_fk", + "tableFrom": "email_send_log", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "email_send_log_book_file_id_book_files_id_fk": { + "name": "email_send_log_book_file_id_book_files_id_fk", + "tableFrom": "email_send_log", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "email_send_log_provider_id_email_providers_id_fk": { + "name": "email_send_log_provider_id_email_providers_id_fk", + "tableFrom": "email_send_log", + "tableTo": "email_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "email_send_log_template_id_email_templates_id_fk": { + "name": "email_send_log_template_id_email_templates_id_fk", + "tableFrom": "email_send_log", + "tableTo": "email_templates", + "columnsFrom": ["template_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_send_log_status_chk": { + "name": "email_send_log_status_chk", + "value": "\"email_send_log\".\"status\" in ('pending', 'sent', 'failed')" + }, + "email_send_log_attempt_count_nonnegative_chk": { + "name": "email_send_log_attempt_count_nonnegative_chk", + "value": "\"email_send_log\".\"attempt_count\" >= 0" + }, + "email_send_log_sent_after_created_chk": { + "name": "email_send_log_sent_after_created_chk", + "value": "\"email_send_log\".\"sent_at\" is null or \"email_send_log\".\"sent_at\" >= \"email_send_log\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_url": { + "name": "action_url", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": false + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "read": { + "name": "read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "notifications_user_id_idx": { + "name": "notifications_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_user_unread_idx": { + "name": "notifications_user_unread_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_created_at_idx": { + "name": "notifications_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notifications_user_id_users_id_fk": { + "name": "notifications_user_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dismissed_duplicate_pairs": { + "name": "dismissed_duplicate_pairs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "entity_id_a": { + "name": "entity_id_a", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "entity_id_b": { + "name": "entity_id_b", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dismissed_by": { + "name": "dismissed_by", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dismissed_duplicate_pairs_entity_a_idx": { + "name": "dismissed_duplicate_pairs_entity_a_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id_a", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dismissed_duplicate_pairs_entity_b_idx": { + "name": "dismissed_duplicate_pairs_entity_b_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id_b", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dismissed_duplicate_pairs_dismissed_by_users_id_fk": { + "name": "dismissed_duplicate_pairs_dismissed_by_users_id_fk", + "tableFrom": "dismissed_duplicate_pairs", + "tableTo": "users", + "columnsFrom": ["dismissed_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "dismissed_duplicate_pairs_unique": { + "name": "dismissed_duplicate_pairs_unique", + "nullsNotDistinct": false, + "columns": ["entity_type", "entity_id_a", "entity_id_b"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dismissed_inline_duplicate_pairs": { + "name": "dismissed_inline_duplicate_pairs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "value_a": { + "name": "value_a", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "value_b": { + "name": "value_b", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dismissed_by": { + "name": "dismissed_by", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dismissed_inline_pairs_entity_a_idx": { + "name": "dismissed_inline_pairs_entity_a_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "value_a", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dismissed_inline_pairs_entity_b_idx": { + "name": "dismissed_inline_pairs_entity_b_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "value_b", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dismissed_inline_duplicate_pairs_dismissed_by_users_id_fk": { + "name": "dismissed_inline_duplicate_pairs_dismissed_by_users_id_fk", + "tableFrom": "dismissed_inline_duplicate_pairs", + "tableTo": "users", + "columnsFrom": ["dismissed_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "dismissed_inline_duplicate_pairs_unique": { + "name": "dismissed_inline_duplicate_pairs_unique", + "nullsNotDistinct": false, + "columns": ["entity_type", "value_a", "value_b"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entity_duplicate_candidates": { + "name": "entity_duplicate_candidates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "entity_id_a": { + "name": "entity_id_a", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "entity_id_b": { + "name": "entity_id_b", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sim_score": { + "name": "sim_score", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "computed_at": { + "name": "computed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "entity_dup_candidates_type_sim_idx": { + "name": "entity_dup_candidates_type_sim_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sim_score", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "entity_dup_candidates_entity_a_idx": { + "name": "entity_dup_candidates_entity_a_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id_a", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "entity_dup_candidates_entity_b_idx": { + "name": "entity_dup_candidates_entity_b_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id_b", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entity_duplicate_candidates_unique": { + "name": "entity_duplicate_candidates_unique", + "nullsNotDistinct": false, + "columns": ["entity_type", "entity_id_a", "entity_id_b"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entity_duplicate_scan_status": { + "name": "entity_duplicate_scan_status", + "schema": "", + "columns": { + "entity_type": { + "name": "entity_type", + "type": "varchar(50)", + "primaryKey": true, + "notNull": true + }, + "is_computing": { + "name": "is_computing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "computed_at": { + "name": "computed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "total_pairs": { + "name": "total_pairs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "threshold": { + "name": "threshold", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "processed_count": { + "name": "processed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_count": { + "name": "total_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.server_fonts": { + "name": "server_fonts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "family_name": { + "name": "family_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "original_file_name": { + "name": "original_file_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "stored_file_name": { + "name": "stored_file_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 400 + }, + "style": { + "name": "style", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sf_hash_uidx": { + "name": "sf_hash_uidx", + "columns": [ + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sf_family_weight_style_uidx": { + "name": "sf_family_weight_style_uidx", + "columns": [ + { + "expression": "family_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "style", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "server_fonts_uploaded_by_users_id_fk": { + "name": "server_fonts_uploaded_by_users_id_fk", + "tableFrom": "server_fonts", + "tableTo": "users", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "server_fonts_format_chk": { + "name": "server_fonts_format_chk", + "value": "\"server_fonts\".\"format\" in ('ttf', 'otf', 'woff', 'woff2')" + }, + "server_fonts_weight_chk": { + "name": "server_fonts_weight_chk", + "value": "\"server_fonts\".\"weight\" >= 100 and \"server_fonts\".\"weight\" <= 900 and \"server_fonts\".\"weight\" % 100 = 0" + }, + "server_fonts_style_chk": { + "name": "server_fonts_style_chk", + "value": "\"server_fonts\".\"style\" in ('normal', 'italic')" + } + }, + "isRLSEnabled": false + }, + "public.user_fonts": { + "name": "user_fonts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "family_name": { + "name": "family_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "original_file_name": { + "name": "original_file_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "stored_file_name": { + "name": "stored_file_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 400 + }, + "style": { + "name": "style", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uf_user_hash_uidx": { + "name": "uf_user_hash_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uf_user_family_weight_style_uidx": { + "name": "uf_user_family_weight_style_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "family_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "style", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fonts_user_id_users_id_fk": { + "name": "user_fonts_user_id_users_id_fk", + "tableFrom": "user_fonts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_fonts_format_chk": { + "name": "user_fonts_format_chk", + "value": "\"user_fonts\".\"format\" in ('ttf', 'otf', 'woff', 'woff2')" + }, + "user_fonts_weight_chk": { + "name": "user_fonts_weight_chk", + "value": "\"user_fonts\".\"weight\" >= 100 and \"user_fonts\".\"weight\" <= 900 and \"user_fonts\".\"weight\" % 100 = 0" + }, + "user_fonts_style_chk": { + "name": "user_fonts_style_chk", + "value": "\"user_fonts\".\"style\" in ('normal', 'italic')" + } + }, + "isRLSEnabled": false + }, + "public.book_file_chapters": { + "name": "book_file_chapters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chapter_index": { + "name": "chapter_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "href": { + "name": "href", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "spine_index": { + "name": "spine_index", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "book_file_chapters_book_file_id_chapter_index_uidx": { + "name": "book_file_chapters_book_file_id_chapter_index_uidx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chapter_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_file_chapters_book_file_id_idx": { + "name": "book_file_chapters_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_file_chapters_book_file_id_book_files_id_fk": { + "name": "book_file_chapters_book_file_id_book_files_id_fk", + "tableFrom": "book_file_chapters", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_file_hash_history": { + "name": "book_file_hash_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_file_hash_history_book_file_id_file_hash_idx": { + "name": "book_file_hash_history_book_file_id_file_hash_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_file_hash_history_file_hash_idx": { + "name": "book_file_hash_history_file_hash_idx", + "columns": [ + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_file_hash_history_book_file_id_book_files_id_fk": { + "name": "book_file_hash_history_book_file_id_book_files_id_fk", + "tableFrom": "book_file_hash_history", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_file_hash_history_reason_chk": { + "name": "book_file_hash_history_reason_chk", + "value": "\"book_file_hash_history\".\"reason\" in ('file_write', 'external_change', 'rescan')" + } + }, + "isRLSEnabled": false + }, + "public.koreader_book_hash_links": { + "name": "koreader_book_hash_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "koreader_title": { + "name": "koreader_title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "koreader_authors": { + "name": "koreader_authors", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "koreader_last_open": { + "name": "koreader_last_open", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_book_hash_links_user_hash_uidx": { + "name": "koreader_book_hash_links_user_hash_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_book_hash_links_user_updated_idx": { + "name": "koreader_book_hash_links_user_updated_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_book_hash_links_book_file_id_idx": { + "name": "koreader_book_hash_links_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_book_hash_links_user_id_users_id_fk": { + "name": "koreader_book_hash_links_user_id_users_id_fk", + "tableFrom": "koreader_book_hash_links", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "koreader_book_hash_links_book_file_id_book_files_id_fk": { + "name": "koreader_book_hash_links_book_file_id_book_files_id_fk", + "tableFrom": "koreader_book_hash_links", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "koreader_book_hash_links_hash_chk": { + "name": "koreader_book_hash_links_hash_chk", + "value": "\"koreader_book_hash_links\".\"hash\" ~ '^[0-9a-f]{32}$'" + } + }, + "isRLSEnabled": false + }, + "public.koreader_bookmark_links": { + "name": "koreader_bookmark_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "bookmark_id": { + "name": "bookmark_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "koreader_key": { + "name": "koreader_key", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "device_datetime": { + "name": "device_datetime", + "type": "varchar(19)", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_bookmark_links_bookmark_device_uidx": { + "name": "koreader_bookmark_links_bookmark_device_uidx", + "columns": [ + { + "expression": "bookmark_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_bookmark_links_user_device_key_idx": { + "name": "koreader_bookmark_links_user_device_key_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "koreader_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_bookmark_links_user_device_idx": { + "name": "koreader_bookmark_links_user_device_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_bookmark_links_bookmark_id_idx": { + "name": "koreader_bookmark_links_bookmark_id_idx", + "columns": [ + { + "expression": "bookmark_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_bookmark_links_bookmark_id_bookmarks_id_fk": { + "name": "koreader_bookmark_links_bookmark_id_bookmarks_id_fk", + "tableFrom": "koreader_bookmark_links", + "tableTo": "bookmarks", + "columnsFrom": ["bookmark_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "koreader_bookmark_links_user_id_users_id_fk": { + "name": "koreader_bookmark_links_user_id_users_id_fk", + "tableFrom": "koreader_bookmark_links", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_device_progress": { + "name": "koreader_device_progress", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device": { + "name": "device", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'KOReader'" + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "percentage": { + "name": "percentage", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "progress": { + "name": "progress", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chapter_index": { + "name": "chapter_index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sync_timestamp": { + "name": "sync_timestamp", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "orphaned": { + "name": "orphaned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "orphaned_hash": { + "name": "orphaned_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_device_progress_book_user_device_uidx": { + "name": "koreader_device_progress_book_user_device_uidx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"koreader_device_progress\".\"orphaned\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_device_progress_orphaned_hash_idx": { + "name": "koreader_device_progress_orphaned_hash_idx", + "columns": [ + { + "expression": "orphaned_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"koreader_device_progress\".\"orphaned\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_device_progress_user_updated_at_idx": { + "name": "koreader_device_progress_user_updated_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_device_progress_book_file_id_idx": { + "name": "koreader_device_progress_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"koreader_device_progress\".\"book_file_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_device_progress_book_file_id_book_files_id_fk": { + "name": "koreader_device_progress_book_file_id_book_files_id_fk", + "tableFrom": "koreader_device_progress", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "koreader_device_progress_user_id_users_id_fk": { + "name": "koreader_device_progress_user_id_users_id_fk", + "tableFrom": "koreader_device_progress", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "koreader_device_progress_percentage_range_chk": { + "name": "koreader_device_progress_percentage_range_chk", + "value": "\"koreader_device_progress\".\"percentage\" is null or (\"koreader_device_progress\".\"percentage\" >= 0 and \"koreader_device_progress\".\"percentage\" <= 1)" + } + }, + "isRLSEnabled": false + }, + "public.koreader_device_retirements": { + "name": "koreader_device_retirements", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_device_retirements_user_id_idx": { + "name": "koreader_device_retirements_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_device_retirements_user_id_users_id_fk": { + "name": "koreader_device_retirements_user_id_users_id_fk", + "tableFrom": "koreader_device_retirements", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_device_retirements_user_id_device_id_pk": { + "name": "koreader_device_retirements_user_id_device_id_pk", + "columns": ["user_id", "device_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_device_settings": { + "name": "koreader_device_settings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "file_naming_pattern": { + "name": "file_naming_pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "series_file_naming_pattern": { + "name": "series_file_naming_pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "standalone_file_naming_pattern": { + "name": "standalone_file_naming_pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_device_settings_user_id_idx": { + "name": "koreader_device_settings_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_device_settings_user_id_users_id_fk": { + "name": "koreader_device_settings_user_id_users_id_fk", + "tableFrom": "koreader_device_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_device_settings_user_id_device_id_pk": { + "name": "koreader_device_settings_user_id_device_id_pk", + "columns": ["user_id", "device_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_device_sweeps": { + "name": "koreader_device_sweeps", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "device_model": { + "name": "device_model", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'KOReader'" + }, + "plugin_version": { + "name": "plugin_version", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "last_sweep_at": { + "name": "last_sweep_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_sweep_books_matched": { + "name": "last_sweep_books_matched", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_sweep_page_stats": { + "name": "last_sweep_page_stats", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_sweep_annotations": { + "name": "last_sweep_annotations", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kds_user_id_idx": { + "name": "kds_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_device_sweeps_user_id_users_id_fk": { + "name": "koreader_device_sweeps_user_id_users_id_fk", + "tableFrom": "koreader_device_sweeps", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_device_sweeps_user_id_device_id_pk": { + "name": "koreader_device_sweeps_user_id_device_id_pk", + "columns": ["user_id", "device_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_page_stats": { + "name": "koreader_page_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "page": { + "name": "page", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "start_time": { + "name": "start_time", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "total_pages": { + "name": "total_pages", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kps_user_file_device_page_start_uidx": { + "name": "kps_user_file_device_page_start_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "page", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "start_time", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kps_user_file_device_start_idx": { + "name": "kps_user_file_device_start_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "start_time", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kps_user_id_idx": { + "name": "kps_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_page_stats_book_file_id_idx": { + "name": "koreader_page_stats_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_page_stats_user_id_users_id_fk": { + "name": "koreader_page_stats_user_id_users_id_fk", + "tableFrom": "koreader_page_stats", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "koreader_page_stats_book_file_id_book_files_id_fk": { + "name": "koreader_page_stats_book_file_id_book_files_id_fk", + "tableFrom": "koreader_page_stats", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "koreader_page_stats_duration_nonnegative_chk": { + "name": "koreader_page_stats_duration_nonnegative_chk", + "value": "\"koreader_page_stats\".\"duration_seconds\" >= 0" + }, + "koreader_page_stats_page_nonnegative_chk": { + "name": "koreader_page_stats_page_nonnegative_chk", + "value": "\"koreader_page_stats\".\"page\" >= 0" + }, + "koreader_page_stats_total_pages_positive_chk": { + "name": "koreader_page_stats_total_pages_positive_chk", + "value": "\"koreader_page_stats\".\"total_pages\" > 0" + }, + "koreader_page_stats_start_time_positive_chk": { + "name": "koreader_page_stats_start_time_positive_chk", + "value": "\"koreader_page_stats\".\"start_time\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.koreader_progress_resets": { + "name": "koreader_progress_resets", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_progress_resets_book_file_id_idx": { + "name": "koreader_progress_resets_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_progress_resets_user_id_users_id_fk": { + "name": "koreader_progress_resets_user_id_users_id_fk", + "tableFrom": "koreader_progress_resets", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "koreader_progress_resets_book_file_id_book_files_id_fk": { + "name": "koreader_progress_resets_book_file_id_book_files_id_fk", + "tableFrom": "koreader_progress_resets", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_progress_resets_user_id_book_file_id_pk": { + "name": "koreader_progress_resets_user_id_book_file_id_pk", + "columns": ["user_id", "book_file_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_unmatched_book_devices": { + "name": "koreader_unmatched_book_devices", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_unmatched_book_devices_user_device_idx": { + "name": "koreader_unmatched_book_devices_user_device_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_unmatched_book_devices_user_id_users_id_fk": { + "name": "koreader_unmatched_book_devices_user_id_users_id_fk", + "tableFrom": "koreader_unmatched_book_devices", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "koreader_unmatched_book_devices_user_hash_fk": { + "name": "koreader_unmatched_book_devices_user_hash_fk", + "tableFrom": "koreader_unmatched_book_devices", + "tableTo": "koreader_unmatched_books", + "columnsFrom": ["user_id", "hash"], + "columnsTo": ["user_id", "hash"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_unmatched_book_devices_user_id_hash_device_id_pk": { + "name": "koreader_unmatched_book_devices_user_id_hash_device_id_pk", + "columns": ["user_id", "hash", "device_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "koreader_unmatched_book_devices_hash_chk": { + "name": "koreader_unmatched_book_devices_hash_chk", + "value": "\"koreader_unmatched_book_devices\".\"hash\" ~ '^[0-9a-f]{32}$'" + } + }, + "isRLSEnabled": false + }, + "public.koreader_unmatched_books": { + "name": "koreader_unmatched_books", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "authors": { + "name": "authors", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_open": { + "name": "last_open", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'statistics'" + }, + "metadata_ambiguous": { + "name": "metadata_ambiguous", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_unmatched_books_user_seen_idx": { + "name": "koreader_unmatched_books_user_seen_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"last_seen_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_unmatched_books_user_id_users_id_fk": { + "name": "koreader_unmatched_books_user_id_users_id_fk", + "tableFrom": "koreader_unmatched_books", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_unmatched_books_user_id_hash_pk": { + "name": "koreader_unmatched_books_user_id_hash_pk", + "columns": ["user_id", "hash"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "koreader_unmatched_books_hash_chk": { + "name": "koreader_unmatched_books_hash_chk", + "value": "\"koreader_unmatched_books\".\"hash\" ~ '^[0-9a-f]{32}$'" + }, + "koreader_unmatched_books_source_chk": { + "name": "koreader_unmatched_books_source_chk", + "value": "\"koreader_unmatched_books\".\"source\" in ('current_file', 'file', 'statistics')" + } + }, + "isRLSEnabled": false + }, + "public.koreader_user_settings": { + "name": "koreader_user_settings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "default_file_naming_pattern": { + "name": "default_file_naming_pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "koreader_user_settings_user_id_users_id_fk": { + "name": "koreader_user_settings_user_id_users_id_fk", + "tableFrom": "koreader_user_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_users": { + "name": "koreader_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password_md5": { + "name": "password_md5", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "sync_enabled": { + "name": "sync_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_users_user_id_uidx": { + "name": "koreader_users_user_id_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_users_username_uidx": { + "name": "koreader_users_username_uidx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_users_user_id_users_id_fk": { + "name": "koreader_users_user_id_users_id_fk", + "tableFrom": "koreader_users", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.achievements": { + "name": "achievements", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "group_key": { + "name": "group_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "tier": { + "name": "tier", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "icon_name": { + "name": "icon_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "rarity": { + "name": "rarity", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "threshold": { + "name": "threshold", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hidden": { + "name": "hidden", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "achievements_category_sort_idx": { + "name": "achievements_category_sort_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "achievements_group_key_idx": { + "name": "achievements_group_key_idx", + "columns": [ + { + "expression": "group_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "achievements_category_chk": { + "name": "achievements_category_chk", + "value": "\"achievements\".\"category\" in ('reading', 'library', 'exploration', 'dedication', 'devices')" + }, + "achievements_rarity_chk": { + "name": "achievements_rarity_chk", + "value": "\"achievements\".\"rarity\" in ('common', 'rare', 'epic', 'legendary')" + }, + "achievements_tier_chk": { + "name": "achievements_tier_chk", + "value": "\"achievements\".\"tier\" is null or (\"achievements\".\"tier\" >= 1 and \"achievements\".\"tier\" <= 4)" + } + }, + "isRLSEnabled": false + }, + "public.user_achievements": { + "name": "user_achievements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "achievement_key": { + "name": "achievement_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "awarded_at": { + "name": "awarded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "context_json": { + "name": "context_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_achievements_user_key_uidx": { + "name": "user_achievements_user_key_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "achievement_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_achievements_user_id_idx": { + "name": "user_achievements_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_achievements_user_awarded_at_idx": { + "name": "user_achievements_user_awarded_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "awarded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_achievements_user_id_users_id_fk": { + "name": "user_achievements_user_id_users_id_fk", + "tableFrom": "user_achievements", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_achievements_achievement_key_achievements_key_fk": { + "name": "user_achievements_achievement_key_achievements_key_fk", + "tableFrom": "user_achievements", + "tableTo": "achievements", + "columnsFrom": ["achievement_key"], + "columnsTo": ["key"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.hardcover_book_state": { + "name": "hardcover_book_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hardcover_book_id": { + "name": "hardcover_book_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hardcover_edition_id": { + "name": "hardcover_edition_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hardcover_user_book_id": { + "name": "hardcover_user_book_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hardcover_read_id": { + "name": "hardcover_read_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "match_method": { + "name": "match_method", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "match_error": { + "name": "match_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_synced_status": { + "name": "last_synced_status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "last_synced_progress": { + "name": "last_synced_progress", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "last_synced_rating": { + "name": "last_synced_rating", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_synced_started_at": { + "name": "last_synced_started_at", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "last_synced_finished_at": { + "name": "last_synced_finished_at", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_override": { + "name": "sync_override", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "sync_excluded": { + "name": "sync_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "hardcover_book_state_book_id_idx": { + "name": "hardcover_book_state_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "hardcover_book_state_user_id_users_id_fk": { + "name": "hardcover_book_state_user_id_users_id_fk", + "tableFrom": "hardcover_book_state", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "hardcover_book_state_book_id_books_id_fk": { + "name": "hardcover_book_state_book_id_books_id_fk", + "tableFrom": "hardcover_book_state", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "hardcover_book_state_user_book_uidx": { + "name": "hardcover_book_state_user_book_uidx", + "nullsNotDistinct": false, + "columns": ["user_id", "book_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.hardcover_user_settings": { + "name": "hardcover_user_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "api_token": { + "name": "api_token", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "book_sync_mode": { + "name": "book_sync_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'all_eligible'" + }, + "auto_sync_on_status_change": { + "name": "auto_sync_on_status_change", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_sync_on_progress_update": { + "name": "auto_sync_on_progress_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_sync_on_rating_change": { + "name": "auto_sync_on_rating_change", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "privacy_setting_id": { + "name": "privacy_setting_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "hardcover_user_settings_user_id_users_id_fk": { + "name": "hardcover_user_settings_user_id_users_id_fk", + "tableFrom": "hardcover_user_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "hardcover_user_settings_user_id_unique": { + "name": "hardcover_user_settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.readwise_user_settings": { + "name": "readwise_user_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "api_token": { + "name": "api_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_synced_annotation_id": { + "name": "last_synced_annotation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disabled_reason": { + "name": "disabled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "readwise_user_settings_user_id_users_id_fk": { + "name": "readwise_user_settings_user_id_users_id_fk", + "tableFrom": "readwise_user_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "readwise_user_settings_user_id_unique": { + "name": "readwise_user_settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.storygraph_book_state": { + "name": "storygraph_book_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "storygraph_book_id": { + "name": "storygraph_book_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "match_method": { + "name": "match_method", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "match_error": { + "name": "match_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_synced_status": { + "name": "last_synced_status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "last_synced_progress": { + "name": "last_synced_progress", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_override": { + "name": "sync_override", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "storygraph_book_state_book_id_idx": { + "name": "storygraph_book_state_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "storygraph_book_state_user_sync_override_idx": { + "name": "storygraph_book_state_user_sync_override_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sync_override", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "storygraph_book_state_user_sync_error_idx": { + "name": "storygraph_book_state_user_sync_error_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"storygraph_book_state\".\"sync_error\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "storygraph_book_state_user_id_users_id_fk": { + "name": "storygraph_book_state_user_id_users_id_fk", + "tableFrom": "storygraph_book_state", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "storygraph_book_state_book_id_books_id_fk": { + "name": "storygraph_book_state_book_id_books_id_fk", + "tableFrom": "storygraph_book_state", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "storygraph_book_state_user_book_uidx": { + "name": "storygraph_book_state_user_book_uidx", + "nullsNotDistinct": false, + "columns": ["user_id", "book_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.storygraph_user_settings": { + "name": "storygraph_user_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "session_cookie": { + "name": "session_cookie", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "remember_token": { + "name": "remember_token", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "book_sync_mode": { + "name": "book_sync_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'all_eligible'" + }, + "auto_sync_on_status_change": { + "name": "auto_sync_on_status_change", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_sync_on_progress_update": { + "name": "auto_sync_on_progress_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "storygraph_user_settings_user_id_users_id_fk": { + "name": "storygraph_user_settings_user_id_users_id_fk", + "tableFrom": "storygraph_user_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "storygraph_user_settings_user_id_unique": { + "name": "storygraph_user_settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_reading_insight_view_sessions": { + "name": "shared_reading_insight_view_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "viewer_user_id": { + "name": "viewer_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sharing_level": { + "name": "sharing_level", + "type": "reading_insights_sharing_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "shared_insight_view_subject_created_idx": { + "name": "shared_insight_view_subject_created_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "shared_insight_view_expires_idx": { + "name": "shared_insight_view_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_reading_insight_view_sessions_viewer_user_id_users_id_fk": { + "name": "shared_reading_insight_view_sessions_viewer_user_id_users_id_fk", + "tableFrom": "shared_reading_insight_view_sessions", + "tableTo": "users", + "columnsFrom": ["viewer_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_reading_insight_view_sessions_subject_user_id_users_id_fk": { + "name": "shared_reading_insight_view_sessions_subject_user_id_users_id_fk", + "tableFrom": "shared_reading_insight_view_sessions", + "tableTo": "users", + "columnsFrom": ["subject_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_preferences": { + "name": "user_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "up_user_category_idx": { + "name": "up_user_category_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.content_filter_type": { + "name": "content_filter_type", + "schema": "public", + "values": ["include", "exclude"] + }, + "public.library_access_level": { + "name": "library_access_level", + "schema": "public", + "values": ["viewer", "editor", "owner"] + }, + "public.reading_insights_sharing_level": { + "name": "reading_insights_sharing_level", + "schema": "public", + "values": ["private", "summary", "detailed"] + }, + "public.user_avatar_source": { + "name": "user_avatar_source", + "schema": "public", + "values": ["none", "external", "uploaded"] + }, + "public.opds_sort_order": { + "name": "opds_sort_order", + "schema": "public", + "values": ["recent", "title_asc", "title_desc", "author_asc", "author_desc", "series_asc", "series_desc"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/server/src/db/migrations/meta/0071_snapshot.json b/server/src/db/migrations/meta/0071_snapshot.json new file mode 100644 index 000000000..572aa56d7 --- /dev/null +++ b/server/src/db/migrations/meta/0071_snapshot.json @@ -0,0 +1,16284 @@ +{ + "id": "ab8ef635-ee73-4686-b17e-96fefdaf0d1e", + "prevId": "bbb24e2e-c4ed-4518-86ea-41c30a01c4a5", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "actor_username": { + "name": "actor_username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "resource_id": { + "name": "resource_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_user_id": { + "name": "idx_audit_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_actor_username": { + "name": "idx_audit_actor_username", + "columns": [ + { + "expression": "actor_username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_actor_username_trgm": { + "name": "idx_audit_actor_username_trgm", + "columns": [ + { + "expression": "actor_username", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_audit_resource": { + "name": "idx_audit_resource", + "columns": [ + { + "expression": "resource", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_resource_id": { + "name": "idx_audit_resource_id", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_resource_trgm": { + "name": "idx_audit_resource_trgm", + "columns": [ + { + "expression": "resource", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_audit_action": { + "name": "idx_audit_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_action_trgm": { + "name": "idx_audit_action_trgm", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_audit_description_trgm": { + "name": "idx_audit_description_trgm", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_audit_meta_text_trgm": { + "name": "idx_audit_meta_text_trgm", + "columns": [ + { + "expression": "(\"meta\"::text) gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_audit_ip": { + "name": "idx_audit_ip", + "columns": [ + { + "expression": "ip", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_created_at": { + "name": "idx_audit_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_reading_insights_access": { + "name": "idx_audit_reading_insights_access", + "columns": [ + { + "expression": "resource", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_user_id_users_id_fk": { + "name": "audit_log_user_id_users_id_fk", + "tableFrom": "audit_log", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_settings": { + "name": "app_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "app_settings_key_unique": { + "name": "app_settings_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.magic_access_tokens": { + "name": "magic_access_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "label": { + "name": "label", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "raw_token": { + "name": "raw_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "use_count": { + "name": "use_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "magic_access_tokens_user_id_idx": { + "name": "magic_access_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "magic_access_tokens_user_id_users_id_fk": { + "name": "magic_access_tokens_user_id_users_id_fk", + "tableFrom": "magic_access_tokens", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "magic_access_tokens_created_by_users_id_fk": { + "name": "magic_access_tokens_created_by_users_id_fk", + "tableFrom": "magic_access_tokens", + "tableTo": "users", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "magic_access_tokens_token_hash_unique": { + "name": "magic_access_tokens_token_hash_unique", + "nullsNotDistinct": false, + "columns": ["token_hash"] + } + }, + "policies": {}, + "checkConstraints": { + "magic_access_tokens_use_count_nonneg_chk": { + "name": "magic_access_tokens_use_count_nonneg_chk", + "value": "\"magic_access_tokens\".\"use_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.password_reset_tokens": { + "name": "password_reset_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "password_reset_tokens_user_id_idx": { + "name": "password_reset_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "password_reset_tokens_expires_at_idx": { + "name": "password_reset_tokens_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "password_reset_tokens_user_id_users_id_fk": { + "name": "password_reset_tokens_user_id_users_id_fk", + "tableFrom": "password_reset_tokens", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "password_reset_tokens_token_hash_unique": { + "name": "password_reset_tokens_token_hash_unique", + "nullsNotDistinct": false, + "columns": ["token_hash"] + } + }, + "policies": {}, + "checkConstraints": { + "password_reset_tokens_expires_after_created_chk": { + "name": "password_reset_tokens_expires_after_created_chk", + "value": "\"password_reset_tokens\".\"expires_at\" > \"password_reset_tokens\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.refresh_tokens": { + "name": "refresh_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "replaced_by_token_hash": { + "name": "replaced_by_token_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "refresh_tokens_user_id_idx": { + "name": "refresh_tokens_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "refresh_tokens_expires_at_idx": { + "name": "refresh_tokens_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "refresh_tokens_user_id_users_id_fk": { + "name": "refresh_tokens_user_id_users_id_fk", + "tableFrom": "refresh_tokens", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "refresh_tokens_token_hash_unique": { + "name": "refresh_tokens_token_hash_unique", + "nullsNotDistinct": false, + "columns": ["token_hash"] + } + }, + "policies": {}, + "checkConstraints": { + "refresh_tokens_expires_after_created_chk": { + "name": "refresh_tokens_expires_after_created_chk", + "value": "\"refresh_tokens\".\"expires_at\" > \"refresh_tokens\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.user_content_filter_genres": { + "name": "user_content_filter_genres", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "filter_type": { + "name": "filter_type", + "type": "content_filter_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "genre_id": { + "name": "genre_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "user_content_filter_genres_genre_id_idx": { + "name": "user_content_filter_genres_genre_id_idx", + "columns": [ + { + "expression": "genre_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_content_filter_genres_user_id_users_id_fk": { + "name": "user_content_filter_genres_user_id_users_id_fk", + "tableFrom": "user_content_filter_genres", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_content_filter_genres_genre_id_genres_id_fk": { + "name": "user_content_filter_genres_genre_id_genres_id_fk", + "tableFrom": "user_content_filter_genres", + "tableTo": "genres", + "columnsFrom": ["genre_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_content_filter_genres_user_id_filter_type_genre_id_pk": { + "name": "user_content_filter_genres_user_id_filter_type_genre_id_pk", + "columns": ["user_id", "filter_type", "genre_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_content_filter_tags": { + "name": "user_content_filter_tags", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "filter_type": { + "name": "filter_type", + "type": "content_filter_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "user_content_filter_tags_tag_id_idx": { + "name": "user_content_filter_tags_tag_id_idx", + "columns": [ + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_content_filter_tags_user_id_users_id_fk": { + "name": "user_content_filter_tags_user_id_users_id_fk", + "tableFrom": "user_content_filter_tags", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_content_filter_tags_tag_id_tags_id_fk": { + "name": "user_content_filter_tags_tag_id_tags_id_fk", + "tableFrom": "user_content_filter_tags", + "tableTo": "tags", + "columnsFrom": ["tag_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_content_filter_tags_user_id_filter_type_tag_id_pk": { + "name": "user_content_filter_tags_user_id_filter_type_tag_id_pk", + "columns": ["user_id", "filter_type", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_library_access": { + "name": "user_library_access", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "access_level": { + "name": "access_level", + "type": "library_access_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'viewer'" + } + }, + "indexes": { + "user_library_access_library_user_idx": { + "name": "user_library_access_library_user_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_library_access_user_id_users_id_fk": { + "name": "user_library_access_user_id_users_id_fk", + "tableFrom": "user_library_access", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_library_access_library_id_libraries_id_fk": { + "name": "user_library_access_library_id_libraries_id_fk", + "tableFrom": "user_library_access", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_library_access_user_id_library_id_pk": { + "name": "user_library_access_user_id_library_id_pk", + "columns": ["user_id", "library_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_permissions": { + "name": "user_permissions", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "permission_name": { + "name": "permission_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_permissions_user_id_users_id_fk": { + "name": "user_permissions_user_id_users_id_fk", + "tableFrom": "user_permissions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_permissions_user_id_permission_name_pk": { + "name": "user_permissions_user_id_permission_name_pk", + "columns": ["user_id", "permission_name"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_superuser": { + "name": "is_superuser", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_default_password": { + "name": "is_default_password", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "token_version": { + "name": "token_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "failed_login_attempts": { + "name": "failed_login_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "locked_until": { + "name": "locked_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "oidc_subject": { + "name": "oidc_subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oidc_issuer": { + "name": "oidc_issuer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "avatar_source": { + "name": "avatar_source", + "type": "user_avatar_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "avatar_version": { + "name": "avatar_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "provisioning_method": { + "name": "provisioning_method", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'local'" + }, + "last_login_at": { + "name": "last_login_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_authenticated_at": { + "name": "last_authenticated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reading_insights_sharing_level": { + "name": "reading_insights_sharing_level", + "type": "reading_insights_sharing_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'private'" + }, + "reading_insights_consented_at": { + "name": "reading_insights_consented_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_username_lower_uidx": { + "name": "users_username_lower_uidx", + "columns": [ + { + "expression": "lower(\"username\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_email_lower_uidx": { + "name": "users_email_lower_uidx", + "columns": [ + { + "expression": "lower(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"users\".\"email\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_oidc_subject_issuer_uidx": { + "name": "users_oidc_subject_issuer_uidx", + "columns": [ + { + "expression": "oidc_subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "oidc_issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"users\".\"oidc_subject\" is not null and \"users\".\"oidc_issuer\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "users_last_authenticated_at_idx": { + "name": "users_last_authenticated_at_idx", + "columns": [ + { + "expression": "last_authenticated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": ["username"] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": { + "users_provisioning_method_chk": { + "name": "users_provisioning_method_chk", + "value": "\"users\".\"provisioning_method\" in ('local', 'manual', 'oidc', 'shared')" + }, + "users_token_version_nonnegative_chk": { + "name": "users_token_version_nonnegative_chk", + "value": "\"users\".\"token_version\" >= 0" + }, + "users_failed_login_attempts_nonnegative_chk": { + "name": "users_failed_login_attempts_nonnegative_chk", + "value": "\"users\".\"failed_login_attempts\" >= 0" + }, + "users_avatar_version_nonnegative_chk": { + "name": "users_avatar_version_nonnegative_chk", + "value": "\"users\".\"avatar_version\" >= 0" + }, + "users_reading_insights_consent_chk": { + "name": "users_reading_insights_consent_chk", + "value": "(\"users\".\"reading_insights_sharing_level\" = 'private' and \"users\".\"reading_insights_consented_at\" is null) or (\"users\".\"reading_insights_sharing_level\" <> 'private' and \"users\".\"reading_insights_consented_at\" is not null)" + } + }, + "isRLSEnabled": false + }, + "public.comic_metadata": { + "name": "comic_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "issue_number": { + "name": "issue_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volume_name": { + "name": "volume_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pencillers": { + "name": "pencillers", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "inkers": { + "name": "inkers", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "colorists": { + "name": "colorists", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "letterers": { + "name": "letterers", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "cover_artists": { + "name": "cover_artists", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "characters": { + "name": "characters", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "teams": { + "name": "teams", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "locations": { + "name": "locations", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "story_arcs": { + "name": "story_arcs", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "comic_metadata_book_id_books_id_fk": { + "name": "comic_metadata_book_id_books_id_fk", + "tableFrom": "comic_metadata", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "comic_metadata_book_id_unique": { + "name": "comic_metadata_book_id_unique", + "nullsNotDistinct": false, + "columns": ["book_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_custom_metadata_values": { + "name": "book_custom_metadata_values", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "field_id": { + "name": "field_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value_text": { + "name": "value_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "value_number": { + "name": "value_number", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "value_date": { + "name": "value_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "value_boolean": { + "name": "value_boolean", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_custom_metadata_values_field_idx": { + "name": "book_custom_metadata_values_field_idx", + "columns": [ + { + "expression": "field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_custom_metadata_values_book_id_books_id_fk": { + "name": "book_custom_metadata_values_book_id_books_id_fk", + "tableFrom": "book_custom_metadata_values", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_custom_metadata_values_field_id_custom_metadata_fields_id_fk": { + "name": "book_custom_metadata_values_field_id_custom_metadata_fields_id_fk", + "tableFrom": "book_custom_metadata_values", + "tableTo": "custom_metadata_fields", + "columnsFrom": ["field_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_custom_metadata_values_book_id_field_id_pk": { + "name": "book_custom_metadata_values_book_id_field_id_pk", + "columns": ["book_id", "field_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_custom_metadata_values_single_value_chk": { + "name": "book_custom_metadata_values_single_value_chk", + "value": "(\n (\"book_custom_metadata_values\".\"value_text\" is not null)::int +\n (\"book_custom_metadata_values\".\"value_number\" is not null)::int +\n (\"book_custom_metadata_values\".\"value_date\" is not null)::int +\n (\"book_custom_metadata_values\".\"value_boolean\" is not null)::int\n ) <= 1" + } + }, + "isRLSEnabled": false + }, + "public.custom_metadata_fields": { + "name": "custom_metadata_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_metadata_fields_key_uidx": { + "name": "custom_metadata_fields_key_uidx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_metadata_fields_active_order_idx": { + "name": "custom_metadata_fields_active_order_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "label", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "custom_metadata_fields_type_chk": { + "name": "custom_metadata_fields_type_chk", + "value": "\"custom_metadata_fields\".\"type\" in ('text', 'url', 'number', 'date', 'boolean')" + }, + "custom_metadata_fields_key_format_chk": { + "name": "custom_metadata_fields_key_format_chk", + "value": "\"custom_metadata_fields\".\"key\" ~ '^[a-z0-9][a-z0-9_]{0,99}$'" + }, + "custom_metadata_fields_label_not_blank_chk": { + "name": "custom_metadata_fields_label_not_blank_chk", + "value": "length(btrim(\"custom_metadata_fields\".\"label\")) > 0" + }, + "custom_metadata_fields_display_order_nonnegative_chk": { + "name": "custom_metadata_fields_display_order_nonnegative_chk", + "value": "\"custom_metadata_fields\".\"display_order\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.custom_metadata_library_fields": { + "name": "custom_metadata_library_fields", + "schema": "", + "columns": { + "field_id": { + "name": "field_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_metadata_library_fields_field_idx": { + "name": "custom_metadata_library_fields_field_idx", + "columns": [ + { + "expression": "field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_metadata_library_fields_library_order_idx": { + "name": "custom_metadata_library_fields_library_order_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "field_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_metadata_library_fields_field_id_custom_metadata_fields_id_fk": { + "name": "custom_metadata_library_fields_field_id_custom_metadata_fields_id_fk", + "tableFrom": "custom_metadata_library_fields", + "tableTo": "custom_metadata_fields", + "columnsFrom": ["field_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_metadata_library_fields_library_id_libraries_id_fk": { + "name": "custom_metadata_library_fields_library_id_libraries_id_fk", + "tableFrom": "custom_metadata_library_fields", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "custom_metadata_library_fields_library_id_field_id_pk": { + "name": "custom_metadata_library_fields_library_id_field_id_pk", + "columns": ["library_id", "field_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "custom_metadata_library_fields_display_order_nonnegative_chk": { + "name": "custom_metadata_library_fields_display_order_nonnegative_chk", + "value": "\"custom_metadata_library_fields\".\"display_order\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.custom_icons": { + "name": "custom_icons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(80)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(120)", + "primaryKey": false, + "notNull": true + }, + "original_file_name": { + "name": "original_file_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "stored_file_name": { + "name": "stored_file_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_icons_slug_uidx": { + "name": "custom_icons_slug_uidx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "custom_icons_slug_chk": { + "name": "custom_icons_slug_chk", + "value": "\"custom_icons\".\"slug\" ~ '^[a-z0-9][a-z0-9-]*[a-z0-9]$' or \"custom_icons\".\"slug\" ~ '^[a-z0-9]$'" + }, + "custom_icons_file_size_positive_chk": { + "name": "custom_icons_file_size_positive_chk", + "value": "\"custom_icons\".\"file_size\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.migration_plan_artifacts": { + "name": "migration_plan_artifacts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_snapshot_hash": { + "name": "source_snapshot_hash", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "profile_hash": { + "name": "profile_hash", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "plan_hash": { + "name": "plan_hash", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source_data": { + "name": "source_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "migration_plan_artifacts_plan_hash_uidx": { + "name": "migration_plan_artifacts_plan_hash_uidx", + "columns": [ + { + "expression": "plan_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_plan_artifacts_source_id_idx": { + "name": "migration_plan_artifacts_source_id_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_plan_artifacts_profile_id_idx": { + "name": "migration_plan_artifacts_profile_id_idx", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_plan_artifacts_source_id_migration_sources_id_fk": { + "name": "migration_plan_artifacts_source_id_migration_sources_id_fk", + "tableFrom": "migration_plan_artifacts", + "tableTo": "migration_sources", + "columnsFrom": ["source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_plan_artifacts_profile_id_migration_profiles_id_fk": { + "name": "migration_plan_artifacts_profile_id_migration_profiles_id_fk", + "tableFrom": "migration_plan_artifacts", + "tableTo": "migration_profiles", + "columnsFrom": ["profile_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_plan_artifacts_created_by_user_id_users_id_fk": { + "name": "migration_plan_artifacts_created_by_user_id_users_id_fk", + "tableFrom": "migration_plan_artifacts", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "migration_plan_artifacts_profile_source_fk": { + "name": "migration_plan_artifacts_profile_source_fk", + "tableFrom": "migration_plan_artifacts", + "tableTo": "migration_profiles", + "columnsFrom": ["profile_id", "source_id"], + "columnsTo": ["id", "source_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "migration_plan_artifacts_id_source_profile_unique": { + "name": "migration_plan_artifacts_id_source_profile_unique", + "nullsNotDistinct": false, + "columns": ["id", "source_id", "profile_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.migration_profiles": { + "name": "migration_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "user_mappings": { + "name": "user_mappings", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "path_mappings": { + "name": "path_mappings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "migration_profiles_source_name_version_uidx": { + "name": "migration_profiles_source_name_version_uidx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_profiles_source_id_idx": { + "name": "migration_profiles_source_id_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_profiles_source_id_migration_sources_id_fk": { + "name": "migration_profiles_source_id_migration_sources_id_fk", + "tableFrom": "migration_profiles", + "tableTo": "migration_sources", + "columnsFrom": ["source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_profiles_created_by_user_id_users_id_fk": { + "name": "migration_profiles_created_by_user_id_users_id_fk", + "tableFrom": "migration_profiles", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "migration_profiles_id_source_id_unique": { + "name": "migration_profiles_id_source_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "source_id"] + } + }, + "policies": {}, + "checkConstraints": { + "migration_profiles_version_positive_chk": { + "name": "migration_profiles_version_positive_chk", + "value": "\"migration_profiles\".\"version\" >= 1" + } + }, + "isRLSEnabled": false + }, + "public.migration_run_metrics": { + "name": "migration_run_metrics", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "stage": { + "name": "stage", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "processed": { + "name": "processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "imported": { + "name": "imported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped": { + "name": "skipped", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "unresolved": { + "name": "unresolved", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed": { + "name": "failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "migration_run_metrics_run_stage_entity_uidx": { + "name": "migration_run_metrics_run_stage_entity_uidx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_run_metrics_run_id_idx": { + "name": "migration_run_metrics_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_run_metrics_run_id_migration_runs_id_fk": { + "name": "migration_run_metrics_run_id_migration_runs_id_fk", + "tableFrom": "migration_run_metrics", + "tableTo": "migration_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "migration_run_metrics_processed_nonnegative_chk": { + "name": "migration_run_metrics_processed_nonnegative_chk", + "value": "\"migration_run_metrics\".\"processed\" >= 0" + }, + "migration_run_metrics_imported_nonnegative_chk": { + "name": "migration_run_metrics_imported_nonnegative_chk", + "value": "\"migration_run_metrics\".\"imported\" >= 0" + }, + "migration_run_metrics_skipped_nonnegative_chk": { + "name": "migration_run_metrics_skipped_nonnegative_chk", + "value": "\"migration_run_metrics\".\"skipped\" >= 0" + }, + "migration_run_metrics_unresolved_nonnegative_chk": { + "name": "migration_run_metrics_unresolved_nonnegative_chk", + "value": "\"migration_run_metrics\".\"unresolved\" >= 0" + }, + "migration_run_metrics_failed_nonnegative_chk": { + "name": "migration_run_metrics_failed_nonnegative_chk", + "value": "\"migration_run_metrics\".\"failed\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.migration_runs": { + "name": "migration_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "plan_artifact_id": { + "name": "plan_artifact_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "target_key": { + "name": "target_key", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'bookorbit'" + }, + "state": { + "name": "state", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "current_stage": { + "name": "current_stage", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "triggered_by_user_id": { + "name": "triggered_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "migration_runs_source_target_state_idx": { + "name": "migration_runs_source_target_state_idx", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_runs_state_idx": { + "name": "migration_runs_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_runs_source_id_migration_sources_id_fk": { + "name": "migration_runs_source_id_migration_sources_id_fk", + "tableFrom": "migration_runs", + "tableTo": "migration_sources", + "columnsFrom": ["source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_runs_profile_id_migration_profiles_id_fk": { + "name": "migration_runs_profile_id_migration_profiles_id_fk", + "tableFrom": "migration_runs", + "tableTo": "migration_profiles", + "columnsFrom": ["profile_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "migration_runs_plan_artifact_id_migration_plan_artifacts_id_fk": { + "name": "migration_runs_plan_artifact_id_migration_plan_artifacts_id_fk", + "tableFrom": "migration_runs", + "tableTo": "migration_plan_artifacts", + "columnsFrom": ["plan_artifact_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "migration_runs_triggered_by_user_id_users_id_fk": { + "name": "migration_runs_triggered_by_user_id_users_id_fk", + "tableFrom": "migration_runs", + "tableTo": "users", + "columnsFrom": ["triggered_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "migration_runs_profile_source_fk": { + "name": "migration_runs_profile_source_fk", + "tableFrom": "migration_runs", + "tableTo": "migration_profiles", + "columnsFrom": ["profile_id", "source_id"], + "columnsTo": ["id", "source_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "migration_runs_plan_artifact_source_profile_fk": { + "name": "migration_runs_plan_artifact_source_profile_fk", + "tableFrom": "migration_runs", + "tableTo": "migration_plan_artifacts", + "columnsFrom": ["plan_artifact_id", "source_id", "profile_id"], + "columnsTo": ["id", "source_id", "profile_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "migration_runs_state_chk": { + "name": "migration_runs_state_chk", + "value": "\"state\" IN ('draft', 'preflight_failed', 'dry_run_ready', 'running', 'failed', 'completed')" + }, + "migration_runs_started_before_ended_chk": { + "name": "migration_runs_started_before_ended_chk", + "value": "\"migration_runs\".\"ended_at\" is null or \"migration_runs\".\"started_at\" is null or \"migration_runs\".\"ended_at\" >= \"migration_runs\".\"started_at\"" + } + }, + "isRLSEnabled": false + }, + "public.migration_sources": { + "name": "migration_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "connection_config": { + "name": "connection_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_validated_at": { + "name": "last_validated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "migration_sources_type_name_uidx": { + "name": "migration_sources_type_name_uidx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "migration_sources_created_by_user_id_idx": { + "name": "migration_sources_created_by_user_id_idx", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "migration_sources_created_by_user_id_users_id_fk": { + "name": "migration_sources_created_by_user_id_users_id_fk", + "tableFrom": "migration_sources", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.libraries": { + "name": "libraries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cover_aspect_ratio": { + "name": "cover_aspect_ratio", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'2/3'" + }, + "watch": { + "name": "watch", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auto_scan_cron_expression": { + "name": "auto_scan_cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_precedence": { + "name": "metadata_precedence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"folderStructure\",\"embedded\",\"nfoFile\",\"opfFile\",\"sidecar\"]'::jsonb" + }, + "format_priority": { + "name": "format_priority", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[\"epub\",\"pdf\",\"cbz\",\"cbr\",\"cb7\",\"mobi\",\"azw3\",\"azw\",\"fb2\",\"m4b\",\"mp3\",\"m4a\",\"opus\",\"ogg\",\"flac\"]'::jsonb" + }, + "allowed_formats": { + "name": "allowed_formats", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "organization_mode": { + "name": "organization_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'book_per_folder'" + }, + "exclude_patterns": { + "name": "exclude_patterns", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "reading_threshold": { + "name": "reading_threshold", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0.25 + }, + "mark_as_finished_percent_complete": { + "name": "mark_as_finished_percent_complete", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 98 + }, + "file_write_enabled": { + "name": "file_write_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_write_write_cover": { + "name": "file_write_write_cover", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "file_write_epub_enabled": { + "name": "file_write_epub_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "file_write_epub_max_file_size_mb": { + "name": "file_write_epub_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "file_write_fb2_enabled": { + "name": "file_write_fb2_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_write_fb2_max_file_size_mb": { + "name": "file_write_fb2_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "file_write_pdf_enabled": { + "name": "file_write_pdf_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "file_write_pdf_max_file_size_mb": { + "name": "file_write_pdf_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "file_write_cbx_enabled": { + "name": "file_write_cbx_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_write_cbx_max_file_size_mb": { + "name": "file_write_cbx_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "file_write_kindle_enabled": { + "name": "file_write_kindle_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_write_kindle_max_file_size_mb": { + "name": "file_write_kindle_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "file_write_audio_enabled": { + "name": "file_write_audio_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_write_audio_max_file_size_mb": { + "name": "file_write_audio_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 500 + }, + "file_rename_enabled": { + "name": "file_rename_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_naming_pattern": { + "name": "file_naming_pattern", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "metadata_fetch_preferences": { + "name": "metadata_fetch_preferences", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "book_metadata_fetch_config": { + "name": "book_metadata_fetch_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "book_metadata_fetch_last_run_at": { + "name": "book_metadata_fetch_last_run_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "book_metadata_fetch_last_queued_count": { + "name": "book_metadata_fetch_last_queued_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "scan_mode": { + "name": "scan_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'auto'" + }, + "poll_interval_seconds": { + "name": "poll_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 300 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "libraries_name_lower_uidx": { + "name": "libraries_name_lower_uidx", + "columns": [ + { + "expression": "lower(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "libraries_display_order_nonnegative_chk": { + "name": "libraries_display_order_nonnegative_chk", + "value": "\"libraries\".\"display_order\" >= 0" + }, + "libraries_organization_mode_chk": { + "name": "libraries_organization_mode_chk", + "value": "\"libraries\".\"organization_mode\" in ('book_per_folder', 'book_per_file')" + }, + "libraries_reading_threshold_range_chk": { + "name": "libraries_reading_threshold_range_chk", + "value": "\"libraries\".\"reading_threshold\" >= 0 and \"libraries\".\"reading_threshold\" <= 100" + }, + "libraries_mark_finished_percent_range_chk": { + "name": "libraries_mark_finished_percent_range_chk", + "value": "\"libraries\".\"mark_as_finished_percent_complete\" >= 0 and \"libraries\".\"mark_as_finished_percent_complete\" <= 100" + }, + "libraries_scan_mode_chk": { + "name": "libraries_scan_mode_chk", + "value": "\"libraries\".\"scan_mode\" in ('auto', 'manual')" + }, + "libraries_poll_interval_nonnegative_chk": { + "name": "libraries_poll_interval_nonnegative_chk", + "value": "\"libraries\".\"poll_interval_seconds\" is null or \"libraries\".\"poll_interval_seconds\" >= 0" + }, + "libraries_file_write_epub_max_size_chk": { + "name": "libraries_file_write_epub_max_size_chk", + "value": "\"libraries\".\"file_write_epub_max_file_size_mb\" >= 1" + }, + "libraries_file_write_pdf_max_size_chk": { + "name": "libraries_file_write_pdf_max_size_chk", + "value": "\"libraries\".\"file_write_pdf_max_file_size_mb\" >= 1" + }, + "libraries_file_write_cbx_max_size_chk": { + "name": "libraries_file_write_cbx_max_size_chk", + "value": "\"libraries\".\"file_write_cbx_max_file_size_mb\" >= 1" + }, + "libraries_file_write_kindle_max_size_chk": { + "name": "libraries_file_write_kindle_max_size_chk", + "value": "\"libraries\".\"file_write_kindle_max_file_size_mb\" >= 1" + }, + "libraries_file_write_audio_max_size_chk": { + "name": "libraries_file_write_audio_max_size_chk", + "value": "\"libraries\".\"file_write_audio_max_file_size_mb\" >= 1" + } + }, + "isRLSEnabled": false + }, + "public.library_folders": { + "name": "library_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "library_folders_library_id_idx": { + "name": "library_folders_library_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "library_folders_library_path_uidx": { + "name": "library_folders_library_path_uidx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "library_folders_library_id_libraries_id_fk": { + "name": "library_folders_library_id_libraries_id_fk", + "tableFrom": "library_folders", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "library_folders_id_library_id_unique": { + "name": "library_folders_id_library_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "library_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_files": { + "name": "book_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_folder_id": { + "name": "library_folder_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "absolute_path": { + "name": "absolute_path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "rel_path": { + "name": "rel_path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": false + }, + "ino": { + "name": "ino", + "type": "numeric(20, 0)", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "mtime": { + "name": "mtime", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'content'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_files_absolute_path_uidx": { + "name": "book_files_absolute_path_uidx", + "columns": [ + { + "expression": "absolute_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_book_id_idx": { + "name": "book_files_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_library_folder_id_idx": { + "name": "book_files_library_folder_id_idx", + "columns": [ + { + "expression": "library_folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_file_hash_idx": { + "name": "book_files_file_hash_idx", + "columns": [ + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_ino_idx": { + "name": "book_files_ino_idx", + "columns": [ + { + "expression": "ino", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_format_idx": { + "name": "book_files_format_idx", + "columns": [ + { + "expression": "format", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_library_folder_file_hash_idx": { + "name": "book_files_library_folder_file_hash_idx", + "columns": [ + { + "expression": "library_folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_files_library_folder_ino_idx": { + "name": "book_files_library_folder_ino_idx", + "columns": [ + { + "expression": "library_folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ino", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_files_book_id_books_id_fk": { + "name": "book_files_book_id_books_id_fk", + "tableFrom": "book_files", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_files_library_folder_id_library_folders_id_fk": { + "name": "book_files_library_folder_id_library_folders_id_fk", + "tableFrom": "book_files", + "tableTo": "library_folders", + "columnsFrom": ["library_folder_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_files_book_folder_consistency_fk": { + "name": "book_files_book_folder_consistency_fk", + "tableFrom": "book_files", + "tableTo": "books", + "columnsFrom": ["book_id", "library_folder_id"], + "columnsTo": ["id", "library_folder_id"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_files_role_chk": { + "name": "book_files_role_chk", + "value": "\"book_files\".\"role\" in ('content', 'cover', 'metadata', 'supplement')" + }, + "book_files_size_bytes_nonnegative_chk": { + "name": "book_files_size_bytes_nonnegative_chk", + "value": "\"book_files\".\"size_bytes\" is null or \"book_files\".\"size_bytes\" >= 0" + }, + "book_files_duration_seconds_nonnegative_chk": { + "name": "book_files_duration_seconds_nonnegative_chk", + "value": "\"book_files\".\"duration_seconds\" is null or \"book_files\".\"duration_seconds\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.books": { + "name": "books", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_folder_id": { + "name": "library_folder_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "primary_file_id": { + "name": "primary_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "primary_author_sort_name": { + "name": "primary_author_sort_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "folder_path": { + "name": "folder_path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'present'" + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "books_library_id_folder_path_idx": { + "name": "books_library_id_folder_path_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "books_primary_file_id_idx": { + "name": "books_primary_file_id_idx", + "columns": [ + { + "expression": "primary_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "books_library_status_id_idx": { + "name": "books_library_status_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "books_library_added_at_idx": { + "name": "books_library_added_at_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"added_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "books_library_visible_added_id_idx": { + "name": "books_library_visible_added_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"added_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"books\".\"status\" <> 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "books_library_author_sort_id_idx": { + "name": "books_library_author_sort_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"primary_author_sort_name\" asc nulls last", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"books\".\"status\" <> 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "books_library_author_sort_desc_id_idx": { + "name": "books_library_author_sort_desc_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"primary_author_sort_name\" desc nulls last", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"books\".\"status\" <> 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "books_library_id_libraries_id_fk": { + "name": "books_library_id_libraries_id_fk", + "tableFrom": "books", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "books_library_folder_id_library_folders_id_fk": { + "name": "books_library_folder_id_library_folders_id_fk", + "tableFrom": "books", + "tableTo": "library_folders", + "columnsFrom": ["library_folder_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "books_primary_file_id_book_files_id_fk": { + "name": "books_primary_file_id_book_files_id_fk", + "tableFrom": "books", + "tableTo": "book_files", + "columnsFrom": ["primary_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "books_library_folder_library_fk": { + "name": "books_library_folder_library_fk", + "tableFrom": "books", + "tableTo": "library_folders", + "columnsFrom": ["library_folder_id", "library_id"], + "columnsTo": ["id", "library_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "books_id_library_folder_id_unique": { + "name": "books_id_library_folder_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "library_folder_id"] + } + }, + "policies": {}, + "checkConstraints": { + "books_status_chk": { + "name": "books_status_chk", + "value": "\"books\".\"status\" in ('present', 'missing', 'processing')" + } + }, + "isRLSEnabled": false + }, + "public.book_duplicate_group_members": { + "name": "book_duplicate_group_members", + "schema": "", + "columns": { + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scan_id": { + "name": "scan_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "book_duplicate_group_members_group_idx": { + "name": "book_duplicate_group_members_group_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_duplicate_group_members_group_id_book_duplicate_groups_id_fk": { + "name": "book_duplicate_group_members_group_id_book_duplicate_groups_id_fk", + "tableFrom": "book_duplicate_group_members", + "tableTo": "book_duplicate_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_group_members_scan_id_book_duplicate_scans_id_fk": { + "name": "book_duplicate_group_members_scan_id_book_duplicate_scans_id_fk", + "tableFrom": "book_duplicate_group_members", + "tableTo": "book_duplicate_scans", + "columnsFrom": ["scan_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_group_members_book_id_books_id_fk": { + "name": "book_duplicate_group_members_book_id_books_id_fk", + "tableFrom": "book_duplicate_group_members", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_duplicate_group_members_scan_id_book_id_pk": { + "name": "book_duplicate_group_members_scan_id_book_id_pk", + "columns": ["scan_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_duplicate_groups": { + "name": "book_duplicate_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "scan_id": { + "name": "scan_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "root_book_id": { + "name": "root_book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reasons": { + "name": "reasons", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "max_title_similarity": { + "name": "max_title_similarity", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "member_count": { + "name": "member_count", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "book_duplicate_groups_scan_root_uidx": { + "name": "book_duplicate_groups_scan_root_uidx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "root_book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_duplicate_groups_scan_id_idx": { + "name": "book_duplicate_groups_scan_id_idx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_duplicate_groups_scan_member_count_idx": { + "name": "book_duplicate_groups_scan_member_count_idx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "member_count", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_duplicate_groups_scan_id_book_duplicate_scans_id_fk": { + "name": "book_duplicate_groups_scan_id_book_duplicate_scans_id_fk", + "tableFrom": "book_duplicate_groups", + "tableTo": "book_duplicate_scans", + "columnsFrom": ["scan_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_groups_root_book_id_books_id_fk": { + "name": "book_duplicate_groups_root_book_id_books_id_fk", + "tableFrom": "book_duplicate_groups", + "tableTo": "books", + "columnsFrom": ["root_book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_duplicate_pairs": { + "name": "book_duplicate_pairs", + "schema": "", + "columns": { + "scan_id": { + "name": "scan_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "book_id_a": { + "name": "book_id_a", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id_b": { + "name": "book_id_b", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reasons": { + "name": "reasons", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "title_similarity": { + "name": "title_similarity", + "type": "real", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "book_duplicate_pairs_group_idx": { + "name": "book_duplicate_pairs_group_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_duplicate_pairs_scan_a_idx": { + "name": "book_duplicate_pairs_scan_a_idx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id_a", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_duplicate_pairs_scan_b_idx": { + "name": "book_duplicate_pairs_scan_b_idx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id_b", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_duplicate_pairs_scan_id_book_duplicate_scans_id_fk": { + "name": "book_duplicate_pairs_scan_id_book_duplicate_scans_id_fk", + "tableFrom": "book_duplicate_pairs", + "tableTo": "book_duplicate_scans", + "columnsFrom": ["scan_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_pairs_group_id_book_duplicate_groups_id_fk": { + "name": "book_duplicate_pairs_group_id_book_duplicate_groups_id_fk", + "tableFrom": "book_duplicate_pairs", + "tableTo": "book_duplicate_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_pairs_book_id_a_books_id_fk": { + "name": "book_duplicate_pairs_book_id_a_books_id_fk", + "tableFrom": "book_duplicate_pairs", + "tableTo": "books", + "columnsFrom": ["book_id_a"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_pairs_book_id_b_books_id_fk": { + "name": "book_duplicate_pairs_book_id_b_books_id_fk", + "tableFrom": "book_duplicate_pairs", + "tableTo": "books", + "columnsFrom": ["book_id_b"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_duplicate_pairs_scan_id_book_id_a_book_id_b_pk": { + "name": "book_duplicate_pairs_scan_id_book_id_a_book_id_b_pk", + "columns": ["scan_id", "book_id_a", "book_id_b"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_duplicate_pairs_order_chk": { + "name": "book_duplicate_pairs_order_chk", + "value": "\"book_duplicate_pairs\".\"book_id_a\" < \"book_duplicate_pairs\".\"book_id_b\"" + } + }, + "isRLSEnabled": false + }, + "public.book_duplicate_scan_keys": { + "name": "book_duplicate_scan_keys", + "schema": "", + "columns": { + "scan_id": { + "name": "scan_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "book_duplicate_scan_keys_lookup_idx": { + "name": "book_duplicate_scan_keys_lookup_idx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "value", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_duplicate_scan_keys_scan_id_book_duplicate_scans_id_fk": { + "name": "book_duplicate_scan_keys_scan_id_book_duplicate_scans_id_fk", + "tableFrom": "book_duplicate_scan_keys", + "tableTo": "book_duplicate_scans", + "columnsFrom": ["scan_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_scan_keys_book_id_books_id_fk": { + "name": "book_duplicate_scan_keys_book_id_books_id_fk", + "tableFrom": "book_duplicate_scan_keys", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_duplicate_scan_keys_scan_id_book_id_kind_value_pk": { + "name": "book_duplicate_scan_keys_scan_id_book_id_kind_value_pk", + "columns": ["scan_id", "book_id", "kind", "value"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_duplicate_scan_keys_kind_chk": { + "name": "book_duplicate_scan_keys_kind_chk", + "value": "\"book_duplicate_scan_keys\".\"kind\" in ('file_hash', 'isbn', 'exact_metadata')" + } + }, + "isRLSEnabled": false + }, + "public.book_duplicate_scans": { + "name": "book_duplicate_scans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_ids": { + "name": "library_ids", + "type": "integer[]", + "primaryKey": false, + "notNull": true + }, + "requested_library_id": { + "name": "requested_library_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "similarity_percent": { + "name": "similarity_percent", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "processed_books": { + "name": "processed_books", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_books": { + "name": "total_books", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_groups": { + "name": "total_groups", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "book_duplicate_scans_user_created_idx": { + "name": "book_duplicate_scans_user_created_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_duplicate_scans_status_idx": { + "name": "book_duplicate_scans_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_duplicate_scans_user_id_users_id_fk": { + "name": "book_duplicate_scans_user_id_users_id_fk", + "tableFrom": "book_duplicate_scans", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_duplicate_scans_similarity_chk": { + "name": "book_duplicate_scans_similarity_chk", + "value": "\"book_duplicate_scans\".\"similarity_percent\" between 70 and 100" + }, + "book_duplicate_scans_status_chk": { + "name": "book_duplicate_scans_status_chk", + "value": "\"book_duplicate_scans\".\"status\" in ('queued', 'running', 'completed', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.book_move_jobs": { + "name": "book_move_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "started_by": { + "name": "started_by", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_library_id": { + "name": "target_library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "target_folder_id": { + "name": "target_folder_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_library_ids": { + "name": "source_library_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "total_books": { + "name": "total_books", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "succeeded": { + "name": "succeeded", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "merged": { + "name": "merged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed": { + "name": "failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped": { + "name": "skipped", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "book_move_jobs_status_idx": { + "name": "book_move_jobs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_move_jobs_started_at_idx": { + "name": "book_move_jobs_started_at_idx", + "columns": [ + { + "expression": "\"started_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_move_jobs_started_by_users_id_fk": { + "name": "book_move_jobs_started_by_users_id_fk", + "tableFrom": "book_move_jobs", + "tableTo": "users", + "columnsFrom": ["started_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_move_jobs_target_library_id_libraries_id_fk": { + "name": "book_move_jobs_target_library_id_libraries_id_fk", + "tableFrom": "book_move_jobs", + "tableTo": "libraries", + "columnsFrom": ["target_library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_move_jobs_target_folder_id_library_folders_id_fk": { + "name": "book_move_jobs_target_folder_id_library_folders_id_fk", + "tableFrom": "book_move_jobs", + "tableTo": "library_folders", + "columnsFrom": ["target_folder_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_move_jobs_status_chk": { + "name": "book_move_jobs_status_chk", + "value": "\"book_move_jobs\".\"status\" in ('running', 'completed', 'failed', 'interrupted')" + }, + "book_move_jobs_counts_nonnegative_chk": { + "name": "book_move_jobs_counts_nonnegative_chk", + "value": "\"book_move_jobs\".\"total_books\" >= 0 and \"book_move_jobs\".\"succeeded\" >= 0 and \"book_move_jobs\".\"merged\" >= 0 and \"book_move_jobs\".\"failed\" >= 0 and \"book_move_jobs\".\"skipped\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.collection_books": { + "name": "collection_books", + "schema": "", + "columns": { + "collection_id": { + "name": "collection_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "position": { + "name": "position", + "type": "bigserial", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "collection_books_book_id_idx": { + "name": "collection_books_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "collection_books_collection_position_idx": { + "name": "collection_books_collection_position_idx", + "columns": [ + { + "expression": "collection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "collection_books_collection_id_collections_id_fk": { + "name": "collection_books_collection_id_collections_id_fk", + "tableFrom": "collection_books", + "tableTo": "collections", + "columnsFrom": ["collection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collection_books_book_id_books_id_fk": { + "name": "collection_books_book_id_books_id_fk", + "tableFrom": "collection_books", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "collection_books_collection_id_book_id_pk": { + "name": "collection_books_collection_id_book_id_pk", + "columns": ["collection_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_to_kobo": { + "name": "sync_to_kobo", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "collections_user_name_uidx": { + "name": "collections_user_name_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "collections_user_display_name_idx": { + "name": "collections_user_display_name_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "collections_user_id_users_id_fk": { + "name": "collections_user_id_users_id_fk", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smart_scope_kobo_subscriptions": { + "name": "smart_scope_kobo_subscriptions", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "smart_scope_id": { + "name": "smart_scope_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "smart_scope_kobo_subscriptions_smart_scope_id_idx": { + "name": "smart_scope_kobo_subscriptions_smart_scope_id_idx", + "columns": [ + { + "expression": "smart_scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "smart_scope_kobo_subscriptions_user_id_users_id_fk": { + "name": "smart_scope_kobo_subscriptions_user_id_users_id_fk", + "tableFrom": "smart_scope_kobo_subscriptions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "smart_scope_kobo_subscriptions_smart_scope_id_smart_scopes_id_fk": { + "name": "smart_scope_kobo_subscriptions_smart_scope_id_smart_scopes_id_fk", + "tableFrom": "smart_scope_kobo_subscriptions", + "tableTo": "smart_scopes", + "columnsFrom": ["smart_scope_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "smart_scope_kobo_subscriptions_user_id_smart_scope_id_pk": { + "name": "smart_scope_kobo_subscriptions_user_id_smart_scope_id_pk", + "columns": ["user_id", "smart_scope_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.smart_scopes": { + "name": "smart_scopes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "filter": { + "name": "filter", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "default_sort": { + "name": "default_sort", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sync_to_kobo": { + "name": "sync_to_kobo", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "smart_scopes_user_id_users_id_fk": { + "name": "smart_scopes_user_id_users_id_fk", + "tableFrom": "smart_scopes", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "smart_scopes_user_id_name_unique": { + "name": "smart_scopes_user_id_name_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_series": { + "name": "book_series", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "normalized_name": { + "name": "normalized_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "expected_book_count": { + "name": "expected_book_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expected_book_count_source": { + "name": "expected_book_count_source", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "expected_book_count_updated_at": { + "name": "expected_book_count_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_series_normalized_name_uidx": { + "name": "book_series_normalized_name_uidx", + "columns": [ + { + "expression": "normalized_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_series_name_trgm_idx": { + "name": "book_series_name_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "book_series_name_unaccent_trgm_idx": { + "name": "book_series_name_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"name\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "book_series_name_lower_idx": { + "name": "book_series_name_lower_idx", + "columns": [ + { + "expression": "lower(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_series_expected_book_count_range_chk": { + "name": "book_series_expected_book_count_range_chk", + "value": "\"book_series\".\"expected_book_count\" IS NULL OR (\"book_series\".\"expected_book_count\" >= 1 AND \"book_series\".\"expected_book_count\" <= 10000)" + } + }, + "isRLSEnabled": false + }, + "public.book_series_memberships": { + "name": "book_series_memberships", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "series_id": { + "name": "series_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "series_index": { + "name": "series_index", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_series_memberships_book_display_uidx": { + "name": "book_series_memberships_book_display_uidx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_series_memberships_series_index_book_idx": { + "name": "book_series_memberships_series_index_book_idx", + "columns": [ + { + "expression": "series_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "series_index", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_series_memberships_book_display_idx": { + "name": "book_series_memberships_book_display_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_series_memberships_book_id_books_id_fk": { + "name": "book_series_memberships_book_id_books_id_fk", + "tableFrom": "book_series_memberships", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_series_memberships_series_id_book_series_id_fk": { + "name": "book_series_memberships_series_id_book_series_id_fk", + "tableFrom": "book_series_memberships", + "tableTo": "book_series", + "columnsFrom": ["series_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_series_memberships_book_id_series_id_pk": { + "name": "book_series_memberships_book_id_series_id_pk", + "columns": ["book_id", "series_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_series_memberships_display_order_nonnegative_chk": { + "name": "book_series_memberships_display_order_nonnegative_chk", + "value": "\"book_series_memberships\".\"display_order\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.author_enrichment_queue": { + "name": "author_enrichment_queue", + "schema": "", + "columns": { + "author_id": { + "name": "author_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "reason": { + "name": "reason", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_http_status": { + "name": "last_http_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "author_enrichment_queue_status_next_attempt_idx": { + "name": "author_enrichment_queue_status_next_attempt_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "author_enrichment_queue_next_attempt_idx": { + "name": "author_enrichment_queue_next_attempt_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "author_enrichment_queue_single_processing_idx": { + "name": "author_enrichment_queue_single_processing_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"author_enrichment_queue\".\"status\" = 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "author_enrichment_queue_author_id_authors_id_fk": { + "name": "author_enrichment_queue_author_id_authors_id_fk", + "tableFrom": "author_enrichment_queue", + "tableTo": "authors", + "columnsFrom": ["author_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "author_enrichment_queue_status_chk": { + "name": "author_enrichment_queue_status_chk", + "value": "\"author_enrichment_queue\".\"status\" in ('queued', 'processing', 'rate_limited', 'failed', 'done')" + }, + "author_enrichment_queue_reason_chk": { + "name": "author_enrichment_queue_reason_chk", + "value": "\"author_enrichment_queue\".\"reason\" in ('unknown', 'metadata_replace', 'manual_backfill', 'manual_backfill_all', 'author_rename', 'author_merge_target')" + }, + "author_enrichment_queue_attempt_count_nonnegative_chk": { + "name": "author_enrichment_queue_attempt_count_nonnegative_chk", + "value": "\"author_enrichment_queue\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.authors": { + "name": "authors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "sort_name": { + "name": "sort_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_photo": { + "name": "has_photo", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "birth_date": { + "name": "birth_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "birth_year": { + "name": "birth_year", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "death_date": { + "name": "death_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "death_year": { + "name": "death_year", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "website": { + "name": "website", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": false + }, + "genres": { + "name": "genres", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "influences": { + "name": "influences", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "metadata_provider": { + "name": "metadata_provider", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "metadata_provider_id": { + "name": "metadata_provider_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "last_enriched_at": { + "name": "last_enriched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "authors_name_trgm_idx": { + "name": "authors_name_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "authors_name_unaccent_trgm_idx": { + "name": "authors_name_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"name\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "authors_name_unique": { + "name": "authors_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_authors": { + "name": "book_authors", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "book_authors_author_id_idx": { + "name": "book_authors_author_id_idx", + "columns": [ + { + "expression": "author_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_authors_book_display_idx": { + "name": "book_authors_book_display_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_authors_book_display_author_idx": { + "name": "book_authors_book_display_author_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "author_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_authors_book_id_books_id_fk": { + "name": "book_authors_book_id_books_id_fk", + "tableFrom": "book_authors", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_authors_author_id_authors_id_fk": { + "name": "book_authors_author_id_authors_id_fk", + "tableFrom": "book_authors", + "tableTo": "authors", + "columnsFrom": ["author_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_authors_book_id_author_id_pk": { + "name": "book_authors_book_id_author_id_pk", + "columns": ["book_id", "author_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_community_ratings": { + "name": "book_community_ratings", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "rating": { + "name": "rating", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "rating_count": { + "name": "rating_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_community_ratings_provider_idx": { + "name": "book_community_ratings_provider_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_community_ratings_rating_book_idx": { + "name": "book_community_ratings_rating_book_idx", + "columns": [ + { + "expression": "rating", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_community_ratings_provider_rating_book_idx": { + "name": "book_community_ratings_provider_rating_book_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "rating", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_community_ratings_book_id_books_id_fk": { + "name": "book_community_ratings_book_id_books_id_fk", + "tableFrom": "book_community_ratings", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_community_ratings_book_id_provider_pk": { + "name": "book_community_ratings_book_id_provider_pk", + "columns": ["book_id", "provider"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_community_ratings_rating_range_chk": { + "name": "book_community_ratings_rating_range_chk", + "value": "\"book_community_ratings\".\"rating\" >= 0 and \"book_community_ratings\".\"rating\" <= 5" + }, + "book_community_ratings_count_nonnegative_chk": { + "name": "book_community_ratings_count_nonnegative_chk", + "value": "\"book_community_ratings\".\"rating_count\" is null or \"book_community_ratings\".\"rating_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.book_genres": { + "name": "book_genres", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "genre_id": { + "name": "genre_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "book_genres_genre_id_idx": { + "name": "book_genres_genre_id_idx", + "columns": [ + { + "expression": "genre_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_genres_book_id_books_id_fk": { + "name": "book_genres_book_id_books_id_fk", + "tableFrom": "book_genres", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_genres_genre_id_genres_id_fk": { + "name": "book_genres_genre_id_genres_id_fk", + "tableFrom": "book_genres", + "tableTo": "genres", + "columnsFrom": ["genre_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_genres_book_id_genre_id_pk": { + "name": "book_genres_book_id_genre_id_pk", + "columns": ["book_id", "genre_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_metadata": { + "name": "book_metadata", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "subtitle": { + "name": "subtitle", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "isbn10": { + "name": "isbn10", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "isbn13": { + "name": "isbn13", + "type": "varchar(13)", + "primaryKey": false, + "notNull": false + }, + "publisher": { + "name": "publisher", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "published_date": { + "name": "published_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "published_year": { + "name": "published_year", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "page_count": { + "name": "page_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "series_id": { + "name": "series_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "series_name": { + "name": "series_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "series_index": { + "name": "series_index", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "rating": { + "name": "rating", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cover_source": { + "name": "cover_source", + "type": "varchar(9)", + "primaryKey": false, + "notNull": false + }, + "google_books_id": { + "name": "google_books_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "goodreads_id": { + "name": "goodreads_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "amazon_id": { + "name": "amazon_id", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "hardcover_id": { + "name": "hardcover_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "hardcover_edition_id": { + "name": "hardcover_edition_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "open_library_id": { + "name": "open_library_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "itunes_id": { + "name": "itunes_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "kobo_id": { + "name": "kobo_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "metadata_score": { + "name": "metadata_score", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_metadata_fetch_at": { + "name": "last_metadata_fetch_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "embedding": { + "name": "embedding", + "type": "vector(256)", + "primaryKey": false, + "notNull": false + }, + "last_written_at": { + "name": "last_written_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "abridged": { + "name": "abridged", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "audible_id": { + "name": "audible_id", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "librofm_id": { + "name": "librofm_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "comicvine_id": { + "name": "comicvine_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "ranobedb_id": { + "name": "ranobedb_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "lubimyczytac_id": { + "name": "lubimyczytac_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aladin_id": { + "name": "aladin_id", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "chapters": { + "name": "chapters", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "locked_fields": { + "name": "locked_fields", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bm_title_trgm_idx": { + "name": "bm_title_trgm_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_title_unaccent_trgm_idx": { + "name": "bm_title_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"title\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_title_lower_idx": { + "name": "bm_title_lower_idx", + "columns": [ + { + "expression": "lower(\"title\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_title_book_id_idx": { + "name": "bm_title_book_id_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_series_trgm_idx": { + "name": "bm_series_trgm_idx", + "columns": [ + { + "expression": "series_name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_series_unaccent_trgm_idx": { + "name": "bm_series_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"series_name\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_series_id_idx": { + "name": "bm_series_id_idx", + "columns": [ + { + "expression": "series_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_series_id_index_book_id_idx": { + "name": "bm_series_id_index_book_id_idx", + "columns": [ + { + "expression": "series_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "series_index", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_series_name_lower_btrim_idx": { + "name": "bm_series_name_lower_btrim_idx", + "columns": [ + { + "expression": "lower(btrim(\"series_name\"))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_publisher_trgm_idx": { + "name": "bm_publisher_trgm_idx", + "columns": [ + { + "expression": "publisher", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_publisher_unaccent_trgm_idx": { + "name": "bm_publisher_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"publisher\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_language_idx": { + "name": "bm_language_idx", + "columns": [ + { + "expression": "language", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_language_trgm_idx": { + "name": "bm_language_trgm_idx", + "columns": [ + { + "expression": "language", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_language_unaccent_trgm_idx": { + "name": "bm_language_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"language\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bm_published_date_idx": { + "name": "bm_published_date_idx", + "columns": [ + { + "expression": "published_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_published_year_idx": { + "name": "bm_published_year_idx", + "columns": [ + { + "expression": "published_year", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_published_date_sort_idx": { + "name": "bm_published_date_sort_idx", + "columns": [ + { + "expression": "coalesce(\"published_date\", make_date(\"published_year\", 1, 1))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_series_name_index_idx": { + "name": "bm_series_name_index_idx", + "columns": [ + { + "expression": "series_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "series_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_isbn10_idx": { + "name": "bm_isbn10_idx", + "columns": [ + { + "expression": "isbn10", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_isbn13_idx": { + "name": "bm_isbn13_idx", + "columns": [ + { + "expression": "isbn13", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_embedding_hnsw_cosine_idx": { + "name": "bm_embedding_hnsw_cosine_idx", + "columns": [ + { + "expression": "\"embedding\" vector_cosine_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": {} + } + }, + "foreignKeys": { + "book_metadata_book_id_books_id_fk": { + "name": "book_metadata_book_id_books_id_fk", + "tableFrom": "book_metadata", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_metadata_series_id_book_series_id_fk": { + "name": "book_metadata_series_id_book_series_id_fk", + "tableFrom": "book_metadata", + "tableTo": "book_series", + "columnsFrom": ["series_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_metadata_rating_range_chk": { + "name": "book_metadata_rating_range_chk", + "value": "\"book_metadata\".\"rating\" is null or (\"book_metadata\".\"rating\" >= 1 and \"book_metadata\".\"rating\" <= 10)" + }, + "book_metadata_published_date_range_chk": { + "name": "book_metadata_published_date_range_chk", + "value": "\"book_metadata\".\"published_date\" is null or (extract(year from \"book_metadata\".\"published_date\") >= 1000 and extract(year from \"book_metadata\".\"published_date\") <= 2200)" + }, + "book_metadata_published_year_range_chk": { + "name": "book_metadata_published_year_range_chk", + "value": "\"book_metadata\".\"published_year\" is null or (\"book_metadata\".\"published_year\" >= 1000 and \"book_metadata\".\"published_year\" <= 2200)" + }, + "book_metadata_page_count_nonnegative_chk": { + "name": "book_metadata_page_count_nonnegative_chk", + "value": "\"book_metadata\".\"page_count\" is null or \"book_metadata\".\"page_count\" >= 0" + }, + "book_metadata_duration_seconds_nonnegative_chk": { + "name": "book_metadata_duration_seconds_nonnegative_chk", + "value": "\"book_metadata\".\"duration_seconds\" is null or \"book_metadata\".\"duration_seconds\" >= 0" + }, + "book_metadata_cover_source_chk": { + "name": "book_metadata_cover_source_chk", + "value": "\"book_metadata\".\"cover_source\" is null or \"book_metadata\".\"cover_source\" in ('extracted', 'custom')" + } + }, + "isRLSEnabled": false + }, + "public.book_metadata_fetch_queue": { + "name": "book_metadata_fetch_queue", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "reason": { + "name": "reason", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'manual_trigger'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_http_status": { + "name": "last_http_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bmfq_status_idx": { + "name": "bmfq_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bmfq_created_at_idx": { + "name": "bmfq_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bmfq_status_created_book_idx": { + "name": "bmfq_status_created_book_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_metadata_fetch_queue_book_id_books_id_fk": { + "name": "book_metadata_fetch_queue_book_id_books_id_fk", + "tableFrom": "book_metadata_fetch_queue", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_metadata_fetch_queue_status_chk": { + "name": "book_metadata_fetch_queue_status_chk", + "value": "\"book_metadata_fetch_queue\".\"status\" in ('queued', 'processing', 'failed')" + }, + "book_metadata_fetch_queue_reason_chk": { + "name": "book_metadata_fetch_queue_reason_chk", + "value": "\"book_metadata_fetch_queue\".\"reason\" in ('event_import', 'manual_trigger', 'manual_retry')" + }, + "book_metadata_fetch_queue_attempt_count_nonnegative_chk": { + "name": "book_metadata_fetch_queue_attempt_count_nonnegative_chk", + "value": "\"book_metadata_fetch_queue\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.book_tags": { + "name": "book_tags", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "book_tags_tag_id_idx": { + "name": "book_tags_tag_id_idx", + "columns": [ + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_tags_book_id_books_id_fk": { + "name": "book_tags_book_id_books_id_fk", + "tableFrom": "book_tags", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_tags_tag_id_tags_id_fk": { + "name": "book_tags_tag_id_tags_id_fk", + "tableFrom": "book_tags", + "tableTo": "tags", + "columnsFrom": ["tag_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_tags_book_id_tag_id_pk": { + "name": "book_tags_book_id_tag_id_pk", + "columns": ["book_id", "tag_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.genres": { + "name": "genres", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "genres_name_trgm_idx": { + "name": "genres_name_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "genres_name_unaccent_trgm_idx": { + "name": "genres_name_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"name\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "genres_name_unique": { + "name": "genres_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tags": { + "name": "tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "tags_name_trgm_idx": { + "name": "tags_name_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "tags_name_unaccent_trgm_idx": { + "name": "tags_name_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"name\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tags_name_unique": { + "name": "tags_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_narrators": { + "name": "book_narrators", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "narrator_id": { + "name": "narrator_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "book_narrators_narrator_id_idx": { + "name": "book_narrators_narrator_id_idx", + "columns": [ + { + "expression": "narrator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_narrators_book_id_books_id_fk": { + "name": "book_narrators_book_id_books_id_fk", + "tableFrom": "book_narrators", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_narrators_narrator_id_narrators_id_fk": { + "name": "book_narrators_narrator_id_narrators_id_fk", + "tableFrom": "book_narrators", + "tableTo": "narrators", + "columnsFrom": ["narrator_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_narrators_book_id_narrator_id_pk": { + "name": "book_narrators_book_id_narrator_id_pk", + "columns": ["book_id", "narrator_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.narrators": { + "name": "narrators", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "sort_name": { + "name": "sort_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "narrators_name_trgm_idx": { + "name": "narrators_name_trgm_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "narrators_name_unaccent_trgm_idx": { + "name": "narrators_name_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"name\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "narrators_name_unique": { + "name": "narrators_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.library_dir_scan_state": { + "name": "library_dir_scan_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "library_folder_id": { + "name": "library_folder_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "dir_path": { + "name": "dir_path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "last_seen_mtime_ms": { + "name": "last_seen_mtime_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "library_dir_scan_state_folder_dir_uidx": { + "name": "library_dir_scan_state_folder_dir_uidx", + "columns": [ + { + "expression": "library_folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dir_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "library_dir_scan_state_folder_idx": { + "name": "library_dir_scan_state_folder_idx", + "columns": [ + { + "expression": "library_folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "library_dir_scan_state_library_folder_id_library_folders_id_fk": { + "name": "library_dir_scan_state_library_folder_id_library_folders_id_fk", + "tableFrom": "library_dir_scan_state", + "tableTo": "library_folders", + "columnsFrom": ["library_folder_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scan_jobs": { + "name": "scan_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "triggered_by": { + "name": "triggered_by", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "added_count": { + "name": "added_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_count": { + "name": "updated_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "missing_count": { + "name": "missing_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "scan_jobs_library_status_idx": { + "name": "scan_jobs_library_status_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scan_jobs_library_id_libraries_id_fk": { + "name": "scan_jobs_library_id_libraries_id_fk", + "tableFrom": "scan_jobs", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "scan_jobs_status_chk": { + "name": "scan_jobs_status_chk", + "value": "\"scan_jobs\".\"status\" in ('running', 'completed', 'failed')" + }, + "scan_jobs_triggered_by_chk": { + "name": "scan_jobs_triggered_by_chk", + "value": "\"scan_jobs\".\"triggered_by\" in ('manual', 'watcher', 'schedule')" + }, + "scan_jobs_added_count_nonnegative_chk": { + "name": "scan_jobs_added_count_nonnegative_chk", + "value": "\"scan_jobs\".\"added_count\" >= 0" + }, + "scan_jobs_updated_count_nonnegative_chk": { + "name": "scan_jobs_updated_count_nonnegative_chk", + "value": "\"scan_jobs\".\"updated_count\" >= 0" + }, + "scan_jobs_missing_count_nonnegative_chk": { + "name": "scan_jobs_missing_count_nonnegative_chk", + "value": "\"scan_jobs\".\"missing_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.annotation_positions": { + "name": "annotation_positions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "annotation_id": { + "name": "annotation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "pos0": { + "name": "pos0", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pos1": { + "name": "pos1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'exact'" + }, + "converter_version": { + "name": "converter_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "extras": { + "name": "extras", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "annotation_positions_annotation_format_uidx": { + "name": "annotation_positions_annotation_format_uidx", + "columns": [ + { + "expression": "annotation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "format", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotation_positions_user_idx": { + "name": "annotation_positions_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotation_positions_book_file_id_idx": { + "name": "annotation_positions_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotation_positions_format_status_idx": { + "name": "annotation_positions_format_status_idx", + "columns": [ + { + "expression": "format", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "annotation_positions_annotation_id_annotations_id_fk": { + "name": "annotation_positions_annotation_id_annotations_id_fk", + "tableFrom": "annotation_positions", + "tableTo": "annotations", + "columnsFrom": ["annotation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "annotation_positions_user_id_users_id_fk": { + "name": "annotation_positions_user_id_users_id_fk", + "tableFrom": "annotation_positions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "annotation_positions_book_file_id_book_files_id_fk": { + "name": "annotation_positions_book_file_id_book_files_id_fk", + "tableFrom": "annotation_positions", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "annotation_positions_format_chk": { + "name": "annotation_positions_format_chk", + "value": "\"annotation_positions\".\"format\" in ('cfi', 'xpointer', 'pdf', 'kobo_span')" + }, + "annotation_positions_status_chk": { + "name": "annotation_positions_status_chk", + "value": "\"annotation_positions\".\"status\" in ('exact', 'repaired', 'failed', 'pending')" + }, + "annotation_positions_pos0_chk": { + "name": "annotation_positions_pos0_chk", + "value": "\"annotation_positions\".\"status\" in ('failed', 'pending') or \"annotation_positions\".\"pos0\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.annotation_sync_state": { + "name": "annotation_sync_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "annotation_id": { + "name": "annotation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "external_key": { + "name": "external_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "external_created_at": { + "name": "external_created_at", + "type": "varchar(19)", + "primaryKey": false, + "notNull": false + }, + "last_applied_version": { + "name": "last_applied_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "delete_acked_at": { + "name": "delete_acked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "first_synced_at": { + "name": "first_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "annotation_sync_state_annotation_source_device_uidx": { + "name": "annotation_sync_state_annotation_source_device_uidx", + "columns": [ + { + "expression": "annotation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotation_sync_state_user_source_device_key_idx": { + "name": "annotation_sync_state_user_source_device_key_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotation_sync_state_user_key_idx": { + "name": "annotation_sync_state_user_key_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotation_sync_state_annotation_id_idx": { + "name": "annotation_sync_state_annotation_id_idx", + "columns": [ + { + "expression": "annotation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "annotation_sync_state_annotation_id_annotations_id_fk": { + "name": "annotation_sync_state_annotation_id_annotations_id_fk", + "tableFrom": "annotation_sync_state", + "tableTo": "annotations", + "columnsFrom": ["annotation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "annotation_sync_state_user_id_users_id_fk": { + "name": "annotation_sync_state_user_id_users_id_fk", + "tableFrom": "annotation_sync_state", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "annotation_sync_state_source_chk": { + "name": "annotation_sync_state_source_chk", + "value": "\"annotation_sync_state\".\"source\" in ('koreader', 'kobo')" + } + }, + "isRLSEnabled": false + }, + "public.annotations": { + "name": "annotations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'yellow'" + }, + "style": { + "name": "style", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'highlight'" + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chapter_title": { + "name": "chapter_title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'web'" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "device_created_at": { + "name": "device_created_at", + "type": "varchar(19)", + "primaryKey": false, + "notNull": false + }, + "device_updated_at": { + "name": "device_updated_at", + "type": "varchar(19)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "annotations_user_id_idx": { + "name": "annotations_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotations_user_id_id_idx": { + "name": "annotations_user_id_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotations_user_book_idx": { + "name": "annotations_user_book_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotations_book_id_idx": { + "name": "annotations_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotations_user_book_active_idx": { + "name": "annotations_user_book_active_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"annotations\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "annotations_user_id_users_id_fk": { + "name": "annotations_user_id_users_id_fk", + "tableFrom": "annotations", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "annotations_book_id_books_id_fk": { + "name": "annotations_book_id_books_id_fk", + "tableFrom": "annotations", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "annotations_style_chk": { + "name": "annotations_style_chk", + "value": "\"annotations\".\"style\" in ('highlight', 'underline', 'strikethrough', 'squiggly', 'invert')" + }, + "annotations_origin_chk": { + "name": "annotations_origin_chk", + "value": "\"annotations\".\"origin\" in ('web', 'koreader', 'kobo')" + } + }, + "isRLSEnabled": false + }, + "public.audiobook_progress": { + "name": "audiobook_progress", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "percentage": { + "name": "percentage", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "current_file_id": { + "name": "current_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position_seconds": { + "name": "position_seconds", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "abp_user_id_idx": { + "name": "abp_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audiobook_progress_book_id_idx": { + "name": "audiobook_progress_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audiobook_progress_current_file_id_idx": { + "name": "audiobook_progress_current_file_id_idx", + "columns": [ + { + "expression": "current_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audiobook_progress_user_id_users_id_fk": { + "name": "audiobook_progress_user_id_users_id_fk", + "tableFrom": "audiobook_progress", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "audiobook_progress_book_id_books_id_fk": { + "name": "audiobook_progress_book_id_books_id_fk", + "tableFrom": "audiobook_progress", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "audiobook_progress_current_file_id_book_files_id_fk": { + "name": "audiobook_progress_current_file_id_book_files_id_fk", + "tableFrom": "audiobook_progress", + "tableTo": "book_files", + "columnsFrom": ["current_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "audiobook_progress_user_id_book_id_pk": { + "name": "audiobook_progress_user_id_book_id_pk", + "columns": ["user_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "audiobook_progress_percentage_range_chk": { + "name": "audiobook_progress_percentage_range_chk", + "value": "\"audiobook_progress\".\"percentage\" >= 0 and \"audiobook_progress\".\"percentage\" <= 100" + }, + "audiobook_progress_position_seconds_nonnegative_chk": { + "name": "audiobook_progress_position_seconds_nonnegative_chk", + "value": "\"audiobook_progress\".\"position_seconds\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.bookmarks": { + "name": "bookmarks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "cfi": { + "name": "cfi", + "type": "varchar(2000)", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "position_seconds": { + "name": "position_seconds", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'web'" + }, + "device_pos": { + "name": "device_pos", + "type": "varchar(4000)", + "primaryKey": false, + "notNull": false + }, + "pageno": { + "name": "pageno", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "bookmarks_user_book_idx": { + "name": "bookmarks_user_book_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bookmarks_book_id_idx": { + "name": "bookmarks_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bookmarks_deleted_at_idx": { + "name": "bookmarks_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"bookmarks\".\"deleted_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "bookmarks_user_book_cfi_uidx": { + "name": "bookmarks_user_book_cfi_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cfi", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"bookmarks\".\"cfi\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "bookmarks_user_book_pos_uidx": { + "name": "bookmarks_user_book_pos_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position_seconds", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"bookmarks\".\"position_seconds\" is not null and \"bookmarks\".\"cfi\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bookmarks_user_id_users_id_fk": { + "name": "bookmarks_user_id_users_id_fk", + "tableFrom": "bookmarks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bookmarks_book_id_books_id_fk": { + "name": "bookmarks_book_id_books_id_fk", + "tableFrom": "bookmarks", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "bookmarks_origin_chk": { + "name": "bookmarks_origin_chk", + "value": "\"bookmarks\".\"origin\" in ('web', 'koreader')" + } + }, + "isRLSEnabled": false + }, + "public.reader_default_preferences": { + "name": "reader_default_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format_group": { + "name": "format_group", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "rdp_user_format_idx": { + "name": "rdp_user_format_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "format_group", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reader_default_preferences_user_id_users_id_fk": { + "name": "reader_default_preferences_user_id_users_id_fk", + "tableFrom": "reader_default_preferences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reader_default_preferences_format_group_chk": { + "name": "reader_default_preferences_format_group_chk", + "value": "\"reader_default_preferences\".\"format_group\" in ('epub', 'pdf', 'cbx', 'audio')" + } + }, + "isRLSEnabled": false + }, + "public.reader_preferences": { + "name": "reader_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "rp_user_file_idx": { + "name": "rp_user_file_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reader_preferences_book_file_id_idx": { + "name": "reader_preferences_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reader_preferences_user_id_users_id_fk": { + "name": "reader_preferences_user_id_users_id_fk", + "tableFrom": "reader_preferences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reader_preferences_book_file_id_book_files_id_fk": { + "name": "reader_preferences_book_file_id_book_files_id_fk", + "tableFrom": "reader_preferences", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reading_attempts": { + "name": "reading_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "started_on": { + "name": "started_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "ended_on": { + "name": "ended_on", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "external_provider": { + "name": "external_provider", + "type": "varchar(40)", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reading_attempts_user_book_idx": { + "name": "reading_attempts_user_book_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reading_attempts_book_id_idx": { + "name": "reading_attempts_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reading_attempts_user_outcome_ended_idx": { + "name": "reading_attempts_user_outcome_ended_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "outcome", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ended_on", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reading_attempts_one_active_uidx": { + "name": "reading_attempts_one_active_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"reading_attempts\".\"outcome\" is null and \"reading_attempts\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "reading_attempts_external_uidx": { + "name": "reading_attempts_external_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"reading_attempts\".\"external_provider\" is not null and \"reading_attempts\".\"external_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reading_attempts_user_id_users_id_fk": { + "name": "reading_attempts_user_id_users_id_fk", + "tableFrom": "reading_attempts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reading_attempts_book_id_books_id_fk": { + "name": "reading_attempts_book_id_books_id_fk", + "tableFrom": "reading_attempts", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reading_attempts_outcome_chk": { + "name": "reading_attempts_outcome_chk", + "value": "\"reading_attempts\".\"outcome\" is null or \"reading_attempts\".\"outcome\" in ('completed', 'skimmed', 'abandoned')" + }, + "reading_attempts_origin_chk": { + "name": "reading_attempts_origin_chk", + "value": "\"reading_attempts\".\"origin\" in ('manual', 'bookorbit', 'kobo', 'koreader', 'hardcover', 'migration')" + }, + "reading_attempts_end_after_start_chk": { + "name": "reading_attempts_end_after_start_chk", + "value": "\"reading_attempts\".\"ended_on\" is null or \"reading_attempts\".\"started_on\" is null or \"reading_attempts\".\"ended_on\" >= \"reading_attempts\".\"started_on\"" + }, + "reading_attempts_closed_has_outcome_chk": { + "name": "reading_attempts_closed_has_outcome_chk", + "value": "\"reading_attempts\".\"ended_on\" is null or \"reading_attempts\".\"outcome\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.reading_progress": { + "name": "reading_progress", + "schema": "", + "columns": { + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "percentage": { + "name": "percentage", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cfi": { + "name": "cfi", + "type": "varchar(2000)", + "primaryKey": false, + "notNull": false + }, + "page_number": { + "name": "page_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "position_seconds": { + "name": "position_seconds", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "kobo_location_source": { + "name": "kobo_location_source", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": false + }, + "kobo_location_type": { + "name": "kobo_location_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "kobo_location_value": { + "name": "kobo_location_value", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "kobo_content_source_progress_percent": { + "name": "kobo_content_source_progress_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "koreader_progress": { + "name": "koreader_progress", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_read_at": { + "name": "last_read_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reading_progress_user_id_idx": { + "name": "reading_progress_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reading_progress_user_last_read_at_idx": { + "name": "reading_progress_user_last_read_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_read_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reading_progress_book_file_id_book_files_id_fk": { + "name": "reading_progress_book_file_id_book_files_id_fk", + "tableFrom": "reading_progress", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reading_progress_user_id_users_id_fk": { + "name": "reading_progress_user_id_users_id_fk", + "tableFrom": "reading_progress", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "reading_progress_book_file_id_user_id_pk": { + "name": "reading_progress_book_file_id_user_id_pk", + "columns": ["book_file_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reading_progress_percentage_range_chk": { + "name": "reading_progress_percentage_range_chk", + "value": "\"reading_progress\".\"percentage\" >= 0 and \"reading_progress\".\"percentage\" <= 100" + }, + "reading_progress_page_number_nonnegative_chk": { + "name": "reading_progress_page_number_nonnegative_chk", + "value": "\"reading_progress\".\"page_number\" is null or \"reading_progress\".\"page_number\" >= 0" + }, + "reading_progress_position_seconds_nonnegative_chk": { + "name": "reading_progress_position_seconds_nonnegative_chk", + "value": "\"reading_progress\".\"position_seconds\" is null or \"reading_progress\".\"position_seconds\" >= 0" + }, + "reading_progress_kobo_content_source_progress_range_chk": { + "name": "reading_progress_kobo_content_source_progress_range_chk", + "value": "\"reading_progress\".\"kobo_content_source_progress_percent\" is null or (\"reading_progress\".\"kobo_content_source_progress_percent\" >= 0 and \"reading_progress\".\"kobo_content_source_progress_percent\" <= 100)" + } + }, + "isRLSEnabled": false + }, + "public.reading_sessions": { + "name": "reading_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "attempt_id": { + "name": "attempt_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "progress_delta": { + "name": "progress_delta", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "end_progress": { + "name": "end_progress", + "type": "real", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "rs_user_session_id_uidx": { + "name": "rs_user_session_id_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rs_user_started_at_idx": { + "name": "rs_user_started_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rs_book_file_started_at_idx": { + "name": "rs_book_file_started_at_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rs_user_book_file_idx": { + "name": "rs_user_book_file_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rs_user_book_started_at_idx": { + "name": "rs_user_book_started_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reading_sessions_book_id_idx": { + "name": "reading_sessions_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "rs_attempt_started_at_idx": { + "name": "rs_attempt_started_at_idx", + "columns": [ + { + "expression": "attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reading_sessions_user_id_users_id_fk": { + "name": "reading_sessions_user_id_users_id_fk", + "tableFrom": "reading_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reading_sessions_book_file_id_book_files_id_fk": { + "name": "reading_sessions_book_file_id_book_files_id_fk", + "tableFrom": "reading_sessions", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reading_sessions_book_id_books_id_fk": { + "name": "reading_sessions_book_id_books_id_fk", + "tableFrom": "reading_sessions", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reading_sessions_attempt_id_reading_attempts_id_fk": { + "name": "reading_sessions_attempt_id_reading_attempts_id_fk", + "tableFrom": "reading_sessions", + "tableTo": "reading_attempts", + "columnsFrom": ["attempt_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "reading_sessions_source_chk": { + "name": "reading_sessions_source_chk", + "value": "\"reading_sessions\".\"source\" in ('web', 'koreader', 'manual', 'kobo')" + }, + "reading_sessions_duration_seconds_nonnegative_chk": { + "name": "reading_sessions_duration_seconds_nonnegative_chk", + "value": "\"reading_sessions\".\"duration_seconds\" >= 0" + }, + "reading_sessions_end_progress_range_chk": { + "name": "reading_sessions_end_progress_range_chk", + "value": "\"reading_sessions\".\"end_progress\" is null or (\"reading_sessions\".\"end_progress\" >= 0 and \"reading_sessions\".\"end_progress\" <= 100)" + }, + "reading_sessions_ended_after_started_chk": { + "name": "reading_sessions_ended_after_started_chk", + "value": "\"reading_sessions\".\"ended_at\" >= \"reading_sessions\".\"started_at\"" + } + }, + "isRLSEnabled": false + }, + "public.user_book_notes": { + "name": "user_book_notes", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ubn_user_id_idx": { + "name": "ubn_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ubn_book_id_idx": { + "name": "ubn_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ubn_book_user_idx": { + "name": "ubn_book_user_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_book_notes_user_id_users_id_fk": { + "name": "user_book_notes_user_id_users_id_fk", + "tableFrom": "user_book_notes", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_book_notes_book_id_books_id_fk": { + "name": "user_book_notes_book_id_books_id_fk", + "tableFrom": "user_book_notes", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_book_notes_user_id_book_id_pk": { + "name": "user_book_notes_user_id_book_id_pk", + "columns": ["user_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_book_notes_note_length_chk": { + "name": "user_book_notes_note_length_chk", + "value": "\"user_book_notes\".\"note\" is null or char_length(\"user_book_notes\".\"note\") <= 10000" + } + }, + "isRLSEnabled": false + }, + "public.user_book_ratings": { + "name": "user_book_ratings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "rating": { + "name": "rating", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ubr_user_id_idx": { + "name": "ubr_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ubr_book_id_idx": { + "name": "ubr_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ubr_book_user_rating_idx": { + "name": "ubr_book_user_rating_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_book_ratings_user_id_users_id_fk": { + "name": "user_book_ratings_user_id_users_id_fk", + "tableFrom": "user_book_ratings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_book_ratings_book_id_books_id_fk": { + "name": "user_book_ratings_book_id_books_id_fk", + "tableFrom": "user_book_ratings", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_book_ratings_user_id_book_id_pk": { + "name": "user_book_ratings_user_id_book_id_pk", + "columns": ["user_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_book_ratings_rating_range_chk": { + "name": "user_book_ratings_rating_range_chk", + "value": "\"user_book_ratings\".\"rating\" is null or (\"user_book_ratings\".\"rating\" >= 1 and \"user_book_ratings\".\"rating\" <= 5)" + } + }, + "isRLSEnabled": false + }, + "public.user_book_status": { + "name": "user_book_status", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'unread'" + }, + "source": { + "name": "source", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'auto'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ubs_user_id_idx": { + "name": "ubs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ubs_user_status_idx": { + "name": "ubs_user_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ubs_book_user_idx": { + "name": "ubs_book_user_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_book_status_user_id_users_id_fk": { + "name": "user_book_status_user_id_users_id_fk", + "tableFrom": "user_book_status", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_book_status_book_id_books_id_fk": { + "name": "user_book_status_book_id_books_id_fk", + "tableFrom": "user_book_status", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_book_status_user_id_book_id_pk": { + "name": "user_book_status_user_id_book_id_pk", + "columns": ["user_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_book_status_status_chk": { + "name": "user_book_status_status_chk", + "value": "\"user_book_status\".\"status\" in ('unread', 'want_to_read', 'reading', 'on_hold', 'rereading', 'read', 'skimmed', 'abandoned')" + }, + "user_book_status_source_chk": { + "name": "user_book_status_source_chk", + "value": "\"user_book_status\".\"source\" in ('auto', 'manual')" + }, + "user_book_status_finished_after_started_chk": { + "name": "user_book_status_finished_after_started_chk", + "value": "\"user_book_status\".\"finished_at\" is null or \"user_book_status\".\"started_at\" is null or \"user_book_status\".\"finished_at\" >= \"user_book_status\".\"started_at\"" + } + }, + "isRLSEnabled": false + }, + "public.user_reading_daily_stats": { + "name": "user_reading_daily_stats", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "day": { + "name": "day", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "reading_seconds": { + "name": "reading_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "progress_delta": { + "name": "progress_delta", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sessions_count": { + "name": "sessions_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "urds_user_day_idx": { + "name": "urds_user_day_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "day", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_reading_daily_stats_library_id_idx": { + "name": "user_reading_daily_stats_library_id_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_reading_daily_stats_user_id_users_id_fk": { + "name": "user_reading_daily_stats_user_id_users_id_fk", + "tableFrom": "user_reading_daily_stats", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_reading_daily_stats_library_id_libraries_id_fk": { + "name": "user_reading_daily_stats_library_id_libraries_id_fk", + "tableFrom": "user_reading_daily_stats", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_reading_daily_stats_user_id_library_id_day_pk": { + "name": "user_reading_daily_stats_user_id_library_id_day_pk", + "columns": ["user_id", "library_id", "day"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_reading_daily_stats_reading_seconds_nonnegative_chk": { + "name": "user_reading_daily_stats_reading_seconds_nonnegative_chk", + "value": "\"user_reading_daily_stats\".\"reading_seconds\" >= 0" + }, + "user_reading_daily_stats_sessions_count_nonnegative_chk": { + "name": "user_reading_daily_stats_sessions_count_nonnegative_chk", + "value": "\"user_reading_daily_stats\".\"sessions_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.oidc_group_mappings": { + "name": "oidc_group_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "oidc_group_claim": { + "name": "oidc_group_claim", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_name": { + "name": "permission_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oidc_group_mappings_provider_claim_uidx": { + "name": "oidc_group_mappings_provider_claim_uidx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "oidc_group_claim", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oidc_group_mappings_provider_id_oidc_providers_id_fk": { + "name": "oidc_group_mappings_provider_id_oidc_providers_id_fk", + "tableFrom": "oidc_group_mappings", + "tableTo": "oidc_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_identities": { + "name": "oidc_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "oidc_subject": { + "name": "oidc_subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_issuer": { + "name": "oidc_issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "linked_at": { + "name": "linked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oidc_identities_provider_subject_uidx": { + "name": "oidc_identities_provider_subject_uidx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "oidc_subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_identities_user_provider_uidx": { + "name": "oidc_identities_user_provider_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_identities_issuer_subject_uidx": { + "name": "oidc_identities_issuer_subject_uidx", + "columns": [ + { + "expression": "oidc_issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "oidc_subject", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_identities_user_id_idx": { + "name": "oidc_identities_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oidc_identities_user_id_users_id_fk": { + "name": "oidc_identities_user_id_users_id_fk", + "tableFrom": "oidc_identities", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_identities_provider_id_oidc_providers_id_fk": { + "name": "oidc_identities_provider_id_oidc_providers_id_fk", + "tableFrom": "oidc_identities", + "tableTo": "oidc_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_permission_grants": { + "name": "oidc_permission_grants", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "permission_name": { + "name": "permission_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "oidc_permission_grants_user_id_users_id_fk": { + "name": "oidc_permission_grants_user_id_users_id_fk", + "tableFrom": "oidc_permission_grants", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_permission_grants_provider_id_oidc_providers_id_fk": { + "name": "oidc_permission_grants_provider_id_oidc_providers_id_fk", + "tableFrom": "oidc_permission_grants", + "tableTo": "oidc_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "oidc_permission_grants_user_id_provider_id_permission_name_pk": { + "name": "oidc_permission_grants_user_id_provider_id_permission_name_pk", + "columns": ["user_id", "provider_id", "permission_name"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_providers": { + "name": "oidc_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "issuer_uri": { + "name": "issuer_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'openid profile email'" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_mapping": { + "name": "claim_mapping", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"username\":\"preferred_username\",\"name\":\"name\",\"email\":\"email\",\"groups\":\"groups\"}'::jsonb" + }, + "auto_provision": { + "name": "auto_provision", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"enabled\":false,\"allowLocalLinking\":false,\"defaultPermissionNames\":[]}'::jsonb" + }, + "display_order": { + "name": "display_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oidc_providers_enabled_order_idx": { + "name": "oidc_providers_enabled_order_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oidc_providers_slug_unique": { + "name": "oidc_providers_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + }, + "oidc_providers_issuer_uri_unique": { + "name": "oidc_providers_issuer_uri_unique", + "nullsNotDistinct": false, + "columns": ["issuer_uri"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_sessions": { + "name": "oidc_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "oidc_subject": { + "name": "oidc_subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_issuer": { + "name": "oidc_issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_session_id": { + "name": "oidc_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token_hint": { + "name": "id_token_hint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idp_refresh_token": { + "name": "idp_refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "name": "revoked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oidc_sessions_user_active_idx": { + "name": "oidc_sessions_user_active_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"oidc_sessions\".\"revoked\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_sessions_subject_issuer_idx": { + "name": "oidc_sessions_subject_issuer_idx", + "columns": [ + { + "expression": "oidc_subject", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "oidc_issuer", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_sessions_sid_idx": { + "name": "oidc_sessions_sid_idx", + "columns": [ + { + "expression": "oidc_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_sessions_expires_at_idx": { + "name": "oidc_sessions_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oidc_sessions_provider_id_idx": { + "name": "oidc_sessions_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oidc_sessions_user_id_users_id_fk": { + "name": "oidc_sessions_user_id_users_id_fk", + "tableFrom": "oidc_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oidc_sessions_provider_id_oidc_providers_id_fk": { + "name": "oidc_sessions_provider_id_oidc_providers_id_fk", + "tableFrom": "oidc_sessions", + "tableTo": "oidc_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_states": { + "name": "oidc_states", + "schema": "", + "columns": { + "state": { + "name": "state", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "meta": { + "name": "meta", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oidc_states_expires_at_idx": { + "name": "oidc_states_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oidc_states_provider_id_oidc_providers_id_fk": { + "name": "oidc_states_provider_id_oidc_providers_id_fk", + "tableFrom": "oidc_states", + "tableTo": "oidc_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oidc_used_jtis": { + "name": "oidc_used_jtis", + "schema": "", + "columns": { + "jti": { + "name": "jti", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oidc_used_jtis_expires_at_idx": { + "name": "oidc_used_jtis_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.opds_users": { + "name": "opds_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "opds_sort_order", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'recent'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "opds_users_username_lower_uidx": { + "name": "opds_users_username_lower_uidx", + "columns": [ + { + "expression": "lower(\"username\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "opds_users_user_id_users_id_fk": { + "name": "opds_users_user_id_users_id_fk", + "tableFrom": "opds_users", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "opds_users_username_unique": { + "name": "opds_users_username_unique", + "nullsNotDistinct": false, + "columns": ["username"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_book_entitlements": { + "name": "kobo_book_entitlements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cover_image_id": { + "name": "cover_image_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "needs_legacy_numeric_removal": { + "name": "needs_legacy_numeric_removal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kobo_book_entitlements_user_id_idx": { + "name": "kobo_book_entitlements_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kobo_book_entitlements_book_id_idx": { + "name": "kobo_book_entitlements_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_book_entitlements_user_id_users_id_fk": { + "name": "kobo_book_entitlements_user_id_users_id_fk", + "tableFrom": "kobo_book_entitlements", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_book_entitlements_book_id_books_id_fk": { + "name": "kobo_book_entitlements_book_id_books_id_fk", + "tableFrom": "kobo_book_entitlements", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kobo_book_entitlements_user_book_unique": { + "name": "kobo_book_entitlements_user_book_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "book_id"] + }, + "kobo_book_entitlements_user_entitlement_unique": { + "name": "kobo_book_entitlements_user_entitlement_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "entitlement_id"] + }, + "kobo_book_entitlements_user_cover_unique": { + "name": "kobo_book_entitlements_user_cover_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "cover_image_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_devices": { + "name": "kobo_devices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "client_device_id": { + "name": "client_device_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kobo_devices_user_id_idx": { + "name": "kobo_devices_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_devices_user_id_users_id_fk": { + "name": "kobo_devices_user_id_users_id_fk", + "tableFrom": "kobo_devices", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kobo_devices_token_unique": { + "name": "kobo_devices_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + }, + "kobo_devices_client_device_id_unique": { + "name": "kobo_devices_client_device_id_unique", + "nullsNotDistinct": false, + "columns": ["client_device_id"] + }, + "kobo_devices_id_user_id_unique": { + "name": "kobo_devices_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_library_snapshots": { + "name": "kobo_library_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "legacy_device_cutoff_at": { + "name": "legacy_device_cutoff_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kobo_library_snapshots_user_id_users_id_fk": { + "name": "kobo_library_snapshots_user_id_users_id_fk", + "tableFrom": "kobo_library_snapshots", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kobo_library_snapshots_user_id_unique": { + "name": "kobo_library_snapshots_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_snapshot_books": { + "name": "kobo_snapshot_books", + "schema": "", + "columns": { + "snapshot_id": { + "name": "snapshot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "synced": { + "name": "synced", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pending_delete": { + "name": "pending_delete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_new": { + "name": "is_new", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "removed_by_device": { + "name": "removed_by_device", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "delivery_hash": { + "name": "delivery_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "metadata_hash": { + "name": "metadata_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kobo_snapshot_books_book_id_idx": { + "name": "kobo_snapshot_books_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kobo_snapshot_books_snapshot_synced_book_idx": { + "name": "kobo_snapshot_books_snapshot_synced_book_idx", + "columns": [ + { + "expression": "snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "synced", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_snapshot_books_snapshot_id_kobo_library_snapshots_id_fk": { + "name": "kobo_snapshot_books_snapshot_id_kobo_library_snapshots_id_fk", + "tableFrom": "kobo_snapshot_books", + "tableTo": "kobo_library_snapshots", + "columnsFrom": ["snapshot_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_snapshot_books_book_id_books_id_fk": { + "name": "kobo_snapshot_books_book_id_books_id_fk", + "tableFrom": "kobo_snapshot_books", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kobo_snapshot_books_snapshot_id_book_id_pk": { + "name": "kobo_snapshot_books_snapshot_id_book_id_pk", + "columns": ["snapshot_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_device_library_snapshots": { + "name": "kobo_device_library_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kobo_device_library_snapshots_user_id_idx": { + "name": "kobo_device_library_snapshots_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_device_library_snapshots_user_id_users_id_fk": { + "name": "kobo_device_library_snapshots_user_id_users_id_fk", + "tableFrom": "kobo_device_library_snapshots", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_device_library_snapshots_device_owner_fk": { + "name": "kobo_device_library_snapshots_device_owner_fk", + "tableFrom": "kobo_device_library_snapshots", + "tableTo": "kobo_devices", + "columnsFrom": ["device_id", "user_id"], + "columnsTo": ["id", "user_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kobo_device_library_snapshots_device_id_unique": { + "name": "kobo_device_library_snapshots_device_id_unique", + "nullsNotDistinct": false, + "columns": ["device_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_reading_states": { + "name": "kobo_reading_states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "entitlement_id": { + "name": "entitlement_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at_kobo": { + "name": "created_at_kobo", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "last_modified_kobo": { + "name": "last_modified_kobo", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "priority_timestamp": { + "name": "priority_timestamp", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "current_bookmark": { + "name": "current_bookmark", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "statistics": { + "name": "statistics", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_info": { + "name": "status_info", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kobo_reading_states_book_id_idx": { + "name": "kobo_reading_states_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_reading_states_user_id_users_id_fk": { + "name": "kobo_reading_states_user_id_users_id_fk", + "tableFrom": "kobo_reading_states", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_reading_states_book_id_books_id_fk": { + "name": "kobo_reading_states_book_id_books_id_fk", + "tableFrom": "kobo_reading_states", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kobo_reading_states_user_id_book_id_unique": { + "name": "kobo_reading_states_user_id_book_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "book_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_device_snapshot_books": { + "name": "kobo_device_snapshot_books", + "schema": "", + "columns": { + "snapshot_id": { + "name": "snapshot_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "synced": { + "name": "synced", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pending_delete": { + "name": "pending_delete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_new": { + "name": "is_new", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "removed_by_device": { + "name": "removed_by_device", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "needs_legacy_numeric_removal": { + "name": "needs_legacy_numeric_removal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "delivery_hash": { + "name": "delivery_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "metadata_hash": { + "name": "metadata_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kobo_device_snapshot_books_book_id_idx": { + "name": "kobo_device_snapshot_books_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kobo_device_snapshot_books_snapshot_synced_book_idx": { + "name": "kobo_device_snapshot_books_snapshot_synced_book_idx", + "columns": [ + { + "expression": "snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "synced", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_device_snapshot_books_snapshot_id_kobo_device_library_snapshots_id_fk": { + "name": "kobo_device_snapshot_books_snapshot_id_kobo_device_library_snapshots_id_fk", + "tableFrom": "kobo_device_snapshot_books", + "tableTo": "kobo_device_library_snapshots", + "columnsFrom": ["snapshot_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_device_snapshot_books_book_id_books_id_fk": { + "name": "kobo_device_snapshot_books_book_id_books_id_fk", + "tableFrom": "kobo_device_snapshot_books", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kobo_device_snapshot_books_snapshot_id_book_id_pk": { + "name": "kobo_device_snapshot_books_snapshot_id_book_id_pk", + "columns": ["snapshot_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_sync_history": { + "name": "kobo_sync_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "event": { + "name": "event", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "counts": { + "name": "counts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "error_class": { + "name": "error_class", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kobo_sync_history_user_created_idx": { + "name": "kobo_sync_history_user_created_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kobo_sync_history_device_created_idx": { + "name": "kobo_sync_history_device_created_idx", + "columns": [ + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kobo_sync_history_user_id_users_id_fk": { + "name": "kobo_sync_history_user_id_users_id_fk", + "tableFrom": "kobo_sync_history", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_sync_history_device_id_kobo_devices_id_fk": { + "name": "kobo_sync_history_device_id_kobo_devices_id_fk", + "tableFrom": "kobo_sync_history", + "tableTo": "kobo_devices", + "columnsFrom": ["device_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kobo_sync_history_event_chk": { + "name": "kobo_sync_history_event_chk", + "value": "\"kobo_sync_history\".\"event\" in ('library_sync', 'book_download', 'progress_update', 'annotations_pull', 'annotations_push')" + }, + "kobo_sync_history_status_chk": { + "name": "kobo_sync_history_status_chk", + "value": "\"kobo_sync_history\".\"status\" in ('success', 'failed')" + }, + "kobo_sync_history_duration_nonnegative_chk": { + "name": "kobo_sync_history_duration_nonnegative_chk", + "value": "\"kobo_sync_history\".\"duration_ms\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.kobo_sync_settings": { + "name": "kobo_sync_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reading_threshold": { + "name": "reading_threshold", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "finished_threshold": { + "name": "finished_threshold", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 99 + }, + "convert_to_kepub": { + "name": "convert_to_kepub", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "force_enable_hyphenation": { + "name": "force_enable_hyphenation", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "kepub_conversion_limit_mb": { + "name": "kepub_conversion_limit_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "two_way_progress_sync": { + "name": "two_way_progress_sync", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sync_bookorbit_annotations_to_kobo": { + "name": "sync_bookorbit_annotations_to_kobo", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "store_sync": { + "name": "store_sync", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kobo_sync_settings_user_id_users_id_fk": { + "name": "kobo_sync_settings_user_id_users_id_fk", + "tableFrom": "kobo_sync_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kobo_sync_settings_user_id_unique": { + "name": "kobo_sync_settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": { + "kobo_sync_settings_reading_threshold_range_chk": { + "name": "kobo_sync_settings_reading_threshold_range_chk", + "value": "\"kobo_sync_settings\".\"reading_threshold\" >= 0 and \"kobo_sync_settings\".\"reading_threshold\" <= 100" + }, + "kobo_sync_settings_finished_threshold_range_chk": { + "name": "kobo_sync_settings_finished_threshold_range_chk", + "value": "\"kobo_sync_settings\".\"finished_threshold\" >= 0 and \"kobo_sync_settings\".\"finished_threshold\" <= 100" + }, + "kobo_sync_settings_conversion_limit_nonnegative_chk": { + "name": "kobo_sync_settings_conversion_limit_nonnegative_chk", + "value": "\"kobo_sync_settings\".\"kepub_conversion_limit_mb\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.book_dock_files": { + "name": "book_dock_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "absolute_path": { + "name": "absolute_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "embedded_metadata": { + "name": "embedded_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "selected_metadata": { + "name": "selected_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "fetched_metadata": { + "name": "fetched_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cover_path": { + "name": "cover_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_library_id": { + "name": "target_library_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "target_folder_id": { + "name": "target_folder_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fetched_metadata_sources": { + "name": "fetched_metadata_sources", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata_edited_at": { + "name": "metadata_edited_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_dock_files_status_idx": { + "name": "book_dock_files_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_dock_files_target_library_id_idx": { + "name": "book_dock_files_target_library_id_idx", + "columns": [ + { + "expression": "target_library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_dock_files_uploaded_by_idx": { + "name": "book_dock_files_uploaded_by_idx", + "columns": [ + { + "expression": "uploaded_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_dock_files_target_library_id_libraries_id_fk": { + "name": "book_dock_files_target_library_id_libraries_id_fk", + "tableFrom": "book_dock_files", + "tableTo": "libraries", + "columnsFrom": ["target_library_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_dock_files_target_folder_id_library_folders_id_fk": { + "name": "book_dock_files_target_folder_id_library_folders_id_fk", + "tableFrom": "book_dock_files", + "tableTo": "library_folders", + "columnsFrom": ["target_folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_dock_files_uploaded_by_users_id_fk": { + "name": "book_dock_files_uploaded_by_users_id_fk", + "tableFrom": "book_dock_files", + "tableTo": "users", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "book_dock_files_absolute_path_unique": { + "name": "book_dock_files_absolute_path_unique", + "nullsNotDistinct": false, + "columns": ["absolute_path"] + } + }, + "policies": {}, + "checkConstraints": { + "book_dock_files_status_chk": { + "name": "book_dock_files_status_chk", + "value": "\"book_dock_files\".\"status\" in ('pending', 'extracting', 'fetching', 'ready', 'error')" + }, + "book_dock_files_confidence_range_chk": { + "name": "book_dock_files_confidence_range_chk", + "value": "\"book_dock_files\".\"confidence\" is null or (\"book_dock_files\".\"confidence\" >= 0 and \"book_dock_files\".\"confidence\" <= 100)" + } + }, + "isRLSEnabled": false + }, + "public.file_write_log": { + "name": "file_write_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "fields_written": { + "name": "fields_written", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "triggered_by": { + "name": "triggered_by", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "written_at": { + "name": "written_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "fwl_book_id_written_at_idx": { + "name": "fwl_book_id_written_at_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "written_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "file_write_log_book_file_id_idx": { + "name": "file_write_log_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "file_write_log_book_id_books_id_fk": { + "name": "file_write_log_book_id_books_id_fk", + "tableFrom": "file_write_log", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "file_write_log_book_file_id_book_files_id_fk": { + "name": "file_write_log_book_file_id_book_files_id_fk", + "tableFrom": "file_write_log", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "file_write_log_user_id_users_id_fk": { + "name": "file_write_log_user_id_users_id_fk", + "tableFrom": "file_write_log", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "file_write_log_status_chk": { + "name": "file_write_log_status_chk", + "value": "\"file_write_log\".\"status\" in ('success', 'skipped', 'failed')" + }, + "file_write_log_triggered_by_chk": { + "name": "file_write_log_triggered_by_chk", + "value": "\"file_write_log\".\"triggered_by\" in ('auto', 'sync')" + }, + "file_write_log_duration_ms_nonnegative_chk": { + "name": "file_write_log_duration_ms_nonnegative_chk", + "value": "\"file_write_log\".\"duration_ms\" is null or \"file_write_log\".\"duration_ms\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.email_templates": { + "name": "email_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_templates_one_default_per_user_uidx": { + "name": "email_templates_one_default_per_user_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"email_templates\".\"is_default\" = true and \"email_templates\".\"user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_templates_system_name_unique": { + "name": "email_templates_system_name_unique", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"email_templates\".\"user_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_templates_user_id_users_id_fk": { + "name": "email_templates_user_id_users_id_fk", + "tableFrom": "email_templates", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "email_templates_user_id_name_unique": { + "name": "email_templates_user_id_name_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_providers": { + "name": "email_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "host": { + "name": "host", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "password_enc": { + "name": "password_enc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "from_name": { + "name": "from_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "from_address": { + "name": "from_address", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "auth": { + "name": "auth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "ssl": { + "name": "ssl", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "start_tls": { + "name": "start_tls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_shared": { + "name": "is_shared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_system_provider": { + "name": "is_system_provider", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tls_reject_unauthorized": { + "name": "tls_reject_unauthorized", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_providers_one_default_per_user_uidx": { + "name": "email_providers_one_default_per_user_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"email_providers\".\"is_default\" = true and \"email_providers\".\"user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_providers_one_system_provider_uidx": { + "name": "email_providers_one_system_provider_uidx", + "columns": [ + { + "expression": "is_system_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"email_providers\".\"is_system_provider\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_providers_user_id_users_id_fk": { + "name": "email_providers_user_id_users_id_fk", + "tableFrom": "email_providers", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "email_providers_user_id_name_unique": { + "name": "email_providers_user_id_name_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "name"] + }, + "email_providers_id_user_id_unique": { + "name": "email_providers_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": { + "email_providers_port_range_chk": { + "name": "email_providers_port_range_chk", + "value": "\"email_providers\".\"port\" >= 1 and \"email_providers\".\"port\" <= 65535" + } + }, + "isRLSEnabled": false + }, + "public.email_recipient_group_members": { + "name": "email_recipient_group_members", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "recipient_id": { + "name": "recipient_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "email_recipient_group_members_user_id_users_id_fk": { + "name": "email_recipient_group_members_user_id_users_id_fk", + "tableFrom": "email_recipient_group_members", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_recipient_group_members_group_id_email_recipient_groups_id_fk": { + "name": "email_recipient_group_members_group_id_email_recipient_groups_id_fk", + "tableFrom": "email_recipient_group_members", + "tableTo": "email_recipient_groups", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_recipient_group_members_recipient_id_email_recipients_id_fk": { + "name": "email_recipient_group_members_recipient_id_email_recipients_id_fk", + "tableFrom": "email_recipient_group_members", + "tableTo": "email_recipients", + "columnsFrom": ["recipient_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_recipient_group_members_group_user_fk": { + "name": "email_recipient_group_members_group_user_fk", + "tableFrom": "email_recipient_group_members", + "tableTo": "email_recipient_groups", + "columnsFrom": ["group_id", "user_id"], + "columnsTo": ["id", "user_id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "email_recipient_group_members_recipient_user_fk": { + "name": "email_recipient_group_members_recipient_user_fk", + "tableFrom": "email_recipient_group_members", + "tableTo": "email_recipients", + "columnsFrom": ["recipient_id", "user_id"], + "columnsTo": ["id", "user_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "email_recipient_group_members_group_id_recipient_id_pk": { + "name": "email_recipient_group_members_group_id_recipient_id_pk", + "columns": ["group_id", "recipient_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_recipient_groups": { + "name": "email_recipient_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "default_template_id": { + "name": "default_template_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "email_recipient_groups_user_id_users_id_fk": { + "name": "email_recipient_groups_user_id_users_id_fk", + "tableFrom": "email_recipient_groups", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_recipient_groups_default_template_id_email_templates_id_fk": { + "name": "email_recipient_groups_default_template_id_email_templates_id_fk", + "tableFrom": "email_recipient_groups", + "tableTo": "email_templates", + "columnsFrom": ["default_template_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "email_recipient_groups_user_id_name_unique": { + "name": "email_recipient_groups_user_id_name_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "name"] + }, + "email_recipient_groups_id_user_id_unique": { + "name": "email_recipient_groups_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_recipients": { + "name": "email_recipients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "device_type": { + "name": "device_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "preferred_format": { + "name": "preferred_format", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "default_template_id": { + "name": "default_template_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_recipients_user_email_lower_uidx": { + "name": "email_recipients_user_email_lower_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_recipients_one_default_per_user_uidx": { + "name": "email_recipients_one_default_per_user_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"email_recipients\".\"is_default\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_recipients_user_id_users_id_fk": { + "name": "email_recipients_user_id_users_id_fk", + "tableFrom": "email_recipients", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_recipients_default_template_id_email_templates_id_fk": { + "name": "email_recipients_default_template_id_email_templates_id_fk", + "tableFrom": "email_recipients", + "tableTo": "email_templates", + "columnsFrom": ["default_template_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "email_recipients_user_id_email_unique": { + "name": "email_recipients_user_id_email_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "email"] + }, + "email_recipients_id_user_id_unique": { + "name": "email_recipients_id_user_id_unique", + "nullsNotDistinct": false, + "columns": ["id", "user_id"] + } + }, + "policies": {}, + "checkConstraints": { + "email_recipients_device_type_chk": { + "name": "email_recipients_device_type_chk", + "value": "\"email_recipients\".\"device_type\" is null or \"email_recipients\".\"device_type\" in ('kindle', 'kobo', 'other')" + }, + "email_recipients_preferred_format_chk": { + "name": "email_recipients_preferred_format_chk", + "value": "\"email_recipients\".\"preferred_format\" is null or \"email_recipients\".\"preferred_format\" in ('epub', 'pdf', 'mobi', 'azw3', 'cbz', 'cbr')" + } + }, + "isRLSEnabled": false + }, + "public.email_preferences": { + "name": "email_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "default_provider_id": { + "name": "default_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "default_recipient_id": { + "name": "default_recipient_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "default_template_id": { + "name": "default_template_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "email_preferences_user_id_users_id_fk": { + "name": "email_preferences_user_id_users_id_fk", + "tableFrom": "email_preferences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_preferences_default_provider_id_email_providers_id_fk": { + "name": "email_preferences_default_provider_id_email_providers_id_fk", + "tableFrom": "email_preferences", + "tableTo": "email_providers", + "columnsFrom": ["default_provider_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "email_preferences_default_recipient_id_email_recipients_id_fk": { + "name": "email_preferences_default_recipient_id_email_recipients_id_fk", + "tableFrom": "email_preferences", + "tableTo": "email_recipients", + "columnsFrom": ["default_recipient_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "email_preferences_default_template_id_email_templates_id_fk": { + "name": "email_preferences_default_template_id_email_templates_id_fk", + "tableFrom": "email_preferences", + "tableTo": "email_templates", + "columnsFrom": ["default_template_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "email_preferences_default_recipient_owner_fk": { + "name": "email_preferences_default_recipient_owner_fk", + "tableFrom": "email_preferences", + "tableTo": "email_recipients", + "columnsFrom": ["default_recipient_id", "user_id"], + "columnsTo": ["id", "user_id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "email_preferences_user_id_unique": { + "name": "email_preferences_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_send_log": { + "name": "email_send_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "template_id": { + "name": "template_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "to_email": { + "name": "to_email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "to_name": { + "name": "to_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_send_log_user_created_at_idx": { + "name": "email_send_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_send_log_book_id_idx": { + "name": "email_send_log_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_send_log_book_file_id_idx": { + "name": "email_send_log_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_send_log_created_at_idx": { + "name": "email_send_log_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_send_log_status_next_retry_idx": { + "name": "email_send_log_status_next_retry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_send_log_user_id_users_id_fk": { + "name": "email_send_log_user_id_users_id_fk", + "tableFrom": "email_send_log", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_send_log_book_id_books_id_fk": { + "name": "email_send_log_book_id_books_id_fk", + "tableFrom": "email_send_log", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "email_send_log_book_file_id_book_files_id_fk": { + "name": "email_send_log_book_file_id_book_files_id_fk", + "tableFrom": "email_send_log", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "email_send_log_provider_id_email_providers_id_fk": { + "name": "email_send_log_provider_id_email_providers_id_fk", + "tableFrom": "email_send_log", + "tableTo": "email_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "email_send_log_template_id_email_templates_id_fk": { + "name": "email_send_log_template_id_email_templates_id_fk", + "tableFrom": "email_send_log", + "tableTo": "email_templates", + "columnsFrom": ["template_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_send_log_status_chk": { + "name": "email_send_log_status_chk", + "value": "\"email_send_log\".\"status\" in ('pending', 'sent', 'failed')" + }, + "email_send_log_attempt_count_nonnegative_chk": { + "name": "email_send_log_attempt_count_nonnegative_chk", + "value": "\"email_send_log\".\"attempt_count\" >= 0" + }, + "email_send_log_sent_after_created_chk": { + "name": "email_send_log_sent_after_created_chk", + "value": "\"email_send_log\".\"sent_at\" is null or \"email_send_log\".\"sent_at\" >= \"email_send_log\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_url": { + "name": "action_url", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": false + }, + "meta": { + "name": "meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "read": { + "name": "read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "notifications_user_id_idx": { + "name": "notifications_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_user_unread_idx": { + "name": "notifications_user_unread_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "read", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_created_at_idx": { + "name": "notifications_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notifications_user_id_users_id_fk": { + "name": "notifications_user_id_users_id_fk", + "tableFrom": "notifications", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dismissed_duplicate_pairs": { + "name": "dismissed_duplicate_pairs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "entity_id_a": { + "name": "entity_id_a", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "entity_id_b": { + "name": "entity_id_b", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dismissed_by": { + "name": "dismissed_by", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dismissed_duplicate_pairs_entity_a_idx": { + "name": "dismissed_duplicate_pairs_entity_a_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id_a", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dismissed_duplicate_pairs_entity_b_idx": { + "name": "dismissed_duplicate_pairs_entity_b_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id_b", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dismissed_duplicate_pairs_dismissed_by_users_id_fk": { + "name": "dismissed_duplicate_pairs_dismissed_by_users_id_fk", + "tableFrom": "dismissed_duplicate_pairs", + "tableTo": "users", + "columnsFrom": ["dismissed_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "dismissed_duplicate_pairs_unique": { + "name": "dismissed_duplicate_pairs_unique", + "nullsNotDistinct": false, + "columns": ["entity_type", "entity_id_a", "entity_id_b"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dismissed_inline_duplicate_pairs": { + "name": "dismissed_inline_duplicate_pairs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "value_a": { + "name": "value_a", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "value_b": { + "name": "value_b", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dismissed_by": { + "name": "dismissed_by", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dismissed_inline_pairs_entity_a_idx": { + "name": "dismissed_inline_pairs_entity_a_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "value_a", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dismissed_inline_pairs_entity_b_idx": { + "name": "dismissed_inline_pairs_entity_b_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "value_b", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dismissed_inline_duplicate_pairs_dismissed_by_users_id_fk": { + "name": "dismissed_inline_duplicate_pairs_dismissed_by_users_id_fk", + "tableFrom": "dismissed_inline_duplicate_pairs", + "tableTo": "users", + "columnsFrom": ["dismissed_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "dismissed_inline_duplicate_pairs_unique": { + "name": "dismissed_inline_duplicate_pairs_unique", + "nullsNotDistinct": false, + "columns": ["entity_type", "value_a", "value_b"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entity_duplicate_candidates": { + "name": "entity_duplicate_candidates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "entity_id_a": { + "name": "entity_id_a", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "entity_id_b": { + "name": "entity_id_b", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sim_score": { + "name": "sim_score", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "computed_at": { + "name": "computed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "entity_dup_candidates_type_sim_idx": { + "name": "entity_dup_candidates_type_sim_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sim_score", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "entity_dup_candidates_entity_a_idx": { + "name": "entity_dup_candidates_entity_a_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id_a", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "entity_dup_candidates_entity_b_idx": { + "name": "entity_dup_candidates_entity_b_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id_b", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "entity_duplicate_candidates_unique": { + "name": "entity_duplicate_candidates_unique", + "nullsNotDistinct": false, + "columns": ["entity_type", "entity_id_a", "entity_id_b"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entity_duplicate_scan_status": { + "name": "entity_duplicate_scan_status", + "schema": "", + "columns": { + "entity_type": { + "name": "entity_type", + "type": "varchar(50)", + "primaryKey": true, + "notNull": true + }, + "is_computing": { + "name": "is_computing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "computed_at": { + "name": "computed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "total_pairs": { + "name": "total_pairs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "threshold": { + "name": "threshold", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "processed_count": { + "name": "processed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_count": { + "name": "total_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.server_fonts": { + "name": "server_fonts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "family_name": { + "name": "family_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "original_file_name": { + "name": "original_file_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "stored_file_name": { + "name": "stored_file_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 400 + }, + "style": { + "name": "style", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sf_hash_uidx": { + "name": "sf_hash_uidx", + "columns": [ + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sf_family_weight_style_uidx": { + "name": "sf_family_weight_style_uidx", + "columns": [ + { + "expression": "family_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "style", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "server_fonts_uploaded_by_users_id_fk": { + "name": "server_fonts_uploaded_by_users_id_fk", + "tableFrom": "server_fonts", + "tableTo": "users", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "server_fonts_format_chk": { + "name": "server_fonts_format_chk", + "value": "\"server_fonts\".\"format\" in ('ttf', 'otf', 'woff', 'woff2')" + }, + "server_fonts_weight_chk": { + "name": "server_fonts_weight_chk", + "value": "\"server_fonts\".\"weight\" >= 100 and \"server_fonts\".\"weight\" <= 900 and \"server_fonts\".\"weight\" % 100 = 0" + }, + "server_fonts_style_chk": { + "name": "server_fonts_style_chk", + "value": "\"server_fonts\".\"style\" in ('normal', 'italic')" + } + }, + "isRLSEnabled": false + }, + "public.user_fonts": { + "name": "user_fonts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "family_name": { + "name": "family_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "original_file_name": { + "name": "original_file_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "stored_file_name": { + "name": "stored_file_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 400 + }, + "style": { + "name": "style", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uf_user_hash_uidx": { + "name": "uf_user_hash_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uf_user_family_weight_style_uidx": { + "name": "uf_user_family_weight_style_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "family_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "style", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_fonts_user_id_users_id_fk": { + "name": "user_fonts_user_id_users_id_fk", + "tableFrom": "user_fonts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_fonts_format_chk": { + "name": "user_fonts_format_chk", + "value": "\"user_fonts\".\"format\" in ('ttf', 'otf', 'woff', 'woff2')" + }, + "user_fonts_weight_chk": { + "name": "user_fonts_weight_chk", + "value": "\"user_fonts\".\"weight\" >= 100 and \"user_fonts\".\"weight\" <= 900 and \"user_fonts\".\"weight\" % 100 = 0" + }, + "user_fonts_style_chk": { + "name": "user_fonts_style_chk", + "value": "\"user_fonts\".\"style\" in ('normal', 'italic')" + } + }, + "isRLSEnabled": false + }, + "public.book_file_chapters": { + "name": "book_file_chapters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chapter_index": { + "name": "chapter_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "href": { + "name": "href", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "spine_index": { + "name": "spine_index", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "book_file_chapters_book_file_id_chapter_index_uidx": { + "name": "book_file_chapters_book_file_id_chapter_index_uidx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chapter_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_file_chapters_book_file_id_idx": { + "name": "book_file_chapters_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_file_chapters_book_file_id_book_files_id_fk": { + "name": "book_file_chapters_book_file_id_book_files_id_fk", + "tableFrom": "book_file_chapters", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_file_hash_history": { + "name": "book_file_hash_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_file_hash_history_book_file_id_file_hash_idx": { + "name": "book_file_hash_history_book_file_id_file_hash_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_file_hash_history_file_hash_idx": { + "name": "book_file_hash_history_file_hash_idx", + "columns": [ + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_file_hash_history_book_file_id_book_files_id_fk": { + "name": "book_file_hash_history_book_file_id_book_files_id_fk", + "tableFrom": "book_file_hash_history", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_file_hash_history_reason_chk": { + "name": "book_file_hash_history_reason_chk", + "value": "\"book_file_hash_history\".\"reason\" in ('file_write', 'external_change', 'rescan')" + } + }, + "isRLSEnabled": false + }, + "public.koreader_book_hash_links": { + "name": "koreader_book_hash_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "koreader_title": { + "name": "koreader_title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "koreader_authors": { + "name": "koreader_authors", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "koreader_last_open": { + "name": "koreader_last_open", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_book_hash_links_user_hash_uidx": { + "name": "koreader_book_hash_links_user_hash_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_book_hash_links_user_updated_idx": { + "name": "koreader_book_hash_links_user_updated_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_book_hash_links_book_file_id_idx": { + "name": "koreader_book_hash_links_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_book_hash_links_user_id_users_id_fk": { + "name": "koreader_book_hash_links_user_id_users_id_fk", + "tableFrom": "koreader_book_hash_links", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "koreader_book_hash_links_book_file_id_book_files_id_fk": { + "name": "koreader_book_hash_links_book_file_id_book_files_id_fk", + "tableFrom": "koreader_book_hash_links", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "koreader_book_hash_links_hash_chk": { + "name": "koreader_book_hash_links_hash_chk", + "value": "\"koreader_book_hash_links\".\"hash\" ~ '^[0-9a-f]{32}$'" + } + }, + "isRLSEnabled": false + }, + "public.koreader_bookmark_links": { + "name": "koreader_bookmark_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "bookmark_id": { + "name": "bookmark_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "koreader_key": { + "name": "koreader_key", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "device_datetime": { + "name": "device_datetime", + "type": "varchar(19)", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_bookmark_links_bookmark_device_uidx": { + "name": "koreader_bookmark_links_bookmark_device_uidx", + "columns": [ + { + "expression": "bookmark_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_bookmark_links_user_device_key_idx": { + "name": "koreader_bookmark_links_user_device_key_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "koreader_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_bookmark_links_user_device_idx": { + "name": "koreader_bookmark_links_user_device_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_bookmark_links_bookmark_id_idx": { + "name": "koreader_bookmark_links_bookmark_id_idx", + "columns": [ + { + "expression": "bookmark_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_bookmark_links_bookmark_id_bookmarks_id_fk": { + "name": "koreader_bookmark_links_bookmark_id_bookmarks_id_fk", + "tableFrom": "koreader_bookmark_links", + "tableTo": "bookmarks", + "columnsFrom": ["bookmark_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "koreader_bookmark_links_user_id_users_id_fk": { + "name": "koreader_bookmark_links_user_id_users_id_fk", + "tableFrom": "koreader_bookmark_links", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_device_progress": { + "name": "koreader_device_progress", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device": { + "name": "device", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'KOReader'" + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "percentage": { + "name": "percentage", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "progress": { + "name": "progress", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chapter_index": { + "name": "chapter_index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sync_timestamp": { + "name": "sync_timestamp", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "orphaned": { + "name": "orphaned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "orphaned_hash": { + "name": "orphaned_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_device_progress_book_user_device_uidx": { + "name": "koreader_device_progress_book_user_device_uidx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"koreader_device_progress\".\"orphaned\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_device_progress_orphaned_hash_idx": { + "name": "koreader_device_progress_orphaned_hash_idx", + "columns": [ + { + "expression": "orphaned_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"koreader_device_progress\".\"orphaned\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_device_progress_user_updated_at_idx": { + "name": "koreader_device_progress_user_updated_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_device_progress_book_file_id_idx": { + "name": "koreader_device_progress_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"koreader_device_progress\".\"book_file_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_device_progress_book_file_id_book_files_id_fk": { + "name": "koreader_device_progress_book_file_id_book_files_id_fk", + "tableFrom": "koreader_device_progress", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "koreader_device_progress_user_id_users_id_fk": { + "name": "koreader_device_progress_user_id_users_id_fk", + "tableFrom": "koreader_device_progress", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "koreader_device_progress_percentage_range_chk": { + "name": "koreader_device_progress_percentage_range_chk", + "value": "\"koreader_device_progress\".\"percentage\" is null or (\"koreader_device_progress\".\"percentage\" >= 0 and \"koreader_device_progress\".\"percentage\" <= 1)" + } + }, + "isRLSEnabled": false + }, + "public.koreader_device_retirements": { + "name": "koreader_device_retirements", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_device_retirements_user_id_idx": { + "name": "koreader_device_retirements_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_device_retirements_user_id_users_id_fk": { + "name": "koreader_device_retirements_user_id_users_id_fk", + "tableFrom": "koreader_device_retirements", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_device_retirements_user_id_device_id_pk": { + "name": "koreader_device_retirements_user_id_device_id_pk", + "columns": ["user_id", "device_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_device_settings": { + "name": "koreader_device_settings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "file_naming_pattern": { + "name": "file_naming_pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "series_file_naming_pattern": { + "name": "series_file_naming_pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "standalone_file_naming_pattern": { + "name": "standalone_file_naming_pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_device_settings_user_id_idx": { + "name": "koreader_device_settings_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_device_settings_user_id_users_id_fk": { + "name": "koreader_device_settings_user_id_users_id_fk", + "tableFrom": "koreader_device_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_device_settings_user_id_device_id_pk": { + "name": "koreader_device_settings_user_id_device_id_pk", + "columns": ["user_id", "device_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_device_sweeps": { + "name": "koreader_device_sweeps", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "device_model": { + "name": "device_model", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'KOReader'" + }, + "plugin_version": { + "name": "plugin_version", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "last_sweep_at": { + "name": "last_sweep_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_sweep_books_matched": { + "name": "last_sweep_books_matched", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_sweep_page_stats": { + "name": "last_sweep_page_stats", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_sweep_annotations": { + "name": "last_sweep_annotations", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kds_user_id_idx": { + "name": "kds_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_device_sweeps_user_id_users_id_fk": { + "name": "koreader_device_sweeps_user_id_users_id_fk", + "tableFrom": "koreader_device_sweeps", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_device_sweeps_user_id_device_id_pk": { + "name": "koreader_device_sweeps_user_id_device_id_pk", + "columns": ["user_id", "device_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_page_stats": { + "name": "koreader_page_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "page": { + "name": "page", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "start_time": { + "name": "start_time", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "total_pages": { + "name": "total_pages", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kps_user_file_device_page_start_uidx": { + "name": "kps_user_file_device_page_start_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "page", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "start_time", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kps_user_file_device_start_idx": { + "name": "kps_user_file_device_start_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "start_time", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kps_user_id_idx": { + "name": "kps_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_page_stats_book_file_id_idx": { + "name": "koreader_page_stats_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_page_stats_user_id_users_id_fk": { + "name": "koreader_page_stats_user_id_users_id_fk", + "tableFrom": "koreader_page_stats", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "koreader_page_stats_book_file_id_book_files_id_fk": { + "name": "koreader_page_stats_book_file_id_book_files_id_fk", + "tableFrom": "koreader_page_stats", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "koreader_page_stats_duration_nonnegative_chk": { + "name": "koreader_page_stats_duration_nonnegative_chk", + "value": "\"koreader_page_stats\".\"duration_seconds\" >= 0" + }, + "koreader_page_stats_page_nonnegative_chk": { + "name": "koreader_page_stats_page_nonnegative_chk", + "value": "\"koreader_page_stats\".\"page\" >= 0" + }, + "koreader_page_stats_total_pages_positive_chk": { + "name": "koreader_page_stats_total_pages_positive_chk", + "value": "\"koreader_page_stats\".\"total_pages\" > 0" + }, + "koreader_page_stats_start_time_positive_chk": { + "name": "koreader_page_stats_start_time_positive_chk", + "value": "\"koreader_page_stats\".\"start_time\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.koreader_progress_reset_devices": { + "name": "koreader_progress_reset_devices", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "converged_at": { + "name": "converged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "koreader_progress_reset_devices_reset_fk": { + "name": "koreader_progress_reset_devices_reset_fk", + "tableFrom": "koreader_progress_reset_devices", + "tableTo": "koreader_progress_resets", + "columnsFrom": ["user_id", "book_file_id"], + "columnsTo": ["user_id", "book_file_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_progress_reset_devices_user_id_book_file_id_device_id_pk": { + "name": "koreader_progress_reset_devices_user_id_book_file_id_device_id_pk", + "columns": ["user_id", "book_file_id", "device_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_progress_resets": { + "name": "koreader_progress_resets", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reset_at": { + "name": "reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_progress_resets_book_file_id_idx": { + "name": "koreader_progress_resets_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_progress_resets_user_id_users_id_fk": { + "name": "koreader_progress_resets_user_id_users_id_fk", + "tableFrom": "koreader_progress_resets", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "koreader_progress_resets_book_file_id_book_files_id_fk": { + "name": "koreader_progress_resets_book_file_id_book_files_id_fk", + "tableFrom": "koreader_progress_resets", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_progress_resets_user_id_book_file_id_pk": { + "name": "koreader_progress_resets_user_id_book_file_id_pk", + "columns": ["user_id", "book_file_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_unmatched_book_devices": { + "name": "koreader_unmatched_book_devices", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_unmatched_book_devices_user_device_idx": { + "name": "koreader_unmatched_book_devices_user_device_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_unmatched_book_devices_user_id_users_id_fk": { + "name": "koreader_unmatched_book_devices_user_id_users_id_fk", + "tableFrom": "koreader_unmatched_book_devices", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "koreader_unmatched_book_devices_user_hash_fk": { + "name": "koreader_unmatched_book_devices_user_hash_fk", + "tableFrom": "koreader_unmatched_book_devices", + "tableTo": "koreader_unmatched_books", + "columnsFrom": ["user_id", "hash"], + "columnsTo": ["user_id", "hash"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_unmatched_book_devices_user_id_hash_device_id_pk": { + "name": "koreader_unmatched_book_devices_user_id_hash_device_id_pk", + "columns": ["user_id", "hash", "device_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "koreader_unmatched_book_devices_hash_chk": { + "name": "koreader_unmatched_book_devices_hash_chk", + "value": "\"koreader_unmatched_book_devices\".\"hash\" ~ '^[0-9a-f]{32}$'" + } + }, + "isRLSEnabled": false + }, + "public.koreader_unmatched_books": { + "name": "koreader_unmatched_books", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "authors": { + "name": "authors", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_open": { + "name": "last_open", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'statistics'" + }, + "metadata_ambiguous": { + "name": "metadata_ambiguous", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_unmatched_books_user_seen_idx": { + "name": "koreader_unmatched_books_user_seen_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"last_seen_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_unmatched_books_user_id_users_id_fk": { + "name": "koreader_unmatched_books_user_id_users_id_fk", + "tableFrom": "koreader_unmatched_books", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_unmatched_books_user_id_hash_pk": { + "name": "koreader_unmatched_books_user_id_hash_pk", + "columns": ["user_id", "hash"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "koreader_unmatched_books_hash_chk": { + "name": "koreader_unmatched_books_hash_chk", + "value": "\"koreader_unmatched_books\".\"hash\" ~ '^[0-9a-f]{32}$'" + }, + "koreader_unmatched_books_source_chk": { + "name": "koreader_unmatched_books_source_chk", + "value": "\"koreader_unmatched_books\".\"source\" in ('current_file', 'file', 'statistics')" + } + }, + "isRLSEnabled": false + }, + "public.koreader_user_settings": { + "name": "koreader_user_settings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "default_file_naming_pattern": { + "name": "default_file_naming_pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "koreader_user_settings_user_id_users_id_fk": { + "name": "koreader_user_settings_user_id_users_id_fk", + "tableFrom": "koreader_user_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.koreader_users": { + "name": "koreader_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password_md5": { + "name": "password_md5", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "sync_enabled": { + "name": "sync_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "koreader_users_user_id_uidx": { + "name": "koreader_users_user_id_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_users_username_uidx": { + "name": "koreader_users_username_uidx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_users_user_id_users_id_fk": { + "name": "koreader_users_user_id_users_id_fk", + "tableFrom": "koreader_users", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.achievements": { + "name": "achievements", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "group_key": { + "name": "group_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "tier": { + "name": "tier", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "icon_name": { + "name": "icon_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "rarity": { + "name": "rarity", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "threshold": { + "name": "threshold", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hidden": { + "name": "hidden", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "achievements_category_sort_idx": { + "name": "achievements_category_sort_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "achievements_group_key_idx": { + "name": "achievements_group_key_idx", + "columns": [ + { + "expression": "group_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "achievements_category_chk": { + "name": "achievements_category_chk", + "value": "\"achievements\".\"category\" in ('reading', 'library', 'exploration', 'dedication', 'devices')" + }, + "achievements_rarity_chk": { + "name": "achievements_rarity_chk", + "value": "\"achievements\".\"rarity\" in ('common', 'rare', 'epic', 'legendary')" + }, + "achievements_tier_chk": { + "name": "achievements_tier_chk", + "value": "\"achievements\".\"tier\" is null or (\"achievements\".\"tier\" >= 1 and \"achievements\".\"tier\" <= 4)" + } + }, + "isRLSEnabled": false + }, + "public.user_achievements": { + "name": "user_achievements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "achievement_key": { + "name": "achievement_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "awarded_at": { + "name": "awarded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "context_json": { + "name": "context_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_achievements_user_key_uidx": { + "name": "user_achievements_user_key_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "achievement_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_achievements_user_id_idx": { + "name": "user_achievements_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_achievements_user_awarded_at_idx": { + "name": "user_achievements_user_awarded_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "awarded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_achievements_user_id_users_id_fk": { + "name": "user_achievements_user_id_users_id_fk", + "tableFrom": "user_achievements", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_achievements_achievement_key_achievements_key_fk": { + "name": "user_achievements_achievement_key_achievements_key_fk", + "tableFrom": "user_achievements", + "tableTo": "achievements", + "columnsFrom": ["achievement_key"], + "columnsTo": ["key"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.hardcover_book_state": { + "name": "hardcover_book_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "hardcover_book_id": { + "name": "hardcover_book_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hardcover_edition_id": { + "name": "hardcover_edition_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hardcover_user_book_id": { + "name": "hardcover_user_book_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hardcover_read_id": { + "name": "hardcover_read_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "match_method": { + "name": "match_method", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "match_error": { + "name": "match_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_synced_status": { + "name": "last_synced_status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "last_synced_progress": { + "name": "last_synced_progress", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "last_synced_rating": { + "name": "last_synced_rating", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_synced_started_at": { + "name": "last_synced_started_at", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "last_synced_finished_at": { + "name": "last_synced_finished_at", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_override": { + "name": "sync_override", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "sync_excluded": { + "name": "sync_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "hardcover_book_state_book_id_idx": { + "name": "hardcover_book_state_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "hardcover_book_state_user_id_users_id_fk": { + "name": "hardcover_book_state_user_id_users_id_fk", + "tableFrom": "hardcover_book_state", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "hardcover_book_state_book_id_books_id_fk": { + "name": "hardcover_book_state_book_id_books_id_fk", + "tableFrom": "hardcover_book_state", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "hardcover_book_state_user_book_uidx": { + "name": "hardcover_book_state_user_book_uidx", + "nullsNotDistinct": false, + "columns": ["user_id", "book_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.hardcover_user_settings": { + "name": "hardcover_user_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "api_token": { + "name": "api_token", + "type": "varchar(2048)", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "book_sync_mode": { + "name": "book_sync_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'all_eligible'" + }, + "auto_sync_on_status_change": { + "name": "auto_sync_on_status_change", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_sync_on_progress_update": { + "name": "auto_sync_on_progress_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_sync_on_rating_change": { + "name": "auto_sync_on_rating_change", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "privacy_setting_id": { + "name": "privacy_setting_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "hardcover_user_settings_user_id_users_id_fk": { + "name": "hardcover_user_settings_user_id_users_id_fk", + "tableFrom": "hardcover_user_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "hardcover_user_settings_user_id_unique": { + "name": "hardcover_user_settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.readwise_user_settings": { + "name": "readwise_user_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "api_token": { + "name": "api_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_synced_annotation_id": { + "name": "last_synced_annotation_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disabled_reason": { + "name": "disabled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "readwise_user_settings_user_id_users_id_fk": { + "name": "readwise_user_settings_user_id_users_id_fk", + "tableFrom": "readwise_user_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "readwise_user_settings_user_id_unique": { + "name": "readwise_user_settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.storygraph_book_state": { + "name": "storygraph_book_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "storygraph_book_id": { + "name": "storygraph_book_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "match_method": { + "name": "match_method", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "match_error": { + "name": "match_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_synced_status": { + "name": "last_synced_status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "last_synced_progress": { + "name": "last_synced_progress", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_override": { + "name": "sync_override", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "storygraph_book_state_book_id_idx": { + "name": "storygraph_book_state_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "storygraph_book_state_user_sync_override_idx": { + "name": "storygraph_book_state_user_sync_override_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sync_override", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "storygraph_book_state_user_sync_error_idx": { + "name": "storygraph_book_state_user_sync_error_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"storygraph_book_state\".\"sync_error\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "storygraph_book_state_user_id_users_id_fk": { + "name": "storygraph_book_state_user_id_users_id_fk", + "tableFrom": "storygraph_book_state", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "storygraph_book_state_book_id_books_id_fk": { + "name": "storygraph_book_state_book_id_books_id_fk", + "tableFrom": "storygraph_book_state", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "storygraph_book_state_user_book_uidx": { + "name": "storygraph_book_state_user_book_uidx", + "nullsNotDistinct": false, + "columns": ["user_id", "book_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.storygraph_user_settings": { + "name": "storygraph_user_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "session_cookie": { + "name": "session_cookie", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "remember_token": { + "name": "remember_token", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "book_sync_mode": { + "name": "book_sync_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'all_eligible'" + }, + "auto_sync_on_status_change": { + "name": "auto_sync_on_status_change", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_sync_on_progress_update": { + "name": "auto_sync_on_progress_update", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "storygraph_user_settings_user_id_users_id_fk": { + "name": "storygraph_user_settings_user_id_users_id_fk", + "tableFrom": "storygraph_user_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "storygraph_user_settings_user_id_unique": { + "name": "storygraph_user_settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.shared_reading_insight_view_sessions": { + "name": "shared_reading_insight_view_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "viewer_user_id": { + "name": "viewer_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "subject_user_id": { + "name": "subject_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sharing_level": { + "name": "sharing_level", + "type": "reading_insights_sharing_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "shared_insight_view_subject_created_idx": { + "name": "shared_insight_view_subject_created_idx", + "columns": [ + { + "expression": "subject_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "shared_insight_view_expires_idx": { + "name": "shared_insight_view_expires_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_reading_insight_view_sessions_viewer_user_id_users_id_fk": { + "name": "shared_reading_insight_view_sessions_viewer_user_id_users_id_fk", + "tableFrom": "shared_reading_insight_view_sessions", + "tableTo": "users", + "columnsFrom": ["viewer_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "shared_reading_insight_view_sessions_subject_user_id_users_id_fk": { + "name": "shared_reading_insight_view_sessions_subject_user_id_users_id_fk", + "tableFrom": "shared_reading_insight_view_sessions", + "tableTo": "users", + "columnsFrom": ["subject_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_preferences": { + "name": "user_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "up_user_category_idx": { + "name": "up_user_category_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_preferences_user_id_users_id_fk": { + "name": "user_preferences_user_id_users_id_fk", + "tableFrom": "user_preferences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.content_filter_type": { + "name": "content_filter_type", + "schema": "public", + "values": ["include", "exclude"] + }, + "public.library_access_level": { + "name": "library_access_level", + "schema": "public", + "values": ["viewer", "editor", "owner"] + }, + "public.reading_insights_sharing_level": { + "name": "reading_insights_sharing_level", + "schema": "public", + "values": ["private", "summary", "detailed"] + }, + "public.user_avatar_source": { + "name": "user_avatar_source", + "schema": "public", + "values": ["none", "external", "uploaded"] + }, + "public.opds_sort_order": { + "name": "opds_sort_order", + "schema": "public", + "values": ["recent", "title_asc", "title_desc", "author_asc", "author_desc", "series_asc", "series_desc"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/server/src/db/migrations/meta/_journal.json b/server/src/db/migrations/meta/_journal.json index ae3fab6d9..b2968394c 100644 --- a/server/src/db/migrations/meta/_journal.json +++ b/server/src/db/migrations/meta/_journal.json @@ -491,6 +491,20 @@ "when": 1787023974136, "tag": "0069_add_author_metadata_fields", "breakpoints": true + }, + { + "idx": 70, + "version": "7", + "when": 1787083687102, + "tag": "0070_add_koreader_progress_resets", + "breakpoints": true + }, + { + "idx": 71, + "version": "7", + "when": 1787086030151, + "tag": "0071_add_koreader_progress_reset_devices", + "breakpoints": true } ] } diff --git a/server/src/db/schema/koreader.ts b/server/src/db/schema/koreader.ts index 2e40cf0e4..b84957267 100644 --- a/server/src/db/schema/koreader.ts +++ b/server/src/db/schema/koreader.ts @@ -352,3 +352,69 @@ export const koreaderBookmarkLinks = pgTable( export type KoreaderBookmarkLink = typeof koreaderBookmarkLinks.$inferSelect; export type NewKoreaderBookmarkLink = typeof koreaderBookmarkLinks.$inferInsert; + +/** + * A deliberate server-side progress reset, recorded as a fact rather than as the absence of + * a reading_progress row. KOReader reads a missing position as "unknown" and answers by + * pushing its own sidecar back, so a reset that is only a deletion is undone the next time + * the book is opened on a device. + * + * A marker is live until it is retired, and serving it retires nothing: both clients can + * classify a reset as a backward sync and drop it without saying so, so being handed the + * position is no evidence the reader ever saw it. What retires a marker is a device that took + * the reset and then read on, or a web reader write, which is the way out for a device that + * never pulls. + * + * It has to be retired on that signal, and not merely held. The kosync pull carries no device + * identity, so a marker kept alive past its purpose is answered to every device, and one that + * already took the reset would be asked to jump back to the start on every sync. + * + * While a marker is live a KOReader push still records its per-device row, so nothing the + * device reports is lost, but it does not move shared progress. + */ +export const koreaderProgressResets = pgTable( + 'koreader_progress_resets', + { + userId: integer('user_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + bookFileId: integer('book_file_id') + .notNull() + .references(() => bookFiles.id, { onDelete: 'cascade' }), + resetAt: timestamp('reset_at', { withTimezone: true }).defaultNow().notNull(), + }, + (t) => [primaryKey({ columns: [t.userId, t.bookFileId] }), index('koreader_progress_resets_book_file_id_idx').on(t.bookFileId)], +); + +export type KoreaderProgressReset = typeof koreaderProgressResets.$inferSelect; +export type NewKoreaderProgressReset = typeof koreaderProgressResets.$inferInsert; + +/** + * Which devices have taken a given reset. The kosync pull carries no device identity, so a + * reset cannot be handed out per device, but the push does carry one and the hold is decided + * there. That asymmetry is what lets this be per-device without a plugin change: a device is + * held until its own push shows it at the start, and one device converging says nothing about + * the others. + * + * Rows hang off the marker and cascade with it, so re-arming a reset holds every device again. + */ +export const koreaderProgressResetDevices = pgTable( + 'koreader_progress_reset_devices', + { + userId: integer('user_id').notNull(), + bookFileId: integer('book_file_id').notNull(), + deviceId: varchar('device_id', { length: 100 }).notNull(), + convergedAt: timestamp('converged_at', { withTimezone: true }).defaultNow().notNull(), + }, + (t) => [ + primaryKey({ columns: [t.userId, t.bookFileId, t.deviceId] }), + foreignKey({ + columns: [t.userId, t.bookFileId], + foreignColumns: [koreaderProgressResets.userId, koreaderProgressResets.bookFileId], + name: 'koreader_progress_reset_devices_reset_fk', + }).onDelete('cascade'), + ], +); + +export type KoreaderProgressResetDevice = typeof koreaderProgressResetDevices.$inferSelect; +export type NewKoreaderProgressResetDevice = typeof koreaderProgressResetDevices.$inferInsert; diff --git a/server/src/modules/book/book.repository.test.ts b/server/src/modules/book/book.repository.test.ts index 450884d8b..584278755 100644 --- a/server/src/modules/book/book.repository.test.ts +++ b/server/src/modules/book/book.repository.test.ts @@ -1,7 +1,7 @@ import { BookRepository } from './book.repository'; import { sql } from 'drizzle-orm'; import { PgDialect } from 'drizzle-orm/pg-core'; -import { bookMetadata, books } from '../../db/schema'; +import { audiobookProgress, bookMetadata, books, koreaderDeviceProgress, koreaderProgressResets, readingProgress } from '../../db/schema'; function makeSelectChain(terminalMethod: string, terminalResult: T) { const chain: Record = { @@ -823,7 +823,9 @@ describe('BookRepository', () => { const onConflictDoUpdate = vi.fn().mockResolvedValue(undefined); const values = vi.fn().mockReturnValue({ onConflictDoUpdate }); const insert = vi.fn().mockReturnValue({ values }); - const db = { insert }; + const resetWhere = vi.fn().mockResolvedValue(undefined); + const del = vi.fn().mockReturnValue({ where: resetWhere }); + const db = { insert, delete: del }; const repo = new BookRepository(db as never); await repo.upsertProgress( @@ -1185,20 +1187,140 @@ describe('BookRepository', () => { await expect(repo.isKoboTwoWayProgressSyncEnabled(6)).resolves.toBe(false); }); - it('clears both reading_progress and audiobook_progress rows for a file id', async () => { - const readingWhere = vi.fn().mockResolvedValue(undefined); - const audioWhere = vi.fn().mockResolvedValue(undefined); - const del = vi.fn().mockReturnValueOnce({ where: readingWhere }).mockReturnValueOnce({ where: audioWhere }); - const db = { + function makeClearProgressDb(file: { bookId: number; primaryFileId: number } | undefined, koboState: Record | undefined) { + const deleted: unknown[] = []; + const inserted: unknown[] = []; + const updated: unknown[] = []; + const executed: unknown[] = []; + + const del = vi.fn().mockImplementation((table: unknown) => ({ + where: vi.fn().mockImplementation(() => { + deleted.push(table); + return Promise.resolve(undefined); + }), + })); + // Recorded at values() so it captures both a plain insert and a chained upsert. + const insert = vi.fn().mockImplementation((table: unknown) => ({ + values: vi.fn().mockImplementation((rows: unknown) => { + inserted.push({ table, rows }); + return Object.assign(Promise.resolve(undefined), { + onConflictDoUpdate: vi.fn().mockResolvedValue(undefined), + onConflictDoNothing: vi.fn().mockResolvedValue(undefined), + }); + }), + })); + const update = vi.fn().mockImplementation((table: unknown) => ({ + set: vi.fn().mockImplementation((patch: unknown) => ({ + where: vi.fn().mockImplementation(() => { + updated.push({ table, patch }); + return Promise.resolve(undefined); + }), + })), + })); + const execute = vi.fn().mockImplementation((statement: unknown) => { + executed.push(statement); + return Promise.resolve(undefined); + }); + + // clearFileProgress looks the file up outside the transaction; inside it, the Kobo reset + // reads the existing reading state and clearBookProgress lists the book's files. + const select = vi + .fn() + .mockImplementation(() => makeSelectChain('limit', koboState ? [koboState] : [])) + .mockImplementationOnce(() => makeSelectChain('limit', file ? [file] : [])); + + const tx = { delete: del, + insert, + update, + execute, + select: vi.fn().mockImplementation(() => makeSelectChain('limit', koboState ? [koboState] : [])), }; + const db = { + select, + delete: del, + insert, + update, + execute, + transaction: vi.fn().mockImplementation(async (cb: (t: unknown) => Promise) => cb(tx)), + }; + return { db, tx, deleted, inserted, updated, executed }; + } + + it('clears reading, audio and KOReader device rows for a file id and records the reset', async () => { + const { db, deleted, inserted } = makeClearProgressDb({ bookId: 3, primaryFileId: 99 }, undefined); const repo = new BookRepository(db as never); await repo.clearFileProgress(7, 99); - expect(del).toHaveBeenCalledTimes(2); - expect(readingWhere).toHaveBeenCalledTimes(1); - expect(audioWhere).toHaveBeenCalledTimes(1); + expect(deleted).toEqual([readingProgress, audiobookProgress, koreaderDeviceProgress, koreaderProgressResets]); + expect(inserted).toHaveLength(1); + expect(inserted[0]).toEqual({ table: koreaderProgressResets, rows: [{ userId: 7, bookFileId: 99 }] }); + }); + + it('leaves the Kobo bookmark alone when the cleared file is not the book primary', async () => { + const { db, updated } = makeClearProgressDb({ bookId: 3, primaryFileId: 42 }, { lastModifiedKobo: '2026-01-01T00:00:00Z' }); + const repo = new BookRepository(db as never); + + await repo.clearFileProgress(7, 99); + + expect(updated).toEqual([]); + }); + + it('winds the Kobo bookmark back to the start when the primary file is cleared', async () => { + const { db, updated, executed } = makeClearProgressDb({ bookId: 3, primaryFileId: 99 }, { lastModifiedKobo: '2026-01-01T00:00:00Z' }); + const repo = new BookRepository(db as never); + + await repo.clearFileProgress(7, 99); + + expect(updated).toHaveLength(1); + const patch = (updated[0] as { patch: Record }).patch; + expect(patch.currentBookmark).toEqual(expect.objectContaining({ ProgressPercent: 0 })); + expect(patch.statusInfo).toEqual(expect.objectContaining({ Status: 'ReadyToRead', TimesStartedReading: 0 })); + // A Kobo bookmark only wins on a strictly newer timestamp, so the reset has to advance past + // whatever the device last reported rather than stamping wall clock over it. + expect(String(patch.lastModifiedKobo) > '2026-01-01T00:00:00Z').toBe(true); + expect(executed).toHaveLength(1); + }); + + it('records a reset for every file of a book', async () => { + const { db, inserted, deleted } = makeClearProgressDb(undefined, undefined); + const repo = new BookRepository(db as never); + db.transaction = vi.fn().mockImplementation(async (cb: (t: unknown) => Promise) => + cb({ + ...db, + select: vi + .fn() + .mockImplementation(() => makeSelectChain('limit', [])) + .mockImplementationOnce(() => makeSelectChain('where', [{ id: 11 }, { id: 12 }])), + }), + ) as never; + + await repo.clearBookProgress(7, 3); + + expect(deleted).toEqual([readingProgress, audiobookProgress, koreaderDeviceProgress, koreaderProgressResets]); + expect(inserted).toEqual([ + { + table: koreaderProgressResets, + rows: [ + { userId: 7, bookFileId: 11 }, + { userId: 7, bookFileId: 12 }, + ], + }, + ]); + }); + + it('retires a pending reset when the web reader writes progress back', async () => { + const onConflictDoUpdate = vi.fn().mockResolvedValue(undefined); + const insert = vi.fn().mockReturnValue({ values: vi.fn().mockReturnValue({ onConflictDoUpdate }) }); + const resetWhere = vi.fn().mockResolvedValue(undefined); + const del = vi.fn().mockReturnValue({ where: resetWhere }); + const repo = new BookRepository({ insert, delete: del } as never); + + await repo.upsertProgress(5, 9, 'epubcfi(/6/2)', null, 40); + + expect(del).toHaveBeenCalledWith(koreaderProgressResets); + expect(resetWhere).toHaveBeenCalledTimes(1); }); describe('temporal jump buckets', () => { diff --git a/server/src/modules/book/book.repository.ts b/server/src/modules/book/book.repository.ts index 8900832a9..c3c7103dd 100644 --- a/server/src/modules/book/book.repository.ts +++ b/server/src/modules/book/book.repository.ts @@ -44,6 +44,8 @@ import { koboReadingStates, koboSnapshotBooks, koboSyncSettings, + koreaderDeviceProgress, + koreaderProgressResets, libraries, narrators, audiobookProgress, @@ -58,6 +60,7 @@ type DbTransaction = Parameters[0]>[0]; type MetadataUpdateExecutor = Pick; type MetadataReadExecutor = Pick; type JsonObj = Record; +type BookRepositoryTx = Parameters['transaction']>[0]>[0]; type CollapsedRawRow = { id: number; @@ -2059,6 +2062,11 @@ export class BookRepository { updatedAt: now, }, }); + + // Reading in BookOrbit is fresher intent than the reset that came before it, and it is + // the way out for a device that never pulls and would otherwise have every push held + // back indefinitely. + await this.db.delete(koreaderProgressResets).where(and(eq(koreaderProgressResets.userId, userId), eq(koreaderProgressResets.bookFileId, fileId))); } async syncKoboReadingStateFromProgress( @@ -2208,18 +2216,117 @@ export class BookRepository { } async clearFileProgress(userId: number, fileId: number): Promise { - await this.db.delete(readingProgress).where(and(eq(readingProgress.userId, userId), eq(readingProgress.bookFileId, fileId))); - await this.db.delete(audiobookProgress).where(and(eq(audiobookProgress.userId, userId), eq(audiobookProgress.currentFileId, fileId))); + const [file] = await this.db + .select({ bookId: bookFiles.bookId, primaryFileId: books.primaryFileId }) + .from(bookFiles) + .innerJoin(books, eq(books.id, bookFiles.bookId)) + .where(eq(bookFiles.id, fileId)) + .limit(1); + + await this.db.transaction(async (tx) => { + await tx.delete(readingProgress).where(and(eq(readingProgress.userId, userId), eq(readingProgress.bookFileId, fileId))); + await tx.delete(audiobookProgress).where(and(eq(audiobookProgress.userId, userId), eq(audiobookProgress.currentFileId, fileId))); + await this.clearExternalDeviceProgress(tx, userId, [fileId]); + // Kobo tracks the book through its primary file, so clearing a secondary file leaves + // the device's bookmark alone. + if (file && file.primaryFileId === fileId) await this.resetKoboReadingState(tx, userId, file.bookId); + }); } async clearBookProgress(userId: number, bookId: number): Promise { - const fileIds = this.db.select({ id: bookFiles.id }).from(bookFiles).where(eq(bookFiles.bookId, bookId)); await this.db.transaction(async (tx) => { + const files = await tx.select({ id: bookFiles.id }).from(bookFiles).where(eq(bookFiles.bookId, bookId)); + const fileIds = files.map((file) => file.id); await tx.delete(readingProgress).where(and(eq(readingProgress.userId, userId), inArray(readingProgress.bookFileId, fileIds))); await tx.delete(audiobookProgress).where(and(eq(audiobookProgress.userId, userId), eq(audiobookProgress.bookId, bookId))); + await this.clearExternalDeviceProgress(tx, userId, fileIds); + await this.resetKoboReadingState(tx, userId, bookId); }); } + /** + * Drops the KOReader per-device positions for these files and records the reset, so the + * cleared position survives contact with a device. Deleting the shared row alone is not + * enough: the sync path would keep serving the device row it left behind, and a device + * that has not been told about the reset would push its own position straight back. + * + * Reading statistics are deliberately untouched. Clearing a position says nothing about + * the time already spent in the book; only the full reading-state reset discards that. + */ + private async clearExternalDeviceProgress(tx: BookRepositoryTx, userId: number, fileIds: number[]): Promise { + if (fileIds.length === 0) return; + await tx + .delete(koreaderDeviceProgress) + .where( + and( + eq(koreaderDeviceProgress.userId, userId), + inArray(koreaderDeviceProgress.bookFileId, fileIds), + eq(koreaderDeviceProgress.orphaned, false), + ), + ); + // Deleted rather than upserted so a repeat reset cascades away the per-device convergence + // rows: every device has to take the new reset, including ones that took the last one. + await tx + .delete(koreaderProgressResets) + .where(and(eq(koreaderProgressResets.userId, userId), inArray(koreaderProgressResets.bookFileId, fileIds))); + await tx.insert(koreaderProgressResets).values(fileIds.map((bookFileId) => ({ userId, bookFileId }))); + } + + /** + * Winds the Kobo bookmark back to the start of the book, matching what the full reading + * state reset does. Without this a Kobo device resumes from its own bookmark and reports + * that position back, undoing the reset the same way KOReader does. + */ + private async resetKoboReadingState(tx: BookRepositoryTx, userId: number, bookId: number): Promise { + const [existing] = await tx + .select({ + lastModifiedKobo: koboReadingStates.lastModifiedKobo, + priorityTimestamp: koboReadingStates.priorityTimestamp, + currentBookmark: koboReadingStates.currentBookmark, + statistics: koboReadingStates.statistics, + statusInfo: koboReadingStates.statusInfo, + }) + .from(koboReadingStates) + .where(and(eq(koboReadingStates.userId, userId), eq(koboReadingStates.bookId, bookId))) + .limit(1); + if (!existing) return; + + const now = new Date(); + const existingBookmark = this.asJsonObj(existing.currentBookmark); + const existingStatusInfo = this.asJsonObj(existing.statusInfo); + const nowIso = advanceIsoTimestamp( + now, + existing.lastModifiedKobo, + existing.priorityTimestamp, + typeof existingBookmark?.LastModified === 'string' ? existingBookmark.LastModified : null, + typeof existingStatusInfo?.LastModified === 'string' ? existingStatusInfo.LastModified : null, + ); + + await tx + .update(koboReadingStates) + .set({ + lastModifiedKobo: nowIso, + priorityTimestamp: nowIso, + currentBookmark: { LastModified: nowIso, ProgressPercent: 0 }, + statistics: { ...(this.asJsonObj(existing.statistics) ?? {}), LastModified: nowIso }, + statusInfo: { ...(existingStatusInfo ?? {}), LastModified: nowIso, Status: 'ReadyToRead', TimesStartedReading: 0 }, + updatedAt: now, + }) + .where(and(eq(koboReadingStates.userId, userId), eq(koboReadingStates.bookId, bookId))); + + await tx.execute(sql` + UPDATE ${koboSnapshotBooks} AS sb + SET synced = false, + is_new = false + FROM ${koboLibrarySnapshots} AS snap + WHERE snap.id = sb.snapshot_id + AND snap.user_id = ${userId} + AND sb.book_id = ${bookId} + AND sb.pending_delete = false + AND sb.removed_by_device = false + `); + } + async findAudioProgress(userId: number, bookId: number) { const [row] = await this.db .select() diff --git a/server/src/modules/koreader/dto/index.ts b/server/src/modules/koreader/dto/index.ts index 252af3ca0..db569e5de 100644 --- a/server/src/modules/koreader/dto/index.ts +++ b/server/src/modules/koreader/dto/index.ts @@ -7,4 +7,5 @@ export * from './koreader-link.dto'; export * from './koreader-package.dto'; export * from './koreader-plugin.dto'; export * from './koreader-progress.dto'; +export * from './koreader-reset-hold.dto'; export * from './koreader-user.dto'; diff --git a/server/src/modules/koreader/dto/koreader-reset-hold.dto.ts b/server/src/modules/koreader/dto/koreader-reset-hold.dto.ts new file mode 100644 index 000000000..a5c1bcb1a --- /dev/null +++ b/server/src/modules/koreader/dto/koreader-reset-hold.dto.ts @@ -0,0 +1,8 @@ +import { IsNotEmpty, IsString, MaxLength } from 'class-validator'; + +export class ReleaseResetHoldDto { + @IsString() + @IsNotEmpty() + @MaxLength(100) + deviceId!: string; +} diff --git a/server/src/modules/koreader/koreader-plugin.service.ts b/server/src/modules/koreader/koreader-plugin.service.ts index 3bb9bd103..982f86d6b 100644 --- a/server/src/modules/koreader/koreader-plugin.service.ts +++ b/server/src/modules/koreader/koreader-plugin.service.ts @@ -238,7 +238,7 @@ export class KoreaderPluginService { }); this.logger.log( - `[${BULK_PROGRESS_EVENT}] [end] userId=${user.id} deviceId=${dto.deviceId.slice(0, 8)} durationMs=${Date.now() - startedAtMs} accepted=${results.length} shared=${applied.shared} stale=${applied.stale} unmatched=${unmatched.length} - bulk progress completed`, + `[${BULK_PROGRESS_EVENT}] [end] userId=${user.id} deviceId=${dto.deviceId.slice(0, 8)} durationMs=${Date.now() - startedAtMs} accepted=${results.length} shared=${applied.shared} stale=${applied.stale} held=${applied.held} unmatched=${unmatched.length} - bulk progress completed`, ); return { results, unmatched }; diff --git a/server/src/modules/koreader/koreader.controller.ts b/server/src/modules/koreader/koreader.controller.ts index 9600dd465..82936955f 100644 --- a/server/src/modules/koreader/koreader.controller.ts +++ b/server/src/modules/koreader/koreader.controller.ts @@ -30,6 +30,7 @@ import { DownloadPluginPackageDto, KoreaderDeviceParamDto, KoreaderSaveProgressDto, + ReleaseResetHoldDto, LinkKoreaderUnmatchedBookDto, TestConnectionDto, UpdateKoreaderDeviceDto, @@ -215,6 +216,13 @@ export class KoreaderController { return this.koreaderService.getBookProgress(user.id, bookId); } + @RequirePermission(Permission.KoreaderSync) + @Post('books/:bookId/reset-hold/release') + async releaseResetHold(@CurrentUser() user: RequestUser, @Param('bookId', ParseIntPipe) bookId: number, @Body() dto: ReleaseResetHoldDto) { + await this.koreaderService.releaseResetHold(user.id, bookId, dto.deviceId); + return { released: true }; + } + @RequirePermission(Permission.KoreaderSync) @Get('plugin-package') async downloadPluginPackage(@CurrentUser() user: RequestUser, @Query() query: DownloadPluginPackageDto, @Res() reply: FastifyReply) { diff --git a/server/src/modules/koreader/koreader.repository.test.ts b/server/src/modules/koreader/koreader.repository.test.ts index 94873c122..ad999fb1c 100644 --- a/server/src/modules/koreader/koreader.repository.test.ts +++ b/server/src/modules/koreader/koreader.repository.test.ts @@ -45,6 +45,49 @@ describe('KoreaderRepository', () => { repo = new KoreaderRepository(db as never); }); + describe('progress resets', () => { + it('returns the reset timestamp when one is outstanding', async () => { + const resetAt = new Date('2026-02-02T12:00:00.000Z'); + db.select.mockReturnValue(makeQueryChain([{ resetAt }])); + + await expect(repo.getProgressReset(10, 7)).resolves.toEqual(resetAt); + }); + + it('returns null when no reset is outstanding', async () => { + db.select.mockReturnValue(makeQueryChain([])); + + await expect(repo.getProgressReset(10, 7)).resolves.toBeNull(); + }); + + it('records convergence per device rather than retiring the marker', async () => { + const onConflictDoNothing = vi.fn().mockResolvedValue(undefined); + const values = vi.fn().mockReturnValue({ onConflictDoNothing }); + db.insert.mockReturnValue({ values }); + + await repo.recordResetConvergence(10, 7, 'device-1'); + + // One device taking the reset says nothing about the others, so the marker stays. + expect(values).toHaveBeenCalledWith({ userId: 7, bookFileId: 10, deviceId: 'device-1' }); + expect(db.delete).not.toHaveBeenCalled(); + }); + + it('reads back the devices that have taken the reset', async () => { + db.select.mockReturnValue(makeQueryChain([{ deviceId: 'device-1' }, { deviceId: 'device-2' }])); + + await expect(repo.getConvergedResetDeviceIds(10, 7)).resolves.toEqual(new Set(['device-1', 'device-2'])); + }); + + it('retires a marker when the outcome is settled for every device', async () => { + const where = vi.fn().mockResolvedValue(undefined); + db.delete.mockReturnValue({ where }); + + await repo.clearProgressReset(10, 7); + + expect(db.delete).toHaveBeenCalledTimes(1); + expect(where).toHaveBeenCalledTimes(1); + }); + }); + describe('resolveBookFileByHash', () => { it('short-circuits when accessible libraries are empty', async () => { await expect(repo.resolveBookFileByHash('hash', [])).resolves.toBeNull(); diff --git a/server/src/modules/koreader/koreader.repository.ts b/server/src/modules/koreader/koreader.repository.ts index 4af0a6ba5..9df5abf95 100644 --- a/server/src/modules/koreader/koreader.repository.ts +++ b/server/src/modules/koreader/koreader.repository.ts @@ -650,6 +650,122 @@ export class KoreaderRepository { return byFile; } + /** + * The live reset marker for a file, or null when none is outstanding. A live marker means + * the user cleared this file's position server-side and no device has been told yet, so it + * outranks every stored position regardless of timestamps: a device that pushed after the + * reset carries a fresh push clock but a pre-reset position, and the two are + * indistinguishable on the wire. + */ + async getProgressReset(bookFileId: number, userId: number) { + const [row] = await this.db + .select({ resetAt: schema.koreaderProgressResets.resetAt }) + .from(schema.koreaderProgressResets) + .where(and(eq(schema.koreaderProgressResets.bookFileId, bookFileId), eq(schema.koreaderProgressResets.userId, userId))) + .limit(1); + return row?.resetAt ?? null; + } + + async getProgressResetsForFiles(bookFileIds: number[], userId: number) { + const byFile = new Map(); + for (const batch of chunk([...new Set(bookFileIds)], BATCH_QUERY_SIZE)) { + const rows = await this.db + .select({ bookFileId: schema.koreaderProgressResets.bookFileId, resetAt: schema.koreaderProgressResets.resetAt }) + .from(schema.koreaderProgressResets) + .where(and(inArray(schema.koreaderProgressResets.bookFileId, batch), eq(schema.koreaderProgressResets.userId, userId))); + for (const row of rows) byFile.set(row.bookFileId, row.resetAt); + } + return byFile; + } + + /** + * The file a book's KOReader progress hangs off, in the shape the shared-progress path needs. + * Resolved through the book's primary file, matching findBookFileIdByBookId: the book page + * reports one file's holds, so the release action has to act on that same file rather than + * whichever one a marker happens to be found on first. + */ + async findProgressBookFileByBookId(bookId: number, accessibleLibraryIds: number[] | null) { + if (accessibleLibraryIds !== null && accessibleLibraryIds.length === 0) return null; + const [row] = await this.db + .select({ + id: schema.bookFiles.id, + bookId: schema.bookFiles.bookId, + libraryId: schema.books.libraryId, + format: schema.bookFiles.format, + }) + .from(schema.bookFiles) + .innerJoin(schema.books, eq(schema.books.id, schema.bookFiles.bookId)) + .where( + and( + eq(schema.books.id, bookId), + eq(schema.books.primaryFileId, schema.bookFiles.id), + // Scoped the same way every other entry point into this module is. A device progress + // row outlives the library grant that created it, so ownership of the row is not + // ownership of the book, and this path writes. + accessibleLibraryIds === null ? undefined : inArray(schema.books.libraryId, accessibleLibraryIds), + ), + ) + .limit(1); + return row ?? null; + } + + async getDeviceProgressForDevice(bookFileId: number, userId: number, deviceId: string) { + const [row] = await this.db + .select() + .from(schema.koreaderDeviceProgress) + .where( + and( + eq(schema.koreaderDeviceProgress.bookFileId, bookFileId), + eq(schema.koreaderDeviceProgress.userId, userId), + eq(schema.koreaderDeviceProgress.deviceId, deviceId), + eq(schema.koreaderDeviceProgress.orphaned, false), + ), + ) + .limit(1); + return row ?? null; + } + + /** Device ids that have taken the outstanding reset for this file. */ + async getConvergedResetDeviceIds(bookFileId: number, userId: number) { + const rows = await this.db + .select({ deviceId: schema.koreaderProgressResetDevices.deviceId }) + .from(schema.koreaderProgressResetDevices) + .where(and(eq(schema.koreaderProgressResetDevices.bookFileId, bookFileId), eq(schema.koreaderProgressResetDevices.userId, userId))); + return new Set(rows.map((row) => row.deviceId)); + } + + async getConvergedResetDeviceIdsForFiles(bookFileIds: number[], userId: number) { + const byFile = new Map>(); + for (const batch of chunk([...new Set(bookFileIds)], BATCH_QUERY_SIZE)) { + const rows = await this.db + .select({ bookFileId: schema.koreaderProgressResetDevices.bookFileId, deviceId: schema.koreaderProgressResetDevices.deviceId }) + .from(schema.koreaderProgressResetDevices) + .where(and(inArray(schema.koreaderProgressResetDevices.bookFileId, batch), eq(schema.koreaderProgressResetDevices.userId, userId))); + for (const row of rows) { + const existing = byFile.get(row.bookFileId); + if (existing) existing.add(row.deviceId); + else byFile.set(row.bookFileId, new Set([row.deviceId])); + } + } + return byFile; + } + + /** + * Records that a device has landed on the reset position. Deliberately per-device and not a + * retirement of the marker: another device may still be holding a pre-reset position, and it + * has to keep being held until it takes the reset itself. + */ + async recordResetConvergence(bookFileId: number, userId: number, deviceId: string) { + await this.db.insert(schema.koreaderProgressResetDevices).values({ userId, bookFileId, deviceId }).onConflictDoNothing(); + } + + /** Retires a marker whose outcome is settled for every device, cascading the per-device rows. */ + async clearProgressReset(bookFileId: number, userId: number) { + await this.db + .delete(schema.koreaderProgressResets) + .where(and(eq(schema.koreaderProgressResets.bookFileId, bookFileId), eq(schema.koreaderProgressResets.userId, userId))); + } + async getDevicesList(userId: number) { const result = await this.db.execute<{ device: string; diff --git a/server/src/modules/koreader/koreader.service.test.ts b/server/src/modules/koreader/koreader.service.test.ts index e326f2526..6c26ffb0d 100644 --- a/server/src/modules/koreader/koreader.service.test.ts +++ b/server/src/modules/koreader/koreader.service.test.ts @@ -60,6 +60,14 @@ describe('KoreaderService', () => { getLatestDeviceProgress: ReturnType; getDeviceProgressForFiles: ReturnType; getReadingProgressUpdatedAtForFiles: ReturnType; + getProgressReset: ReturnType; + getProgressResetsForFiles: ReturnType; + getConvergedResetDeviceIds: ReturnType; + getConvergedResetDeviceIdsForFiles: ReturnType; + recordResetConvergence: ReturnType; + clearProgressReset: ReturnType; + findProgressBookFileByBookId: ReturnType; + getDeviceProgressForDevice: ReturnType; getReadingProgress: ReturnType; getTotalSyncedBooks: ReturnType; getDevicesList: ReturnType; @@ -132,6 +140,14 @@ describe('KoreaderService', () => { getLatestDeviceProgress: vi.fn(), getDeviceProgressForFiles: vi.fn().mockResolvedValue(new Map()), getReadingProgressUpdatedAtForFiles: vi.fn().mockResolvedValue(new Map()), + getProgressReset: vi.fn().mockResolvedValue(null), + getProgressResetsForFiles: vi.fn().mockResolvedValue(new Map()), + getConvergedResetDeviceIds: vi.fn().mockResolvedValue(new Set()), + getConvergedResetDeviceIdsForFiles: vi.fn().mockResolvedValue(new Map()), + recordResetConvergence: vi.fn().mockResolvedValue(undefined), + clearProgressReset: vi.fn().mockResolvedValue(undefined), + findProgressBookFileByBookId: vi.fn().mockResolvedValue(null), + getDeviceProgressForDevice: vi.fn().mockResolvedValue(null), getReadingProgress: vi.fn(), getTotalSyncedBooks: vi.fn(), getDevicesList: vi.fn().mockResolvedValue([]), @@ -557,7 +573,7 @@ describe('KoreaderService', () => { expect.objectContaining({ bookFileId: 11, syncTimestamp: null }), ]); expect(mockRepo.upsertReadingProgress).toHaveBeenCalledTimes(2); - expect(result).toEqual({ shared: 2, stale: 0 }); + expect(result).toEqual({ shared: 2, stale: 0, held: 0 }); }); it('routes each entry by its own format within a single sweep', async () => { @@ -586,7 +602,7 @@ describe('KoreaderService', () => { }); it('issues no query for an empty entry list', async () => { - await expect(service.applyBulkProgress(7, [], device)).resolves.toEqual({ shared: 0, stale: 0 }); + await expect(service.applyBulkProgress(7, [], device)).resolves.toEqual({ shared: 0, stale: 0, held: 0 }); expect(mockRepo.getDeviceProgressForFiles).not.toHaveBeenCalled(); expect(mockRepo.upsertDeviceProgressMany).not.toHaveBeenCalled(); @@ -602,7 +618,7 @@ describe('KoreaderService', () => { expect(mockRepo.upsertDeviceProgressMany).toHaveBeenCalledTimes(1); expect(mockRepo.upsertReadingProgress).not.toHaveBeenCalled(); expect(mockChapterExtractor.extractAndStoreChapters).not.toHaveBeenCalled(); - expect(result).toEqual({ shared: 0, stale: 1 }); + expect(result).toEqual({ shared: 0, stale: 1, held: 0 }); }); it('treats the web reader position as newer known state', async () => { @@ -610,7 +626,7 @@ describe('KoreaderService', () => { const result = await service.applyBulkProgress(7, [{ bookFile: bookFile(10), percentage: 0.2, timestamp: 1700000000 }], device); - expect(result).toEqual({ shared: 0, stale: 1 }); + expect(result).toEqual({ shared: 0, stale: 1, held: 0 }); }); it('never treats an entry without a device timestamp as stale', async () => { @@ -620,7 +636,7 @@ describe('KoreaderService', () => { const result = await service.applyBulkProgress(7, [{ bookFile: bookFile(10), percentage: 0.2 }], device); - expect(result).toEqual({ shared: 1, stale: 0 }); + expect(result).toEqual({ shared: 1, stale: 0, held: 0 }); }); it('lets a later entry for the same file observe the position this batch already applied', async () => { @@ -633,7 +649,7 @@ describe('KoreaderService', () => { device, ); - expect(result).toEqual({ shared: 1, stale: 1 }); + expect(result).toEqual({ shared: 1, stale: 1, held: 0 }); // One row per file: the last entry wins, exactly as a sequential upsert would leave it. expect(mockRepo.upsertDeviceProgressMany.mock.calls[0]![0]).toEqual([expect.objectContaining({ bookFileId: 10, percentage: 0.3 })]); }); @@ -670,6 +686,248 @@ describe('KoreaderService', () => { }); }); + describe('progress resets', () => { + const resetAt = new Date('2026-02-02T12:00:00.000Z'); + const device = { device: 'Kobo Libra 2', deviceId: 'device-a' }; + + function bookFile(id: number, bookId = id * 10, format: string | null = 'epub') { + return { id, bookId, libraryId: 1, format }; + } + + it('answers a pending reset with a real start position, not an empty one', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + + const pulled = await service.getProgress(7, 'doc-hash'); + + // Stock kosync feeds progress straight to GotoXPointer with no percentage fallback, so an + // empty string leaves the reader exactly where it was. + expect(pulled).toEqual(expect.objectContaining({ percentage: 0, progress: '/body/DocFragment[1]/body', device: 'web' })); + expect(mockRepo.getLatestDeviceProgress).not.toHaveBeenCalled(); + }); + + it('sends the page form of the start position for a paged document', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'cbz' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + + expect((await service.getProgress(7, 'doc-hash'))?.progress).toBe('1'); + }); + + it('stamps each delivery as current so the client reads it as a forward sync', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + + const pulled = await service.getProgress(7, 'doc-hash'); + + // Both clients compare this against their last page turn and default backward syncs to + // disabled, so the original reset time would be dropped without ever prompting. + expect(pulled!.timestamp).toBeGreaterThan(Math.floor(resetAt.getTime() / 1000)); + }); + + it('keeps the reset after serving it, because delivery is not application', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + + await service.getProgress(7, 'doc-hash'); + + expect(mockRepo.clearProgressReset).not.toHaveBeenCalled(); + expect(mockRepo.recordResetConvergence).not.toHaveBeenCalled(); + }); + + it('records a stale push while a reset is pending but holds it out of shared progress', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + + await service.saveProgress(7, { document: 'doc-hash', percentage: 0.42, progress: '/body/DocFragment[6]/body', device_id: 'device-1' }); + + expect(mockRepo.upsertDeviceProgress).toHaveBeenCalledTimes(1); + expect(mockRepo.upsertReadingProgress).not.toHaveBeenCalled(); + expect(mockBookService.autoUpdateReadStatusForProgress).not.toHaveBeenCalled(); + }); + + it('lets a push that reports the start position through, and marks that device converged', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + + await service.saveProgress(7, { document: 'doc-hash', percentage: 0.004, progress: '/body/DocFragment[1]/body', device_id: 'device-1' }); + + expect(mockRepo.recordResetConvergence).toHaveBeenCalledWith(10, 7, 'device-1'); + expect(mockRepo.upsertReadingProgress).toHaveBeenCalledTimes(1); + }); + + it('judges convergence on the position, so page one of a short book still counts', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'cbz' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + + // Page 1 of a hundred-page comic reports 1%, which no threshold meaning "the start of a + // long book" could accept, and that device would be held forever. + await service.saveProgress(7, { document: 'doc-hash', percentage: 0.01, progress: '1', device_id: 'device-1' }); + + expect(mockRepo.recordResetConvergence).toHaveBeenCalledWith(10, 7, 'device-1'); + }); + + it('does not accept an early position that is not the start', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'cbz' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + + await service.saveProgress(7, { document: 'doc-hash', percentage: 0.008, progress: '9', device_id: 'device-1' }); + + expect(mockRepo.recordResetConvergence).not.toHaveBeenCalled(); + expect(mockRepo.upsertReadingProgress).not.toHaveBeenCalled(); + }); + + it('retires the reset once a converged device reads on, so the pull stops serving it', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + mockRepo.getConvergedResetDeviceIds.mockResolvedValue(new Set(['device-1'])); + + await service.saveProgress(7, { document: 'doc-hash', percentage: 0.5, progress: '/body/DocFragment[9]/body', device_id: 'device-1' }); + + // The pull is anonymous, so a marker left alive past its purpose is answered to this + // device too, and it would be sent back to the start on every sync. + expect(mockRepo.clearProgressReset).toHaveBeenCalledWith(10, 7); + }); + + it('does not retire the reset while a converged device is still sitting at the start', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + mockRepo.getConvergedResetDeviceIds.mockResolvedValue(new Set(['device-1'])); + + await service.saveProgress(7, { document: 'doc-hash', percentage: 0, progress: '/body/DocFragment[1]/body', device_id: 'device-1' }); + + expect(mockRepo.clearProgressReset).not.toHaveBeenCalled(); + }); + + it('does not treat a single-fragment EPUB position as the start on percentage alone', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + + // Every position in a one-spine EPUB is inside DocFragment[1], so the fragment alone + // would call this device converged wherever it happens to be sitting. + await service.saveProgress(7, { document: 'doc-hash', percentage: 0.5, progress: '/body/DocFragment[1]/body/p[80]', device_id: 'device-1' }); + + expect(mockRepo.recordResetConvergence).not.toHaveBeenCalled(); + expect(mockRepo.upsertReadingProgress).not.toHaveBeenCalled(); + }); + + it('keeps holding a second device after the first one has converged', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + mockRepo.getConvergedResetDeviceIds.mockResolvedValue(new Set(['device-1'])); + + await service.saveProgress(7, { document: 'doc-hash', percentage: 0.42, progress: '/body/DocFragment[6]/body', device_id: 'device-2' }); + + expect(mockRepo.upsertReadingProgress).not.toHaveBeenCalled(); + }); + + it('lets a device that already converged push freely', async () => { + mockRepo.resolveBookFileByHash.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + mockRepo.getConvergedResetDeviceIds.mockResolvedValue(new Set(['device-1'])); + + await service.saveProgress(7, { document: 'doc-hash', percentage: 0.55, progress: '/body/DocFragment[9]/body', device_id: 'device-1' }); + + expect(mockRepo.upsertReadingProgress).toHaveBeenCalledTimes(1); + }); + + it('holds swept positions behind a pending reset and reports them separately from stale ones', async () => { + mockRepo.getProgressResetsForFiles.mockResolvedValue(new Map([[10, resetAt]])); + + const result = await service.applyBulkProgress( + 7, + [ + { bookFile: bookFile(10), percentage: 0.42, progress: '/body/DocFragment[6]/body' }, + { bookFile: bookFile(11), percentage: 0.3 }, + ], + device, + ); + + expect(result).toEqual({ shared: 1, stale: 0, held: 1 }); + // The device row is still written for the held file: nothing the device reported is lost, + // it just does not move the book. + expect(mockRepo.upsertDeviceProgressMany.mock.calls[0]![0]).toHaveLength(2); + expect(mockRepo.upsertReadingProgress).toHaveBeenCalledTimes(1); + }); + + it('does not report a held device as KOReader-latest', async () => { + mockRepo.findBookFileIdByBookId.mockResolvedValue(10); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + mockRepo.getBookProgressForDashboard.mockResolvedValue({ + deviceProgress: [ + { device: 'Kobo Libra', deviceId: 'device-1', percentage: 0.42, chapterIndex: 3, updatedAt: new Date('2026-02-02T13:00:00.000Z') }, + ], + readingProgress: null, + }); + mockRepo.getChapters.mockResolvedValue([]); + mockRepo.getLastFileWriteTime.mockResolvedValue(null); + + const info = await service.getBookProgress(7, 20); + + expect(info?.canonicalSource).toBe('web_reader'); + expect(info?.canonicalPercentage).toBe(0); + // Reported rather than hidden, so the hold is legible instead of looking like a lost sync. + expect(info?.heldByReset).toEqual([expect.objectContaining({ deviceId: 'device-1', percentage: 42 })]); + }); + + it('counts a converged device as canonical again even while the marker is still live', async () => { + mockRepo.findBookFileIdByBookId.mockResolvedValue(10); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + mockRepo.getConvergedResetDeviceIds.mockResolvedValue(new Set(['device-1'])); + mockRepo.getBookProgressForDashboard.mockResolvedValue({ + deviceProgress: [ + { device: 'Kobo Libra', deviceId: 'device-1', percentage: 0.2, chapterIndex: 1, updatedAt: new Date('2026-02-02T13:00:00.000Z') }, + ], + readingProgress: null, + }); + mockRepo.getChapters.mockResolvedValue([]); + mockRepo.getLastFileWriteTime.mockResolvedValue(null); + + const info = await service.getBookProgress(7, 20); + + expect(info?.canonicalSource).toBe('koreader'); + expect(info?.heldByReset).toEqual([]); + }); + + it('releasing a hold accepts that device position and retires the reset', async () => { + mockRepo.findProgressBookFileByBookId.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getDeviceProgressForDevice.mockResolvedValue({ percentage: 0.42, progress: '/body/DocFragment[6]/body', syncTimestamp: null }); + mockRepo.getProgressReset.mockResolvedValue(resetAt); + + await service.releaseResetHold(7, 20, 'device-1'); + + // Recording convergence instead would leave the marker live, and the next pull would ask + // this same device to go back to the start. + expect(mockRepo.clearProgressReset).toHaveBeenCalledWith(10, 7); + expect(mockRepo.upsertReadingProgress).toHaveBeenCalledTimes(1); + }); + + it('refuses to release a hold for a device with no recorded position', async () => { + mockRepo.findProgressBookFileByBookId.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getDeviceProgressForDevice.mockResolvedValue(null); + + await expect(service.releaseResetHold(7, 20, 'device-1')).rejects.toThrow(NotFoundException); + }); + + it('answers a release with no pending reset as not found rather than a database error', async () => { + mockRepo.findProgressBookFileByBookId.mockResolvedValue({ id: 10, bookId: 20, libraryId: 1, format: 'epub' }); + mockRepo.getDeviceProgressForDevice.mockResolvedValue({ percentage: 0.42, progress: '/body/DocFragment[6]/body', syncTimestamp: null }); + mockRepo.getProgressReset.mockResolvedValue(null); + + await expect(service.releaseResetHold(7, 20, 'device-1')).rejects.toThrow(NotFoundException); + expect(mockRepo.upsertReadingProgress).not.toHaveBeenCalled(); + }); + + it('resolves the release target only within libraries the user can still reach', async () => { + mockRepo.getAccessibleLibraryIds.mockResolvedValue([3]); + mockRepo.findProgressBookFileByBookId.mockResolvedValue(null); + + // A device progress row outlives the library grant that created it, so holding a row is + // not proof of access to the book it points at. + await expect(service.releaseResetHold(7, 20, 'device-1')).rejects.toThrow(NotFoundException); + expect(mockRepo.findProgressBookFileByBookId).toHaveBeenCalledWith(20, [3]); + }); + }); + describe('getProgress', () => { it('returns device progress when the device sync is latest', async () => { const latestDeviceTime = new Date('2026-02-01T10:00:00.000Z'); @@ -1342,6 +1600,7 @@ describe('KoreaderService', () => { }, ], fileModifiedSinceLastSync: true, + heldByReset: [], }); }); @@ -1400,6 +1659,7 @@ describe('KoreaderService', () => { }, ], fileModifiedSinceLastSync: false, + heldByReset: [], }); }); diff --git a/server/src/modules/koreader/koreader.service.ts b/server/src/modules/koreader/koreader.service.ts index f2d3bf62f..834a04bb0 100644 --- a/server/src/modules/koreader/koreader.service.ts +++ b/server/src/modules/koreader/koreader.service.ts @@ -29,6 +29,19 @@ const FILE_NAMING_CACHE_TTL_MS = 30_000; const FILE_NAMING_CACHE_MAX_ENTRIES = 5_000; const SHARED_PROGRESS_CONCURRENCY = 4; const CHAPTER_EXTRACTION_CONCURRENCY = 2; +const RESET_DEVICE = 'web'; +const RESET_DEVICE_ID = 'bookorbit-web'; +/** The start of a paged document, in the page-number form KOReader sends for those formats. */ +const RESET_PAGED_POSITION = '1'; +/** The start of a reflowable document, in the same xpointer shape the web branch synthesises. */ +const RESET_REFLOWABLE_POSITION = '/body/DocFragment[1]/body'; +/** + * Only used when a device reports no position at all. A percentage cannot carry this on its + * own: page 1 of a hundred-page comic already reads as 1%, so any threshold tight enough to + * mean "the start" of a long book would never be reachable on a short one, and that device + * would stay held forever. + */ +const RESET_CONVERGED_PERCENTAGE_FALLBACK = 0.01; /** `format` routes the incoming position to the cfi or the pageNumber column. */ export interface KoreaderProgressBookFile { @@ -197,10 +210,54 @@ export class KoreaderService { if (options?.skipSharedProgress) return; + if (!(await this.resolveResetHold(userId, bookFile, data))) return; + const previousPercentage = previousDeviceProgress?.percentage != null ? toBookorbitPercentage(previousDeviceProgress.percentage) : null; await this.applySharedProgress(userId, bookFile, data, previousPercentage); } + /** + * Decides whether a pushed position may move shared progress while a reset is outstanding. + * + * The push clock cannot answer this. KOReader stamps a push with the time it sends it, not + * the time the position was reached, so a device replaying a position from before the reset + * looks exactly as recent as one that genuinely read on afterwards. What separates them is + * whether that device has been observed at the reset position since the reset was made. + * + * The judgement is per device. Serving the reset proves nothing, because both clients can + * classify it as a backward sync and drop it without telling anyone, so only the device's + * own push counts, and one device converging says nothing about the others. + */ + private async resolveResetHold( + userId: number, + bookFile: KoreaderProgressBookFile, + data: { percentage: number; progress?: string; device: string; deviceId: string }, + ): Promise { + const resetAt = await this.repo.getProgressReset(bookFile.id, userId); + if (!resetAt) return true; + + const atStart = isResetStartPosition(bookFile.format, data.percentage, data.progress); + const converged = await this.repo.getConvergedResetDeviceIds(bookFile.id, userId); + if (converged.has(data.deviceId)) { + // This device took the reset and has since read on, so the reset has done its work and + // must stop being served. The pull carries no device identity, so a marker kept alive + // for some other device would be answered to this one too, and it would be asked to + // jump back to the start on every sync for the rest of the book. + if (!atStart) await this.repo.clearProgressReset(bookFile.id, userId); + return true; + } + + if (atStart) { + await this.repo.recordResetConvergence(bookFile.id, userId, data.deviceId); + return true; + } + + this.logger.log( + `[${SYNC_EVENT}] [end] userId=${userId} bookFileId=${bookFile.id} device="${sanitizeLogValue(data.device)}" deviceId="${sanitizeLogValue(data.deviceId)}" percentage=${data.percentage} resetAt=${resetAt.toISOString()} held=true - device position recorded but held behind a pending reset`, + ); + return false; + } + /** * Bulk sibling of applyProgressForResolvedFile for whole-library sweeps. Reads every * file's device and reader progress in bounded batch queries, decides staleness in @@ -216,13 +273,15 @@ export class KoreaderService { userId: number, entries: BulkProgressEntry[], device: { device: string; deviceId: string }, - ): Promise<{ shared: number; stale: number }> { - if (entries.length === 0) return { shared: 0, stale: 0 }; + ): Promise<{ shared: number; stale: number; held: number }> { + if (entries.length === 0) return { shared: 0, stale: 0, held: 0 }; const bookFileIds = entries.map((entry) => entry.bookFile.id); - const [deviceRows, readerUpdatedAt] = await Promise.all([ + const [deviceRows, readerUpdatedAt, resets, convergedByFile] = await Promise.all([ this.repo.getDeviceProgressForFiles(bookFileIds, userId), this.repo.getReadingProgressUpdatedAtForFiles(bookFileIds, userId), + this.repo.getProgressResetsForFiles(bookFileIds, userId), + this.repo.getConvergedResetDeviceIdsForFiles(bookFileIds, userId), ]); const appliedAt = new Date(); @@ -251,7 +310,7 @@ export class KoreaderService { // sees what a sequential apply would have seen. state.ownDeviceNewest = entry.timestamp ?? appliedAtSeconds; state.latestPercentage = entry.percentage; - return { entry, stale, previousPercentage }; + return { entry, stale, previousPercentage, heldByReset: false }; }); const deviceUpserts = new Map(); @@ -270,7 +329,34 @@ export class KoreaderService { await this.repo.upsertDeviceProgressMany([...deviceUpserts.values()], appliedAt); await this.repo.restoreDevice(userId, device.deviceId); - const shared = plans.filter((plan) => !plan.stale); + // A sweep carries the device's whole shelf, so a book the user reset in BookOrbit shows up + // here holding its pre-reset sidecar position. Same judgement as a single push, for the one + // device this sweep speaks for. + const convergedResetFileIds = new Set(); + const retiredResetFileIds = new Set(); + for (const plan of plans) { + if (plan.stale) continue; + const bookFileId = plan.entry.bookFile.id; + if (!resets.get(bookFileId)) continue; + const atStart = isResetStartPosition(plan.entry.bookFile.format, plan.entry.percentage, plan.entry.progress); + if (convergedByFile.get(bookFileId)?.has(device.deviceId)) { + if (!atStart) retiredResetFileIds.add(bookFileId); + continue; + } + if (atStart) convergedResetFileIds.add(bookFileId); + else plan.heldByReset = true; + } + for (const bookFileId of convergedResetFileIds) await this.repo.recordResetConvergence(bookFileId, userId, device.deviceId); + for (const bookFileId of retiredResetFileIds) await this.repo.clearProgressReset(bookFileId, userId); + + const held = plans.filter((plan) => plan.heldByReset).length; + if (held > 0) { + this.logger.log( + `[${SYNC_EVENT}] [end] userId=${userId} deviceId="${sanitizeLogValue(device.deviceId)}" held=${held} - bulk positions recorded but held behind pending resets`, + ); + } + + const shared = plans.filter((plan) => !plan.stale && !plan.heldByReset); // Chapter extraction parses an EPUB per file, so it stays off the request path as it // does for a single sync, but bounded instead of one unawaited call per item. const extractionFileIds = [...new Set(shared.map((plan) => plan.entry.bookFile.id))]; @@ -297,7 +383,7 @@ export class KoreaderService { } }); - return { shared: shared.length, stale: plans.length - shared.length }; + return { shared: shared.length, stale: plans.filter((plan) => plan.stale).length, held }; } private async applySharedProgress( @@ -352,6 +438,34 @@ export class KoreaderService { if (!bookFile) return null; + // An outstanding reset is answered before anything stored, and answered with a position + // rather than with silence. An empty body reads as "this server knows nothing about your + // book", which is the one reply that leaves the device certain its own position is the + // only one there is, and it pushes it back. Zero attributed to BookOrbit is a position the + // device can act on, and the plugin's default forward strategy puts it to the reader. + const resetAt = await this.repo.getProgressReset(bookFile.id, userId); + if (resetAt) { + this.logger.log( + `[${SYNC_EVENT}] [end] userId=${userId} bookFileId=${bookFile.id} resetAt=${resetAt.toISOString()} - reset position served to device`, + ); + return { + document: documentHash, + percentage: 0, + // A real position, not an empty string: KOReader's own kosync plugin feeds this + // straight to GotoPage or GotoXPointer with no percentage fallback, so an empty value + // leaves it sitting exactly where it was. This is also the position a converged push + // has to report back, so what we ask for and what we accept are the same thing. + progress: isPagedReadingFormat(bookFile.format) ? RESET_PAGED_POSITION : RESET_REFLOWABLE_POSITION, + device: RESET_DEVICE, + device_id: RESET_DEVICE_ID, + // Stamped now rather than when the user reset, because a live marker is a standing + // instruction. Both clients compare this against their last page turn and default + // backward syncs to disabled, so the original time would lose that comparison on any + // pull deferred past a page turn, and be discarded without ever prompting. + timestamp: Math.floor(Date.now() / 1000), + }; + } + const latestDevice = await this.repo.getLatestDeviceProgress(bookFile.id, userId); const readingProg = await this.repo.getReadingProgress(bookFile.id, userId); @@ -593,13 +707,22 @@ export class KoreaderService { if (deviceProgress.length === 0 && !readingProgress) return null; const chapters = await this.repo.getChapters(bookFileId); - const latestDevice = deviceProgress[0]; - const deviceTime = latestDevice?.updatedAt?.getTime() ?? 0; const readerTime = readingProgress?.updatedAt?.getTime() ?? 0; - const isKoreaderLatest = latestDevice && deviceTime >= readerTime; - const canonicalPercentage = isKoreaderLatest ? toBookorbitPercentage(latestDevice.percentage ?? 0) : (readingProgress?.percentage ?? 0); - const canonicalChapterIndex = isKoreaderLatest ? (latestDevice.chapterIndex ?? null) : null; + // A held device's row can be newer than everything else and still not be the canonical + // position, because it is a position the user asked to discard. Scoped to the devices + // actually being held: once a device takes the reset, its reading counts again even though + // the marker stays live for whichever devices have not. + const resetAt = await this.repo.getProgressReset(bookFileId, userId); + const convergedDeviceIds = resetAt ? await this.repo.getConvergedResetDeviceIds(bookFileId, userId) : null; + const isHeld = (deviceId: string) => convergedDeviceIds !== null && !convergedDeviceIds.has(deviceId); + const heldDevices = deviceProgress.filter((dp) => isHeld(dp.deviceId)); + const liveDevice = deviceProgress.find((dp) => !isHeld(dp.deviceId)); + const liveDeviceTime = liveDevice?.updatedAt?.getTime() ?? 0; + const isKoreaderLatest = liveDevice && liveDeviceTime >= readerTime; + const canonicalPercentage = isKoreaderLatest ? toBookorbitPercentage(liveDevice.percentage ?? 0) : (readingProgress?.percentage ?? 0); + const canonicalTime = isKoreaderLatest ? Math.max(liveDeviceTime, readerTime) : readerTime; + const canonicalChapterIndex = isKoreaderLatest ? (liveDevice.chapterIndex ?? null) : null; const lastWriteTime = await this.repo.getLastFileWriteTime(bookFileId); const fileModifiedSinceLastSync = @@ -616,7 +739,7 @@ export class KoreaderService { canonicalChapterIndex, canonicalChapterTitle: canonicalChapterIndex != null ? (chapters.find((c) => c.chapterIndex === canonicalChapterIndex)?.title ?? null) : null, canonicalSource: isKoreaderLatest ? 'koreader' : 'web_reader', - canonicalUpdatedAt: new Date(Math.max(deviceTime, readerTime)).toISOString(), + canonicalUpdatedAt: new Date(canonicalTime).toISOString(), devices: deviceProgress.map((dp) => ({ device: dp.device, deviceId: dp.deviceId, @@ -626,8 +749,72 @@ export class KoreaderService { updatedAt: dp.updatedAt!.toISOString(), })), fileModifiedSinceLastSync, + heldByReset: heldDevices.map((dp) => ({ + device: dp.device, + deviceId: dp.deviceId, + percentage: toBookorbitPercentage(dp.percentage ?? 0), + updatedAt: dp.updatedAt!.toISOString(), + })), }; } + + /** + * Accepts a held device's position and stops holding it. The reader answered the prompt on + * the device and said no, and without this the only ways out are reading the book in + * BookOrbit or resetting again, neither of which is reachable from seeing the hold. + */ + async releaseResetHold(userId: number, bookId: number, deviceId: string): Promise { + const startedAt = Date.now(); + const accessibleLibraryIds = await this.repo.getAccessibleLibraryIds(userId); + const bookFile = await this.repo.findProgressBookFileByBookId(bookId, accessibleLibraryIds); + if (!bookFile) throw new NotFoundException(`No synced file found for book ${bookId}`); + + const held = await this.repo.getDeviceProgressForDevice(bookFile.id, userId, deviceId); + if (!held) throw new NotFoundException(`No KOReader progress found for device on book ${bookId}`); + + // Checked before writing, so a hold released twice, or released as the device converges on + // its own, answers with a plain 404 rather than tripping the marker's foreign key. + if ((await this.repo.getProgressReset(bookFile.id, userId)) === null) { + throw new NotFoundException(`No pending reset for book ${bookId}`); + } + + // Retired outright rather than recorded as convergence: the reader has said this position + // is the one they want, so leaving the marker live would have the next pull answer zero + // and ask the same device to go back to the start again. + await this.repo.clearProgressReset(bookFile.id, userId); + await this.applySharedProgress( + userId, + bookFile, + { percentage: held.percentage ?? 0, progress: held.progress ?? undefined, timestamp: held.syncTimestamp ?? undefined }, + null, + ); + + this.logger.log( + `[${SYNC_EVENT}] [end] userId=${userId} bookFileId=${bookFile.id} deviceId="${sanitizeLogValue(deviceId)}" durationMs=${Date.now() - startedAt} - reset hold released to the device position`, + ); + } +} + +/** + * Whether a reported position is the start of the book, which is what a device landing on a + * reset looks like. Routed by format because KOReader describes the two kinds of document + * differently, and judged on the position rather than the percentage wherever one is sent. + */ +export function isResetStartPosition(format: string | null | undefined, percentage: number, progress?: string | null): boolean { + if (isPagedReadingFormat(format)) { + const page = parseKoreaderPageNumber(progress); + if (page !== null) return page <= 1; + } else { + const trimmed = progress?.trim(); + // The percentage has to agree for a reflowable book. An EPUB with a single spine item puts + // every position in the first fragment, so the fragment alone would call a device converged + // wherever it happens to be sitting. Paged formats need no such guard, because a page + // number says exactly where the reader is however short the book. + if (trimmed) { + return /^\/body\/DocFragment\[1\](\/|$)/.test(trimmed) && percentage <= RESET_CONVERGED_PERCENTAGE_FALLBACK; + } + } + return percentage <= RESET_CONVERGED_PERCENTAGE_FALLBACK; } function toBookorbitPercentage(koreaderPct: number): number { diff --git a/server/src/modules/reading-state/reading-state.repository.test.ts b/server/src/modules/reading-state/reading-state.repository.test.ts index 737e59274..6d0f46f10 100644 --- a/server/src/modules/reading-state/reading-state.repository.test.ts +++ b/server/src/modules/reading-state/reading-state.repository.test.ts @@ -4,6 +4,7 @@ import { audiobookProgress, koreaderDeviceProgress, koreaderPageStats, + koreaderProgressResets, koboReadingStates, readingProgress, readingSessions, @@ -38,9 +39,12 @@ function makeHarness(selectResults: unknown[]) { const statusValues = vi.fn(() => ({ onConflictDoUpdate: statusConflict })); const dailyConflict = vi.fn().mockResolvedValue(undefined); const dailyValues = vi.fn(() => ({ onConflictDoUpdate: dailyConflict })); + const resetConflict = vi.fn().mockResolvedValue(undefined); + const resetValues = vi.fn(() => ({ onConflictDoUpdate: resetConflict })); const insert = vi.fn((table: unknown) => { if (table === userBookStatus) return { values: statusValues }; if (table === userReadingDailyStats) return { values: dailyValues }; + if (table === koreaderProgressResets) return { values: resetValues }; throw new Error('Unexpected table in insert'); }); @@ -62,6 +66,7 @@ function makeHarness(selectResults: unknown[]) { statusConflict, dailyValues, dailyConflict, + resetValues, update, updateSet, }; @@ -83,17 +88,20 @@ describe('ReadingStateRepository', () => { durationSeconds: 1800, progressDelta: 6, }; - const { repo, transaction, tx, deleteFn, statusValues, statusConflict, dailyValues, dailyConflict, update, updateSet } = makeHarness([ - [{ libraryId: 3 }], - [deletedSession], - [], - [{ total: 2 }], - [{ total: 1 }], - [{ total: 3 }], - [{ total: 4 }], - [{ id: 88 }], - [remainingSession], - ]); + const { repo, transaction, tx, deleteFn, statusValues, statusConflict, dailyValues, dailyConflict, resetValues, update, updateSet } = makeHarness( + [ + [{ libraryId: 3 }], + [deletedSession], + [], + [{ total: 2 }], + [{ total: 1 }], + [{ total: 3 }], + [{ total: 4 }], + [{ id: 88 }], + [{ id: 501 }, { id: 502 }], + [remainingSession], + ], + ); const result = await repo.resetBookReadingState(7, 42, 'UTC'); @@ -103,6 +111,12 @@ describe('ReadingStateRepository', () => { expect(deleteFn).toHaveBeenCalledWith(koreaderDeviceProgress); expect(deleteFn).toHaveBeenCalledWith(koreaderPageStats); expect(deleteFn).toHaveBeenCalledWith(readingSessions); + // The wipe has to leave a reset behind for each file, or a KOReader device reopening the + // book reads the missing position as unknown and pushes its own straight back. + expect(resetValues).toHaveBeenCalledWith([ + { userId: 7, bookFileId: 501 }, + { userId: 7, bookFileId: 502 }, + ]); expect(deleteFn).toHaveBeenCalledWith(userReadingDailyStats); expect(statusValues).toHaveBeenCalledWith( expect.objectContaining({ diff --git a/server/src/modules/reading-state/reading-state.repository.ts b/server/src/modules/reading-state/reading-state.repository.ts index 66badd5aa..56cdc22a8 100644 --- a/server/src/modules/reading-state/reading-state.repository.ts +++ b/server/src/modules/reading-state/reading-state.repository.ts @@ -21,6 +21,7 @@ import { koboSnapshotBooks, koreaderDeviceProgress, koreaderPageStats, + koreaderProgressResets, readingProgress, readingAttempts, readingSessions, @@ -74,6 +75,10 @@ export class ReadingStateRepository { const now = new Date(); const nowIso = now.toISOString(); + // Materialised separately from the subquery above because the reset markers are + // inserted per file, and a wiped book still has to be able to tell a device that its + // position is gone rather than merely unknown. + const resetFileIds = (await tx.select({ id: bookFiles.id }).from(bookFiles).where(eq(bookFiles.bookId, bookId))).map((file) => file.id); await Promise.all([ tx.delete(readingProgress).where(and(eq(readingProgress.userId, userId), inArray(readingProgress.bookFileId, fileIds))), @@ -81,6 +86,12 @@ export class ReadingStateRepository { tx.delete(koreaderDeviceProgress).where(and(eq(koreaderDeviceProgress.userId, userId), inArray(koreaderDeviceProgress.bookFileId, fileIds))), tx.delete(koreaderPageStats).where(and(eq(koreaderPageStats.userId, userId), inArray(koreaderPageStats.bookFileId, fileIds))), tx.delete(readingSessions).where(and(eq(readingSessions.userId, userId), eq(readingSessions.bookId, bookId))), + resetFileIds.length > 0 + ? tx + .delete(koreaderProgressResets) + .where(and(eq(koreaderProgressResets.userId, userId), inArray(koreaderProgressResets.bookFileId, resetFileIds))) + .then(() => tx.insert(koreaderProgressResets).values(resetFileIds.map((bookFileId) => ({ userId, bookFileId })))) + : Promise.resolve(), tx.delete(readingAttempts).where(and(eq(readingAttempts.userId, userId), eq(readingAttempts.bookId, bookId))), tx .insert(userBookStatus) diff --git a/server/test/koreader-progress-reset.e2e-spec.ts b/server/test/koreader-progress-reset.e2e-spec.ts new file mode 100644 index 000000000..06dd8fe82 --- /dev/null +++ b/server/test/koreader-progress-reset.e2e-spec.ts @@ -0,0 +1,341 @@ +import { randomUUID } from 'crypto'; +import { eq } from 'drizzle-orm'; + +import * as schema from '../src/db/schema'; +import { createEpubFixture } from './e2e/reader-state-isolation/reader-state-isolation-fixture-builder'; +import { + authHeader, + closeReaderStateIsolationE2EContext, + createLibraryWithFolder, + createReaderStateIsolationE2EContext, + locateBookByAbsolutePath, + triggerAndWaitForLibraryScan, + type CreatedLibrary, + type LocatedBookFile, + type ReaderStateIsolationE2EContext, +} from './e2e/reader-state-isolation/reader-state-isolation-harness'; + +const KOREADER_USERNAME = `progress-reset-device-${randomUUID().slice(0, 8)}`; +const KOREADER_PASSWORD = 'ProgressResetPass123'; +const XPOINTER = '/body/DocFragment[8]/body/p[12]/text().0'; + +/** + * A reset made in BookOrbit has to survive contact with a device that still holds the old + * position in its sidecar. The device cannot tell the server when it reached that position, + * only when it sent it, so these cases pin the behaviour that makes the reset win anyway: + * the pull answers with a position rather than with silence, and a push that predates the + * reset is recorded without moving the book. + */ +describe('KOReader progress reset propagation (e2e)', { timeout: 180_000 }, () => { + let ctx!: ReaderStateIsolationE2EContext; + let library!: CreatedLibrary; + let epub!: LocatedBookFile; + let epubHash!: string; + + function deviceHeaders(): Record { + return { 'x-auth-user': KOREADER_USERNAME, 'x-auth-key': KOREADER_PASSWORD }; + } + + async function pushFromDevice(percentage: number, progress = XPOINTER, deviceId = 'reset-device-1') { + const response = await ctx.app.inject({ + method: 'PUT', + url: '/api/v1/koreader/syncs/progress', + headers: deviceHeaders(), + payload: { document: epubHash, percentage, progress, device: 'Kobo Libra', device_id: deviceId }, + }); + expect(response.statusCode).toBe(200); + } + + async function pullFromDevice() { + const response = await ctx.app.inject({ + method: 'GET', + url: `/api/v1/koreader/syncs/progress/${epubHash}`, + headers: deviceHeaders(), + }); + expect(response.statusCode).toBe(200); + return response.json() as { percentage?: number; progress?: string | null; device?: string; device_id?: string; timestamp?: number }; + } + + async function clearProgressFromWeb() { + const response = await ctx.app.inject({ + method: 'DELETE', + url: `/api/v1/books/files/${epub.bookFileId}/progress`, + headers: authHeader(ctx.adminToken), + }); + expect([200, 204]).toContain(response.statusCode); + } + + async function storedPercentage(): Promise { + const [row] = await ctx.db + .select({ percentage: schema.readingProgress.percentage }) + .from(schema.readingProgress) + .where(eq(schema.readingProgress.bookFileId, epub.bookFileId)); + return row?.percentage ?? null; + } + + async function deviceRows() { + return ctx.db + .select({ percentage: schema.koreaderDeviceProgress.percentage }) + .from(schema.koreaderDeviceProgress) + .where(eq(schema.koreaderDeviceProgress.bookFileId, epub.bookFileId)); + } + + async function convergedDevices() { + return ctx.db + .select({ deviceId: schema.koreaderProgressResetDevices.deviceId }) + .from(schema.koreaderProgressResetDevices) + .where(eq(schema.koreaderProgressResetDevices.bookFileId, epub.bookFileId)); + } + + async function pendingResets() { + return ctx.db + .select({ resetAt: schema.koreaderProgressResets.resetAt }) + .from(schema.koreaderProgressResets) + .where(eq(schema.koreaderProgressResets.bookFileId, epub.bookFileId)); + } + + beforeAll(async () => { + ctx = await createReaderStateIsolationE2EContext(); + library = await createLibraryWithFolder(ctx, { name: `koreader-progress-reset-${randomUUID()}` }); + + const epubPath = await createEpubFixture(library.folderPath, 'progress-reset-book.epub', { + title: `Progress Reset Book ${randomUUID()}`, + uid: `urn:uuid:${randomUUID()}`, + }); + + await triggerAndWaitForLibraryScan(ctx, library.libraryId); + epub = await locateBookByAbsolutePath(ctx, epubPath); + const [row] = await ctx.db.select({ fileHash: schema.bookFiles.fileHash }).from(schema.bookFiles).where(eq(schema.bookFiles.id, epub.bookFileId)); + expect(row?.fileHash).toBeTruthy(); + epubHash = row!.fileHash!; + + const credentials = await ctx.app.inject({ + method: 'POST', + url: '/api/v1/koreader/credentials', + headers: authHeader(ctx.adminToken), + payload: { username: KOREADER_USERNAME, password: KOREADER_PASSWORD }, + }); + expect([200, 201]).toContain(credentials.statusCode); + }, 180_000); + + afterAll(async () => { + if (ctx) await closeReaderStateIsolationE2EContext(ctx); + }); + + beforeEach(async () => { + await ctx.db.delete(schema.koreaderProgressResets).where(eq(schema.koreaderProgressResets.bookFileId, epub.bookFileId)); + await ctx.db.delete(schema.koreaderDeviceProgress).where(eq(schema.koreaderDeviceProgress.bookFileId, epub.bookFileId)); + await ctx.db.delete(schema.readingProgress).where(eq(schema.readingProgress.bookFileId, epub.bookFileId)); + }); + + it('clears the device position alongside the shared one', async () => { + await pushFromDevice(0.42); + expect(await deviceRows()).toHaveLength(1); + + await clearProgressFromWeb(); + + // Leaving the device row behind is what let the old position be served straight back. + expect(await deviceRows()).toHaveLength(0); + expect(await storedPercentage()).toBeNull(); + expect(await pendingResets()).toHaveLength(1); + }); + + it('answers the next pull with a start position a stock client can act on', async () => { + await pushFromDevice(0.42); + await clearProgressFromWeb(); + + const pulled = await pullFromDevice(); + + expect(pulled.percentage).toBe(0); + // Stock kosync has no percentage fallback, so an empty position would leave it put. + expect(pulled.progress).toBe('/body/DocFragment[1]/body'); + expect(pulled.device).toBe('web'); + expect(pulled.device_id).toBe('bookorbit-web'); + }); + + it('stamps the delivery as current so the client reads it as a forward sync', async () => { + await pushFromDevice(0.42); + await clearProgressFromWeb(); + const [marker] = await pendingResets(); + + const pulled = await pullFromDevice(); + + expect(pulled.timestamp).toBeGreaterThanOrEqual(Math.floor(marker!.resetAt.getTime() / 1000)); + }); + + it('keeps the reset after serving it, because the client can discard it silently', async () => { + await pushFromDevice(0.42); + await clearProgressFromWeb(); + + await pullFromDevice(); + + expect(await pendingResets()).toHaveLength(1); + // Still held, so a reset the reader never saw cannot be undone by the next push. + await pushFromDevice(0.42); + expect(await storedPercentage()).toBeNull(); + }); + + it('holds a push that predates the reset without losing what the device reported', async () => { + await pushFromDevice(0.42); + await clearProgressFromWeb(); + + await pushFromDevice(0.42); + + // The push clock says "now" either way, so only the reset marker can tell these apart. + expect(await storedPercentage()).toBeNull(); + expect(await deviceRows()).toHaveLength(1); + expect(await pendingResets()).toHaveLength(1); + }); + + it('keeps answering the reset while a device keeps replaying its old position', async () => { + await pushFromDevice(0.42); + await clearProgressFromWeb(); + await pushFromDevice(0.42); + + expect((await pullFromDevice()).percentage).toBe(0); + }); + + it('accepts a device once it reports the start position, and lets it read on', async () => { + await pushFromDevice(0.42); + await clearProgressFromWeb(); + + await pushFromDevice(0.004, '/body/DocFragment[1]/body'); + expect(await convergedDevices()).toHaveLength(1); + expect(await storedPercentage()).toBeCloseTo(0.4, 5); + + await pushFromDevice(0.15); + expect(await storedPercentage()).toBeCloseTo(15, 5); + }); + + it('stops answering with the reset once a device has taken it and read on', async () => { + await pushFromDevice(0.42); + await clearProgressFromWeb(); + await pushFromDevice(0.004, '/body/DocFragment[1]/body'); + await pushFromDevice(0.5, '/body/DocFragment[9]/body'); + + const pulled = await pullFromDevice(); + + expect(pulled.percentage).toBeCloseTo(0.5, 5); + }); + + it('keeps holding a second device after the first one has taken the reset', async () => { + await pushFromDevice(0.42, XPOINTER, 'reset-device-1'); + await pushFromDevice(0.42, XPOINTER, 'reset-device-2'); + await clearProgressFromWeb(); + + await pushFromDevice(0.004, '/body/DocFragment[1]/body', 'reset-device-1'); + expect(await storedPercentage()).toBeCloseTo(0.4, 5); + + // Device two never saw the reset, so its position is still not the book's. + await pushFromDevice(0.42, XPOINTER, 'reset-device-2'); + expect(await storedPercentage()).toBeCloseTo(0.4, 5); + }); + + it('stops protecting a second device once the first has read on, which is the accepted bound', async () => { + await pushFromDevice(0.42, XPOINTER, 'reset-device-1'); + await pushFromDevice(0.42, XPOINTER, 'reset-device-2'); + await clearProgressFromWeb(); + await pushFromDevice(0.004, '/body/DocFragment[1]/body', 'reset-device-1'); + + // Reading on retires the marker, because the pull carries no device identity and a marker + // kept alive for device two would send device one back to the start on every sync. + await pushFromDevice(0.5, '/body/DocFragment[9]/body', 'reset-device-1'); + expect(await pendingResets()).toHaveLength(0); + + // So device two's stale position lands. This is a deliberate bound, not an oversight: + // holding it would need device_id on the pull, which is a plugin change. + await pushFromDevice(0.42, XPOINTER, 'reset-device-2'); + expect(await storedPercentage()).toBeCloseTo(42, 5); + }); + + it('a released hold is not re-served on the next pull', async () => { + await pushFromDevice(0.42); + await clearProgressFromWeb(); + await pushFromDevice(0.42); + + const released = await ctx.app.inject({ + method: 'POST', + url: `/api/v1/koreader/books/${epub.bookId}/reset-hold/release`, + headers: authHeader(ctx.adminToken), + payload: { deviceId: 'reset-device-1' }, + }); + expect([200, 201]).toContain(released.statusCode); + + // Leaving the marker live here would ask the device that was just released to go back. + expect((await pullFromDevice()).percentage).toBeCloseTo(0.42, 5); + }); + + it('answers a release with no pending reset as not found', async () => { + await pushFromDevice(0.42); + + const released = await ctx.app.inject({ + method: 'POST', + url: `/api/v1/koreader/books/${epub.bookId}/reset-hold/release`, + headers: authHeader(ctx.adminToken), + payload: { deviceId: 'reset-device-1' }, + }); + + expect(released.statusCode).toBe(404); + }); + + it('releases a hold on request and takes that device position', async () => { + await pushFromDevice(0.42); + await clearProgressFromWeb(); + await pushFromDevice(0.42); + + const released = await ctx.app.inject({ + method: 'POST', + url: `/api/v1/koreader/books/${epub.bookId}/reset-hold/release`, + headers: authHeader(ctx.adminToken), + payload: { deviceId: 'reset-device-1' }, + }); + expect([200, 201]).toContain(released.statusCode); + + expect(await storedPercentage()).toBeCloseTo(42, 5); + }); + + it('re-arms every device when the book is reset a second time', async () => { + await pushFromDevice(0.42); + await clearProgressFromWeb(); + await pushFromDevice(0.004, '/body/DocFragment[1]/body'); + expect(await convergedDevices()).toHaveLength(1); + + await pushFromDevice(0.5); + await clearProgressFromWeb(); + + // A device that took the previous reset has not taken this one. + expect(await convergedDevices()).toHaveLength(0); + await pushFromDevice(0.5); + expect(await storedPercentage()).toBeNull(); + }); + + it('lets reading in BookOrbit retire a reset the device never collected', async () => { + await pushFromDevice(0.42); + await clearProgressFromWeb(); + + const saved = await ctx.app.inject({ + method: 'POST', + url: `/api/v1/books/files/${epub.bookFileId}/progress`, + headers: authHeader(ctx.adminToken), + payload: { percentage: 30, cfi: 'epubcfi(/6/4!/4/2/2:10)' }, + }); + expect([200, 201, 204]).toContain(saved.statusCode); + + expect(await pendingResets()).toHaveLength(0); + expect(await storedPercentage()).toBeCloseTo(30, 5); + }); + + it('arms the same reset when the whole reading state is wiped', async () => { + await pushFromDevice(0.42); + + const reset = await ctx.app.inject({ + method: 'POST', + url: `/api/v1/books/${epub.bookId}/reset-reading-state`, + headers: authHeader(ctx.adminToken), + }); + expect([200, 201]).toContain(reset.statusCode); + + expect(await pendingResets()).toHaveLength(1); + expect((await pullFromDevice()).percentage).toBe(0); + }); +});