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 }) }}
+
+
+
+
+ {{ t('book.detail.details.resetHoldNotice', { device: held.device, percent: formatPercent(held.percentage) }) }}
+
+
+
+ {{ t('book.detail.details.resetHoldReleaseFailed') }}
+
+
+
@@ -2281,7 +2328,10 @@ watch(
-
+
+
+
+
+
+ {{ t('book.detail.details.resetHoldNotice', { device: held.device, percent: formatPercent(held.percentage) }) }}
+
+
+
+ {{ t('book.detail.details.resetHoldReleaseFailed') }}
+
+
+
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);
+ });
+});