From 7f12e8cd6d1ac8fe03e6e9a176ebdf1e68925a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-Xavier=20Payet?= Date: Sat, 17 Oct 2026 01:33:05 +0200 Subject: [PATCH] fix(kobo): reconcile counter estimates on their counter interval (#1796) fix(kobo): reconcile counter estimates on their counter interval (#1796) Stop counting offline Kobo reading twice. Record when each counter estimate's interval began, split measured sessions across intervals by wall-clock overlap, and subtract measured seconds instead of dropping the estimate, so both arrival orders converge. Closes #1667 Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com> --- scripts/e2e/suite-registry.mjs | 19 + ...0117_add_reading_session_sync_interval.sql | 1 + .../src/db/migrations/meta/0117_snapshot.json | 23855 ++++++++++++++++ server/src/db/migrations/meta/_journal.json | 7 + server/src/db/schema/reader.ts | 2 + .../kobo/kobo-statistics-session.util.ts | 2 +- .../services/kobo-reading-state.service.ts | 15 +- ...ding-session-cumulative.repository.test.ts | 261 +- .../reading-session.repository.ts | 163 +- .../reading-session.service.ts | 2 +- ...bo-reading-time-reconciliation.e2e-spec.ts | 479 + 11 files changed, 24707 insertions(+), 99 deletions(-) create mode 100644 server/src/db/migrations/0117_add_reading_session_sync_interval.sql create mode 100644 server/src/db/migrations/meta/0117_snapshot.json create mode 100644 server/test/kobo-reading-time-reconciliation.e2e-spec.ts diff --git a/scripts/e2e/suite-registry.mjs b/scripts/e2e/suite-registry.mjs index 5ffc60e44..0878631b5 100644 --- a/scripts/e2e/suite-registry.mjs +++ b/scripts/e2e/suite-registry.mjs @@ -1080,6 +1080,25 @@ export const E2E_SUITES = Object.freeze({ ...SHARED_DB_AND_HELPER_PATHS, ], }, + "kobo-reading-time-reconciliation": { + id: "kobo-reading-time-reconciliation", + name: "Kobo Reading Time Reconciliation", + timeout: 45, + lane: "full", + description: "Kobo counter-derived estimate and measured session reconciliation suite", + vitestTarget: "test/kobo-reading-time-reconciliation.e2e-spec.ts", + junitOutput: `${TEST_RESULTS_DIR}/kobo-reading-time-reconciliation-e2e-junit.xml`, + prepareDedicatedDatabase: true, + useDedicatedDatabase: true, + changedPaths: [ + "server/src/modules/kobo/**", + "server/src/modules/reading-session/**", + "server/src/common/utils/reading-daily-stats.utils.ts", + "server/test/kobo-reading-time-reconciliation.e2e-spec.ts", + "server/test/e2e/reader-state-isolation/**", + ...SHARED_DB_AND_HELPER_PATHS, + ], + }, "kobo-cover-identity": { id: "kobo-cover-identity", name: "Kobo Cover Identity", diff --git a/server/src/db/migrations/0117_add_reading_session_sync_interval.sql b/server/src/db/migrations/0117_add_reading_session_sync_interval.sql new file mode 100644 index 000000000..185173ced --- /dev/null +++ b/server/src/db/migrations/0117_add_reading_session_sync_interval.sql @@ -0,0 +1 @@ +ALTER TABLE "reading_sessions" ADD COLUMN "sync_interval_started_at" timestamp with time zone; \ No newline at end of file diff --git a/server/src/db/migrations/meta/0117_snapshot.json b/server/src/db/migrations/meta/0117_snapshot.json new file mode 100644 index 000000000..418256355 --- /dev/null +++ b/server/src/db/migrations/meta/0117_snapshot.json @@ -0,0 +1,23855 @@ +{ + "id": "aa4f307a-a064-41cc-890c-a1c1b71b786b", + "prevId": "c5ec208d-aad0-4c1f-9a5f-42dfa6b20ee5", + "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 + }, + "resource_label": { + "name": "resource_label", + "type": "varchar(255)", + "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_resource_label_trgm": { + "name": "idx_audit_resource_label_trgm", + "columns": [ + { + "expression": "resource_label", + "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.auth_sessions": { + "name": "auth_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_version": { + "name": "token_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "authentication_method": { + "name": "authentication_method", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "client_kind": { + "name": "client_kind", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'web'" + }, + "device_label": { + "name": "device_label", + "type": "varchar(100)", + "primaryKey": false, + "notNull": 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 + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auth_sessions_user_id_idx": { + "name": "auth_sessions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auth_sessions_expires_at_idx": { + "name": "auth_sessions_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_sessions_user_id_users_id_fk": { + "name": "auth_sessions_user_id_users_id_fk", + "tableFrom": "auth_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "auth_sessions_client_kind_chk": { + "name": "auth_sessions_client_kind_chk", + "value": "\"auth_sessions\".\"client_kind\" in ('web', 'native')" + }, + "auth_sessions_authentication_method_chk": { + "name": "auth_sessions_authentication_method_chk", + "value": "\"auth_sessions\".\"authentication_method\" in ('password', 'oidc', 'magic_link', 'setup', 'legacy')" + } + }, + "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 + }, + "session_id": { + "name": "session_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "authentication_method": { + "name": "authentication_method", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'legacy'" + }, + "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_session_id_idx": { + "name": "refresh_tokens_session_id_idx", + "columns": [ + { + "expression": "session_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" + }, + "refresh_tokens_session_id_auth_sessions_id_fk": { + "name": "refresh_tokens_session_id_auth_sessions_id_fk", + "tableFrom": "refresh_tokens", + "tableTo": "auth_sessions", + "columnsFrom": ["session_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\"" + }, + "refresh_tokens_authentication_method_chk": { + "name": "refresh_tokens_authentication_method_chk", + "value": "\"refresh_tokens\".\"authentication_method\" in ('password', 'oidc', 'magic_link', 'setup', 'legacy')" + } + }, + "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 + }, + "see_own_requested_books": { + "name": "see_own_requested_books", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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 + }, + "type": { + "name": "type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'books'" + }, + "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 + }, + "watch_local_folders": { + "name": "watch_local_folders", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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" + }, + "regex_metadata": { + "name": "regex_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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'" + }, + "added_at_source": { + "name": "added_at_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'imported'" + }, + "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": "double precision", + "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_write_all_files": { + "name": "file_write_all_files", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_write_read_along_enabled": { + "name": "file_write_read_along_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "file_write_read_along_max_file_size_mb": { + "name": "file_write_read_along_max_file_size_mb", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1000 + }, + "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 + }, + "koreader_hash_revision": { + "name": "koreader_hash_revision", + "type": "bigint", + "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": { + "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_type_chk": { + "name": "libraries_type_chk", + "value": "\"libraries\".\"type\" in ('books', 'podcasts')" + }, + "libraries_organization_mode_chk": { + "name": "libraries_organization_mode_chk", + "value": "\"libraries\".\"organization_mode\" in ('book_per_folder', 'book_per_file')" + }, + "libraries_added_at_source_chk": { + "name": "libraries_added_at_source_chk", + "value": "\"libraries\".\"added_at_source\" in ('imported', 'file_modified', 'file_created')" + }, + "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 + }, + "scan_state_version": { + "name": "scan_state_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "role": { + "name": "role", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'downloads'" + }, + "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": { + "library_folders_scan_state_version_nonnegative_chk": { + "name": "library_folders_scan_state_version_nonnegative_chk", + "value": "\"library_folders\".\"scan_state_version\" >= 0" + }, + "library_folders_role_chk": { + "name": "library_folders_role_chk", + "value": "\"library_folders\".\"role\" in ('downloads', 'local')" + } + }, + "isRLSEnabled": false + }, + "public.book_files": { + "name": "book_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "public_id": { + "name": "public_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "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 + }, + "is_fixed_layout": { + "name": "is_fixed_layout", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "media_overlay_available": { + "name": "media_overlay_available", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "media_overlay_duration_seconds": { + "name": "media_overlay_duration_seconds", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "media_overlay_checked_at": { + "name": "media_overlay_checked_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_files_public_id_uidx": { + "name": "book_files_public_id_uidx", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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_change_timestamp_idx": { + "name": "book_files_change_timestamp_idx", + "columns": [ + { + "expression": "greatest(\"created_at\", \"updated_at\") desc", + "asc": true, + "isExpression": 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_media_overlay_available_idx": { + "name": "book_files_media_overlay_available_idx", + "columns": [ + { + "expression": "media_overlay_available", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"book_files\".\"media_overlay_available\" = true", + "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" + }, + "book_files_media_overlay_duration_seconds_nonnegative_chk": { + "name": "book_files_media_overlay_duration_seconds_nonnegative_chk", + "value": "\"book_files\".\"media_overlay_duration_seconds\" is null or \"book_files\".\"media_overlay_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_covers": { + "name": "book_covers", + "schema": "", + "columns": { + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "medium": { + "name": "medium", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(9)", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "dormant_since": { + "name": "dormant_since", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "book_covers_book_id_books_id_fk": { + "name": "book_covers_book_id_books_id_fk", + "tableFrom": "book_covers", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_covers_book_id_medium_pk": { + "name": "book_covers_book_id_medium_pk", + "columns": ["book_id", "medium"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_covers_medium_chk": { + "name": "book_covers_medium_chk", + "value": "\"book_covers\".\"medium\" in ('ebook', 'audio')" + }, + "book_covers_source_chk": { + "name": "book_covers_source_chk", + "value": "\"book_covers\".\"source\" in ('extracted', 'custom')" + }, + "book_covers_origin_chk": { + "name": "book_covers_origin_chk", + "value": "\"book_covers\".\"origin\" in ('embedded', 'folder_image', 'opf', 'provider', 'dock', 'upload', 'legacy')" + }, + "book_covers_width_positive_chk": { + "name": "book_covers_width_positive_chk", + "value": "\"book_covers\".\"width\" is null or \"book_covers\".\"width\" > 0" + }, + "book_covers_height_positive_chk": { + "name": "book_covers_height_positive_chk", + "value": "\"book_covers\".\"height\" is null or \"book_covers\".\"height\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.book_duplicate_dismissals": { + "name": "book_duplicate_dismissals", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_duplicate_dismissals_user_created_idx": { + "name": "book_duplicate_dismissals_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": {} + } + }, + "foreignKeys": { + "book_duplicate_dismissals_user_id_users_id_fk": { + "name": "book_duplicate_dismissals_user_id_users_id_fk", + "tableFrom": "book_duplicate_dismissals", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_dismissals_book_id_a_books_id_fk": { + "name": "book_duplicate_dismissals_book_id_a_books_id_fk", + "tableFrom": "book_duplicate_dismissals", + "tableTo": "books", + "columnsFrom": ["book_id_a"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_duplicate_dismissals_book_id_b_books_id_fk": { + "name": "book_duplicate_dismissals_book_id_b_books_id_fk", + "tableFrom": "book_duplicate_dismissals", + "tableTo": "books", + "columnsFrom": ["book_id_b"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_duplicate_dismissals_user_id_book_id_a_book_id_b_pk": { + "name": "book_duplicate_dismissals_user_id_book_id_a_book_id_b_pk", + "columns": ["user_id", "book_id_a", "book_id_b"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_duplicate_dismissals_order_chk": { + "name": "book_duplicate_dismissals_order_chk", + "value": "\"book_duplicate_dismissals\".\"book_id_a\" < \"book_duplicate_dismissals\".\"book_id_b\"" + } + }, + "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 + }, + "confidence": { + "name": "confidence", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "member_bytes_total": { + "name": "member_bytes_total", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "member_bytes_max": { + "name": "member_bytes_max", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "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": {} + }, + "book_duplicate_groups_scan_reclaimable_idx": { + "name": "book_duplicate_groups_scan_reclaimable_idx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "(\"member_bytes_total\" - \"member_bytes_max\") desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_duplicate_groups_scan_confidence_idx": { + "name": "book_duplicate_groups_scan_confidence_idx", + "columns": [ + { + "expression": "scan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "confidence", + "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": { + "book_duplicate_groups_confidence_chk": { + "name": "book_duplicate_groups_confidence_chk", + "value": "\"book_duplicate_groups\".\"confidence\" between 1 and 4" + } + }, + "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 + }, + "total_extra_copies": { + "name": "total_extra_copies", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_reclaimable_bytes": { + "name": "total_reclaimable_bytes", + "type": "bigint", + "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.collection_podcasts": { + "name": "collection_podcasts", + "schema": "", + "columns": { + "collection_id": { + "name": "collection_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "podcast_id": { + "name": "podcast_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "added_at": { + "name": "added_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "collection_podcasts_podcast_id_idx": { + "name": "collection_podcasts_podcast_id_idx", + "columns": [ + { + "expression": "podcast_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "collection_podcasts_collection_id_collections_id_fk": { + "name": "collection_podcasts_collection_id_collections_id_fk", + "tableFrom": "collection_podcasts", + "tableTo": "collections", + "columnsFrom": ["collection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "collection_podcasts_podcast_id_podcasts_id_fk": { + "name": "collection_podcasts_podcast_id_podcasts_id_fk", + "tableFrom": "collection_podcasts", + "tableTo": "podcasts", + "columnsFrom": ["podcast_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "collection_podcasts_collection_id_podcast_id_pk": { + "name": "collection_podcasts_collection_id_podcast_id_pk", + "columns": ["collection_id", "podcast_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 + }, + "media_type": { + "name": "media_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'books'" + }, + "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 + }, + "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": { + "collections_user_media_type_name_uidx": { + "name": "collections_user_media_type_name_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "media_type", + "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": {} + }, + "collections_public_display_name_idx": { + "name": "collections_public_display_name_idx", + "columns": [ + { + "expression": "is_public", + "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": { + "collections_media_type_chk": { + "name": "collections_media_type_chk", + "value": "\"collections\".\"media_type\" in ('books', 'podcasts')" + }, + "collections_kobo_books_only_chk": { + "name": "collections_kobo_books_only_chk", + "value": "\"collections\".\"sync_to_kobo\" = false or \"collections\".\"media_type\" = 'books'" + } + }, + "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 + }, + "media_type": { + "name": "media_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'books'" + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "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": { + "smart_scopes_user_media_type_idx": { + "name": "smart_scopes_user_media_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "media_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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" + }, + "smart_scopes_library_id_libraries_id_fk": { + "name": "smart_scopes_library_id_libraries_id_fk", + "tableFrom": "smart_scopes", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "smart_scopes_user_id_media_type_name_unique": { + "name": "smart_scopes_user_id_media_type_name_unique", + "nullsNotDistinct": false, + "columns": ["user_id", "media_type", "name"] + } + }, + "policies": {}, + "checkConstraints": { + "smart_scopes_media_type_chk": { + "name": "smart_scopes_media_type_chk", + "value": "\"smart_scopes\".\"media_type\" in ('books', 'podcasts')" + }, + "smart_scopes_library_scope_chk": { + "name": "smart_scopes_library_scope_chk", + "value": "(\"smart_scopes\".\"media_type\" = 'podcasts') = (\"smart_scopes\".\"library_id\" is not null)" + }, + "smart_scopes_kobo_books_only_chk": { + "name": "smart_scopes_kobo_books_only_chk", + "value": "\"smart_scopes\".\"sync_to_kobo\" = false or \"smart_scopes\".\"media_type\" = 'books'" + } + }, + "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": "varchar(20)", + "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": "(CASE WHEN \"series_index\" IS NULL THEN NULL ELSE ARRAY[split_part(\"series_index\", '.', 1)::numeric, CASE WHEN strpos(\"series_index\", '.') = 0 THEN -1::numeric ELSE split_part(\"series_index\", '.', 2)::numeric END] END)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"series_index\" COLLATE \"C\"", + "asc": true, + "isExpression": 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" + }, + "book_series_memberships_series_index_format_chk": { + "name": "book_series_memberships_series_index_format_chk", + "value": "\"book_series_memberships\".\"series_index\" is null or \"book_series_memberships\".\"series_index\" ~ '^[0-9]+([.][0-9]+)?$'" + } + }, + "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 + }, + "ignore_cache": { + "name": "ignore_cache", + "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": { + "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": {} + }, + "book_community_ratings_count_book_idx": { + "name": "book_community_ratings_count_book_idx", + "columns": [ + { + "expression": "rating_count", + "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_count_book_idx": { + "name": "book_community_ratings_provider_count_book_idx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "rating_count", + "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": "varchar(20)", + "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[]" + }, + "cover_updated_at": { + "name": "cover_updated_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": { + "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_subtitle_unaccent_trgm_idx": { + "name": "bm_subtitle_unaccent_trgm_idx", + "columns": [ + { + "expression": "public.bookorbit_unaccent(\"subtitle\") gin_trgm_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "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": "(CASE WHEN \"series_index\" IS NULL THEN NULL ELSE ARRAY[split_part(\"series_index\", '.', 1)::numeric, CASE WHEN strpos(\"series_index\", '.') = 0 THEN -1::numeric ELSE split_part(\"series_index\", '.', 2)::numeric END] END)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"series_index\" COLLATE \"C\"", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bm_series_index_sort_idx": { + "name": "bm_series_index_sort_idx", + "columns": [ + { + "expression": "(CASE WHEN \"series_index\" IS NULL THEN NULL ELSE ARRAY[split_part(\"series_index\", '.', 1)::numeric, CASE WHEN strpos(\"series_index\", '.') = 0 THEN -1::numeric ELSE split_part(\"series_index\", '.', 2)::numeric END] END)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"series_index\" COLLATE \"C\"", + "asc": true, + "isExpression": 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_page_count_idx": { + "name": "bm_page_count_idx", + "columns": [ + { + "expression": "page_count", + "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": "(CASE WHEN \"series_index\" IS NULL THEN NULL ELSE ARRAY[split_part(\"series_index\", '.', 1)::numeric, CASE WHEN strpos(\"series_index\", '.') = 0 THEN -1::numeric ELSE split_part(\"series_index\", '.', 2)::numeric END] END)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"series_index\" COLLATE \"C\"", + "asc": true, + "isExpression": 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_series_index_format_chk": { + "name": "book_metadata_series_index_format_chk", + "value": "\"book_metadata\".\"series_index\" is null or \"book_metadata\".\"series_index\" ~ '^[0-9]+([.][0-9]+)?$'" + }, + "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 + }, + "ignore_cache": { + "name": "ignore_cache", + "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": { + "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": {} + }, + "scan_jobs_library_started_at_idx": { + "name": "scan_jobs_library_started_at_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" desc", + "asc": true, + "isExpression": 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 + }, + "source_created_at": { + "name": "source_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "starred_at": { + "name": "starred_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": { + "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": {} + }, + "annotations_user_created_active_idx": { + "name": "annotations_user_created_active_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"source_created_at\", \"created_at\") desc", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"annotations\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "annotations_user_book_created_active_idx": { + "name": "annotations_user_book_created_active_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"source_created_at\", \"created_at\") desc", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "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 + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "captured_at": { + "name": "captured_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "operation_id": { + "name": "operation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "manifest_revision": { + "name": "manifest_revision", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "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" + }, + "audiobook_progress_revision_positive_chk": { + "name": "audiobook_progress_revision_positive_chk", + "value": "\"audiobook_progress\".\"revision\" >= 1" + } + }, + "isRLSEnabled": false + }, + "public.book_journal_entries": { + "name": "book_journal_entries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quote": { + "name": "quote", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chapter_title": { + "name": "chapter_title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "position_percent": { + "name": "position_percent", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "cfi": { + "name": "cfi", + "type": "varchar(2000)", + "primaryKey": false, + "notNull": false + }, + "position_seconds": { + "name": "position_seconds", + "type": "real", + "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": { + "book_journal_entries_user_client_id_uidx": { + "name": "book_journal_entries_user_client_id_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_journal_entries_user_book_created_active_idx": { + "name": "book_journal_entries_user_book_created_active_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"book_journal_entries\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_journal_entries_book_id_idx": { + "name": "book_journal_entries_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_journal_entries_user_id_users_id_fk": { + "name": "book_journal_entries_user_id_users_id_fk", + "tableFrom": "book_journal_entries", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_journal_entries_book_id_books_id_fk": { + "name": "book_journal_entries_book_id_books_id_fk", + "tableFrom": "book_journal_entries", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_journal_entries_body_length_chk": { + "name": "book_journal_entries_body_length_chk", + "value": "char_length(\"book_journal_entries\".\"body\") between 1 and 10000" + }, + "book_journal_entries_quote_length_chk": { + "name": "book_journal_entries_quote_length_chk", + "value": "\"book_journal_entries\".\"quote\" is null or char_length(\"book_journal_entries\".\"quote\") <= 5000" + }, + "book_journal_entries_position_percent_range_chk": { + "name": "book_journal_entries_position_percent_range_chk", + "value": "\"book_journal_entries\".\"position_percent\" is null or (\"book_journal_entries\".\"position_percent\" >= 0 and \"book_journal_entries\".\"position_percent\" <= 100)" + }, + "book_journal_entries_position_seconds_nonnegative_chk": { + "name": "book_journal_entries_position_seconds_nonnegative_chk", + "value": "\"book_journal_entries\".\"position_seconds\" is null or \"book_journal_entries\".\"position_seconds\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.bookmarks": { + "name": "bookmarks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "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 + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chapter_id": { + "name": "chapter_id", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "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_client_id_uidx": { + "name": "bookmarks_user_book_client_id_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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 and \"bookmarks\".\"deleted_at\" 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 + }, + "media_overlay_fragment": { + "name": "media_overlay_fragment", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": false + }, + "media_overlay_section_index": { + "name": "media_overlay_section_index", + "type": "integer", + "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 + }, + "narration_percentage": { + "name": "narration_percentage", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "narration_updated_at": { + "name": "narration_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "text_updated_at": { + "name": "text_updated_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()" + }, + "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_media_overlay_section_index_nonnegative_chk": { + "name": "reading_progress_media_overlay_section_index_nonnegative_chk", + "value": "\"reading_progress\".\"media_overlay_section_index\" is null or \"reading_progress\".\"media_overlay_section_index\" >= 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)" + }, + "reading_progress_narration_percentage_range_chk": { + "name": "reading_progress_narration_percentage_range_chk", + "value": "\"reading_progress\".\"narration_percentage\" is null or (\"reading_progress\".\"narration_percentage\" >= 0 and \"reading_progress\".\"narration_percentage\" <= 100)" + } + }, + "isRLSEnabled": false + }, + "public.reading_session_sync_cursors": { + "name": "reading_session_sync_cursors", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "source_device_key": { + "name": "source_device_key", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "rssc_book_id_idx": { + "name": "rssc_book_id_idx", + "columns": [ + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "reading_session_sync_cursors_user_id_users_id_fk": { + "name": "reading_session_sync_cursors_user_id_users_id_fk", + "tableFrom": "reading_session_sync_cursors", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reading_session_sync_cursors_book_id_books_id_fk": { + "name": "reading_session_sync_cursors_book_id_books_id_fk", + "tableFrom": "reading_session_sync_cursors", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "reading_session_sync_cursors_user_id_book_id_source_source_device_key_pk": { + "name": "reading_session_sync_cursors_user_id_book_id_source_source_device_key_pk", + "columns": ["user_id", "book_id", "source", "source_device_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "rssc_counter_nonnegative_chk": { + "name": "rssc_counter_nonnegative_chk", + "value": "\"reading_session_sync_cursors\".\"counter\" >= 0" + }, + "rssc_generation_nonnegative_chk": { + "name": "rssc_generation_nonnegative_chk", + "value": "\"reading_session_sync_cursors\".\"generation\" >= 0" + } + }, + "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 + }, + "source_device_key": { + "name": "source_device_key", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "session_type": { + "name": "session_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'read'" + }, + "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 + }, + "sync_interval_started_at": { + "name": "sync_interval_started_at", + "type": "timestamp with time zone", + "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": {} + }, + "rs_user_book_source_device_started_idx": { + "name": "rs_user_book_source_device_started_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_device_key", + "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', 'ios', 'watchos', 'android', '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_read_aloud_sync_settings": { + "name": "user_book_read_aloud_sync_settings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'auto'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ubrass_user_id_idx": { + "name": "ubrass_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_book_read_aloud_sync_settings_user_id_users_id_fk": { + "name": "user_book_read_aloud_sync_settings_user_id_users_id_fk", + "tableFrom": "user_book_read_aloud_sync_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_book_read_aloud_sync_settings_book_id_books_id_fk": { + "name": "user_book_read_aloud_sync_settings_book_id_books_id_fk", + "tableFrom": "user_book_read_aloud_sync_settings", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_book_read_aloud_sync_settings_user_id_book_id_pk": { + "name": "user_book_read_aloud_sync_settings_user_id_book_id_pk", + "columns": ["user_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_book_read_aloud_sync_settings_mode_chk": { + "name": "user_book_read_aloud_sync_settings_mode_chk", + "value": "\"user_book_read_aloud_sync_settings\".\"mode\" in ('auto', 'disabled')" + } + }, + "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 + }, + "session_id": { + "name": "session_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "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_session_id_idx": { + "name": "oidc_sessions_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "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_session_id_auth_sessions_id_fk": { + "name": "oidc_sessions_session_id_auth_sessions_id_fk", + "tableFrom": "oidc_sessions", + "tableTo": "auth_sessions", + "columnsFrom": ["session_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 + }, + "read_aloud_delivery": { + "name": "read_aloud_delivery", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "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_device_deliveries": { + "name": "kobo_device_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "book_id": { + "name": "book_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_file_id": { + "name": "source_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'offered'" + }, + "previous_id": { + "name": "previous_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "current": { + "name": "current", + "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_device_deliveries_current_idx": { + "name": "kobo_device_deliveries_current_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kobo_device_deliveries\".\"current\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kobo_device_deliveries_device_book_idx": { + "name": "kobo_device_deliveries_device_book_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "device_id", + "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_deliveries_user_id_users_id_fk": { + "name": "kobo_device_deliveries_user_id_users_id_fk", + "tableFrom": "kobo_device_deliveries", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_device_deliveries_book_id_books_id_fk": { + "name": "kobo_device_deliveries_book_id_books_id_fk", + "tableFrom": "kobo_device_deliveries", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_device_deliveries_source_file_id_book_files_id_fk": { + "name": "kobo_device_deliveries_source_file_id_book_files_id_fk", + "tableFrom": "kobo_device_deliveries", + "tableTo": "book_files", + "columnsFrom": ["source_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "kobo_device_deliveries_owner_fk": { + "name": "kobo_device_deliveries_owner_fk", + "tableFrom": "kobo_device_deliveries", + "tableTo": "kobo_devices", + "columnsFrom": ["device_id", "user_id"], + "columnsTo": ["id", "user_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kobo_device_reading_states": { + "name": "kobo_device_reading_states", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_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 + }, + "delivery_id": { + "name": "delivery_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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": {}, + "foreignKeys": { + "kobo_device_reading_states_user_id_users_id_fk": { + "name": "kobo_device_reading_states_user_id_users_id_fk", + "tableFrom": "kobo_device_reading_states", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_device_reading_states_book_id_books_id_fk": { + "name": "kobo_device_reading_states_book_id_books_id_fk", + "tableFrom": "kobo_device_reading_states", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kobo_device_reading_states_delivery_id_kobo_device_deliveries_id_fk": { + "name": "kobo_device_reading_states_delivery_id_kobo_device_deliveries_id_fk", + "tableFrom": "kobo_device_reading_states", + "tableTo": "kobo_device_deliveries", + "columnsFrom": ["delivery_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "kobo_device_reading_states_owner_fk": { + "name": "kobo_device_reading_states_owner_fk", + "tableFrom": "kobo_device_reading_states", + "tableTo": "kobo_devices", + "columnsFrom": ["device_id", "user_id"], + "columnsTo": ["id", "user_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kobo_device_reading_states_user_id_device_id_book_id_pk": { + "name": "kobo_device_reading_states_user_id_device_id_book_id_pk", + "columns": ["user_id", "device_id", "book_id"] + } + }, + "uniqueConstraints": {}, + "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 + }, + "delivery_retry_at": { + "name": "delivery_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "delivery_failure_code": { + "name": "delivery_failure_code", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "read_aloud_delivery": { + "name": "read_aloud_delivery", + "type": "varchar(20)", + "primaryKey": false, + "notNull": 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 + }, + "unit_directory": { + "name": "unit_directory", + "type": "text", + "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 + }, + "auto_finalize_suppressed": { + "name": "auto_finalize_suppressed", + "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": { + "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"] + }, + "book_dock_files_unit_directory_unique": { + "name": "book_dock_files_unit_directory_unique", + "nullsNotDistinct": false, + "columns": ["unit_directory"] + } + }, + "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.book_dock_unit_files": { + "name": "book_dock_unit_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "dock_file_id": { + "name": "dock_file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "absolute_path": { + "name": "absolute_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "varchar(500)", + "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 + }, + "role": { + "name": "role", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'content'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_dock_unit_files_dock_file_id_sort_order_idx": { + "name": "book_dock_unit_files_dock_file_id_sort_order_idx", + "columns": [ + { + "expression": "dock_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_dock_unit_files_dock_file_id_book_dock_files_id_fk": { + "name": "book_dock_unit_files_dock_file_id_book_dock_files_id_fk", + "tableFrom": "book_dock_unit_files", + "tableTo": "book_dock_files", + "columnsFrom": ["dock_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "book_dock_unit_files_absolute_path_unique": { + "name": "book_dock_unit_files_absolute_path_unique", + "nullsNotDistinct": false, + "columns": ["absolute_path"] + } + }, + "policies": {}, + "checkConstraints": { + "book_dock_unit_files_role_chk": { + "name": "book_dock_unit_files_role_chk", + "value": "\"book_dock_unit_files\".\"role\" in ('content', 'cover', 'metadata', 'supplement')" + } + }, + "isRLSEnabled": false + }, + "public.book_request_dedupe_aliases": { + "name": "book_request_dedupe_aliases", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "book_request_dedupe_aliases_key_idx": { + "name": "book_request_dedupe_aliases_key_idx", + "columns": [ + { + "expression": "dedupe_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_request_dedupe_aliases_request_id_book_requests_id_fk": { + "name": "book_request_dedupe_aliases_request_id_book_requests_id_fk", + "tableFrom": "book_request_dedupe_aliases", + "tableTo": "book_requests", + "columnsFrom": ["request_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_request_dedupe_aliases_request_id_dedupe_key_pk": { + "name": "book_request_dedupe_aliases_request_id_dedupe_key_pk", + "columns": ["request_id", "dedupe_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_request_dismissals": { + "name": "book_request_dismissals", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_request_dismissals_user_id_idx": { + "name": "book_request_dismissals_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_request_dismissals_request_id_book_requests_id_fk": { + "name": "book_request_dismissals_request_id_book_requests_id_fk", + "tableFrom": "book_request_dismissals", + "tableTo": "book_requests", + "columnsFrom": ["request_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_request_dismissals_user_id_users_id_fk": { + "name": "book_request_dismissals_user_id_users_id_fk", + "tableFrom": "book_request_dismissals", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_request_dismissals_request_id_user_id_pk": { + "name": "book_request_dismissals_request_id_user_id_pk", + "columns": ["request_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_request_subscribers": { + "name": "book_request_subscribers", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "book_request_subscribers_user_id_idx": { + "name": "book_request_subscribers_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_request_subscribers_request_id_book_requests_id_fk": { + "name": "book_request_subscribers_request_id_book_requests_id_fk", + "tableFrom": "book_request_subscribers", + "tableTo": "book_requests", + "columnsFrom": ["request_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_request_subscribers_user_id_users_id_fk": { + "name": "book_request_subscribers_user_id_users_id_fk", + "tableFrom": "book_request_subscribers", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "book_request_subscribers_request_id_user_id_pk": { + "name": "book_request_subscribers_request_id_user_id_pk", + "columns": ["request_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.book_requests": { + "name": "book_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "media_kind": { + "name": "media_kind", + "type": "book_request_media_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "subtitle": { + "name": "subtitle", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "authors": { + "name": "authors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "series_name": { + "name": "series_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "series_index": { + "name": "series_index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "isbn10": { + "name": "isbn10", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "isbn13": { + "name": "isbn13", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "published_year": { + "name": "published_year", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "cover_url": { + "name": "cover_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_key": { + "name": "provider_key", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "metadata_sources": { + "name": "metadata_sources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "preferred_formats": { + "name": "preferred_formats", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "note": { + "name": "note", + "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 + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "decided_by_user_id": { + "name": "decided_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "decision_note": { + "name": "decision_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "matched_book_id": { + "name": "matched_book_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "book_dock_file_id": { + "name": "book_dock_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "failure_meta": { + "name": "failure_meta", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "self_serve": { + "name": "self_serve", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "fulfiller_user_id": { + "name": "fulfiller_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "companion_request_id": { + "name": "companion_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "target_book_id": { + "name": "target_book_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "varchar(500)", + "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": { + "book_requests_user_status_idx": { + "name": "book_requests_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": {} + }, + "book_requests_status_created_at_idx": { + "name": "book_requests_status_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_requests_target_library_id_idx": { + "name": "book_requests_target_library_id_idx", + "columns": [ + { + "expression": "target_library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_requests_created_at_idx": { + "name": "book_requests_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_requests_title_idx": { + "name": "book_requests_title_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_requests_matched_book_user_idx": { + "name": "book_requests_matched_book_user_idx", + "columns": [ + { + "expression": "matched_book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"book_requests\".\"matched_book_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_requests_companion_request_id_idx": { + "name": "book_requests_companion_request_id_idx", + "columns": [ + { + "expression": "companion_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"book_requests\".\"companion_request_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_requests_target_book_id_idx": { + "name": "book_requests_target_book_id_idx", + "columns": [ + { + "expression": "target_book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"book_requests\".\"target_book_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_requests_self_serve_idx": { + "name": "book_requests_self_serve_idx", + "columns": [ + { + "expression": "self_serve", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"book_requests\".\"self_serve\"", + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_requests_active_dedupe_key_uidx": { + "name": "book_requests_active_dedupe_key_uidx", + "columns": [ + { + "expression": "dedupe_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"book_requests\".\"status\" in ('pending', 'approved', 'searching', 'grabbed', 'downloading', 'importing', 'needs_review')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_requests_user_id_users_id_fk": { + "name": "book_requests_user_id_users_id_fk", + "tableFrom": "book_requests", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_requests_target_library_id_libraries_id_fk": { + "name": "book_requests_target_library_id_libraries_id_fk", + "tableFrom": "book_requests", + "tableTo": "libraries", + "columnsFrom": ["target_library_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_requests_target_folder_id_library_folders_id_fk": { + "name": "book_requests_target_folder_id_library_folders_id_fk", + "tableFrom": "book_requests", + "tableTo": "library_folders", + "columnsFrom": ["target_folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_requests_created_by_user_id_users_id_fk": { + "name": "book_requests_created_by_user_id_users_id_fk", + "tableFrom": "book_requests", + "tableTo": "users", + "columnsFrom": ["created_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_requests_decided_by_user_id_users_id_fk": { + "name": "book_requests_decided_by_user_id_users_id_fk", + "tableFrom": "book_requests", + "tableTo": "users", + "columnsFrom": ["decided_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_requests_matched_book_id_books_id_fk": { + "name": "book_requests_matched_book_id_books_id_fk", + "tableFrom": "book_requests", + "tableTo": "books", + "columnsFrom": ["matched_book_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_requests_book_dock_file_id_book_dock_files_id_fk": { + "name": "book_requests_book_dock_file_id_book_dock_files_id_fk", + "tableFrom": "book_requests", + "tableTo": "book_dock_files", + "columnsFrom": ["book_dock_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_requests_fulfiller_user_id_users_id_fk": { + "name": "book_requests_fulfiller_user_id_users_id_fk", + "tableFrom": "book_requests", + "tableTo": "users", + "columnsFrom": ["fulfiller_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_requests_companion_request_id_book_requests_id_fk": { + "name": "book_requests_companion_request_id_book_requests_id_fk", + "tableFrom": "book_requests", + "tableTo": "book_requests", + "columnsFrom": ["companion_request_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_requests_target_book_id_books_id_fk": { + "name": "book_requests_target_book_id_books_id_fk", + "tableFrom": "book_requests", + "tableTo": "books", + "columnsFrom": ["target_book_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_requests_status_chk": { + "name": "book_requests_status_chk", + "value": "\"book_requests\".\"status\" in ('pending', 'approved', 'rejected', 'cancelled', 'searching', 'grabbed', 'downloading', 'importing', 'needs_review', 'available', 'failed')" + }, + "book_requests_series_index_nonnegative_chk": { + "name": "book_requests_series_index_nonnegative_chk", + "value": "\"book_requests\".\"series_index\" is null or \"book_requests\".\"series_index\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.book_request_downloads": { + "name": "book_request_downloads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "download_client_id": { + "name": "download_client_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "indexer_id": { + "name": "indexer_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "automated": { + "name": "automated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "release_title": { + "name": "release_title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "release_guid": { + "name": "release_guid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_size_bytes": { + "name": "release_size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "release_seeders": { + "name": "release_seeders", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_format": { + "name": "release_format", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "freeleech": { + "name": "freeleech", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "client_key": { + "name": "client_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "progress_percent": { + "name": "progress_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "downloaded_bytes": { + "name": "downloaded_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_bytes": { + "name": "total_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "direct_url": { + "name": "direct_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direct_file_name": { + "name": "direct_file_name", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "direct_etag": { + "name": "direct_etag", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "direct_last_modified": { + "name": "direct_last_modified", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "content_path": { + "name": "content_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "local_path": { + "name": "local_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "book_dock_file_id": { + "name": "book_dock_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "release_units": { + "name": "release_units", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grabbed_at": { + "name": "grabbed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_progress_at": { + "name": "last_progress_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "imported_at": { + "name": "imported_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_request_downloads_status_idx": { + "name": "book_request_downloads_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_request_downloads_request_latest_idx": { + "name": "book_request_downloads_request_latest_idx", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_request_downloads_book_dock_file_id_idx": { + "name": "book_request_downloads_book_dock_file_id_idx", + "columns": [ + { + "expression": "book_dock_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "book_request_downloads_active_hash_uidx": { + "name": "book_request_downloads_active_hash_uidx", + "columns": [ + { + "expression": "coalesce(\"download_client_id\", 0)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "client_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"book_request_downloads\".\"status\" in ('queued', 'downloading', 'completed', 'importing', 'needs_review')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "book_request_downloads_request_id_book_requests_id_fk": { + "name": "book_request_downloads_request_id_book_requests_id_fk", + "tableFrom": "book_request_downloads", + "tableTo": "book_requests", + "columnsFrom": ["request_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "book_request_downloads_download_client_id_download_clients_id_fk": { + "name": "book_request_downloads_download_client_id_download_clients_id_fk", + "tableFrom": "book_request_downloads", + "tableTo": "download_clients", + "columnsFrom": ["download_client_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_request_downloads_indexer_id_request_indexers_id_fk": { + "name": "book_request_downloads_indexer_id_request_indexers_id_fk", + "tableFrom": "book_request_downloads", + "tableTo": "request_indexers", + "columnsFrom": ["indexer_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "book_request_downloads_book_dock_file_id_book_dock_files_id_fk": { + "name": "book_request_downloads_book_dock_file_id_book_dock_files_id_fk", + "tableFrom": "book_request_downloads", + "tableTo": "book_dock_files", + "columnsFrom": ["book_dock_file_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "book_request_downloads_source_chk": { + "name": "book_request_downloads_source_chk", + "value": "\"book_request_downloads\".\"source\" in ('magnet', 'torrent_file', 'direct_url', 'nzb_file')" + }, + "book_request_downloads_status_chk": { + "name": "book_request_downloads_status_chk", + "value": "\"book_request_downloads\".\"status\" in ('queued', 'downloading', 'completed', 'importing', 'needs_review', 'imported', 'failed')" + }, + "book_request_downloads_progress_range_chk": { + "name": "book_request_downloads_progress_range_chk", + "value": "\"book_request_downloads\".\"progress_percent\" >= 0 and \"book_request_downloads\".\"progress_percent\" <= 100" + } + }, + "isRLSEnabled": false + }, + "public.download_client_path_mappings": { + "name": "download_client_path_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "download_client_id": { + "name": "download_client_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "remote_path": { + "name": "remote_path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "local_path": { + "name": "local_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": { + "download_client_path_mappings_client_remote_uidx": { + "name": "download_client_path_mappings_client_remote_uidx", + "columns": [ + { + "expression": "download_client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "remote_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "download_client_path_mappings_download_client_id_download_clients_id_fk": { + "name": "download_client_path_mappings_download_client_id_download_clients_id_fk", + "tableFrom": "download_client_path_mappings", + "tableTo": "download_clients", + "columnsFrom": ["download_client_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.download_clients": { + "name": "download_clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "credentials_enc": { + "name": "credentials_enc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "default": "'bookorbit'" + }, + "use_hardlinks": { + "name": "use_hardlinks", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_private_address": { + "name": "allow_private_address", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_tested_at": { + "name": "last_tested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_ok": { + "name": "last_test_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_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": { + "download_clients_name_lower_uidx": { + "name": "download_clients_name_lower_uidx", + "columns": [ + { + "expression": "lower(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "download_clients_enabled_priority_idx": { + "name": "download_clients_enabled_priority_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "download_clients_adapter_type_chk": { + "name": "download_clients_adapter_type_chk", + "value": "\"download_clients\".\"adapter_type\" in ('qbittorrent', 'transmission', 'deluge', 'nzbget', 'sabnzbd')" + }, + "download_clients_priority_range_chk": { + "name": "download_clients_priority_range_chk", + "value": "\"download_clients\".\"priority\" >= 1 and \"download_clients\".\"priority\" <= 100" + } + }, + "isRLSEnabled": false + }, + "public.request_indexers": { + "name": "request_indexers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "adapter_type": { + "name": "adapter_type", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "manager_id": { + "name": "manager_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "manager_external_id": { + "name": "manager_external_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "manager_available": { + "name": "manager_available", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "manager_last_seen_at": { + "name": "manager_last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "manager_metadata": { + "name": "manager_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentials_enc": { + "name": "credentials_enc", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allow_private_address": { + "name": "allow_private_address", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "apply_tracker_seed_goals": { + "name": "apply_tracker_seed_goals", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "seed_ratio_goal": { + "name": "seed_ratio_goal", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "seed_time_minutes": { + "name": "seed_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "disabled_media_kinds": { + "name": "disabled_media_kinds", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "isbn_search_disabled": { + "name": "isbn_search_disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "network_profile": { + "name": "network_profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_tested_at": { + "name": "last_tested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_ok": { + "name": "last_test_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_search_at": { + "name": "last_search_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_search_ok": { + "name": "last_search_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_search_error": { + "name": "last_search_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "search_failure_streak": { + "name": "search_failure_streak", + "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": { + "request_indexers_name_lower_uidx": { + "name": "request_indexers_name_lower_uidx", + "columns": [ + { + "expression": "lower(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "request_indexers_manager_external_uidx": { + "name": "request_indexers_manager_external_uidx", + "columns": [ + { + "expression": "manager_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "manager_external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "request_indexers_enabled_idx": { + "name": "request_indexers_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "request_indexers_manager_idx": { + "name": "request_indexers_manager_idx", + "columns": [ + { + "expression": "manager_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "request_indexers_manager_id_request_indexer_managers_id_fk": { + "name": "request_indexers_manager_id_request_indexer_managers_id_fk", + "tableFrom": "request_indexers", + "tableTo": "request_indexer_managers", + "columnsFrom": ["manager_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "request_indexers_adapter_type_chk": { + "name": "request_indexers_adapter_type_chk", + "value": "\"request_indexers\".\"adapter_type\" ~ '^[a-z0-9][a-z0-9-]{0,29}$'" + }, + "request_indexers_manager_fields_chk": { + "name": "request_indexers_manager_fields_chk", + "value": "(\"request_indexers\".\"manager_id\" is null and \"request_indexers\".\"manager_external_id\" is null and \"request_indexers\".\"manager_metadata\" is null) or (\"request_indexers\".\"manager_id\" is not null and \"request_indexers\".\"manager_external_id\" is not null and \"request_indexers\".\"manager_metadata\" is not null and \"request_indexers\".\"credentials_enc\" is null and \"request_indexers\".\"adapter_type\" in ('torznab', 'newznab'))" + }, + "request_indexers_seed_ratio_goal_chk": { + "name": "request_indexers_seed_ratio_goal_chk", + "value": "\"request_indexers\".\"seed_ratio_goal\" is null or (\"request_indexers\".\"seed_ratio_goal\" > 0 and \"request_indexers\".\"seed_ratio_goal\" < 'Infinity'::double precision)" + }, + "request_indexers_seed_time_minutes_chk": { + "name": "request_indexers_seed_time_minutes_chk", + "value": "\"request_indexers\".\"seed_time_minutes\" is null or \"request_indexers\".\"seed_time_minutes\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.request_indexer_managers": { + "name": "request_indexer_managers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credentials_enc": { + "name": "credentials_enc", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_private_address": { + "name": "allow_private_address", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sync_new_indexers": { + "name": "sync_new_indexers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "per_indexer_timeout_seconds": { + "name": "per_indexer_timeout_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20 + }, + "overall_search_budget_seconds": { + "name": "overall_search_budget_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "auto_expand_categories": { + "name": "auto_expand_categories", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inherit_seed_limits": { + "name": "inherit_seed_limits", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "network_profile": { + "name": "network_profile", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "last_tested_at": { + "name": "last_tested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_ok": { + "name": "last_test_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_ok": { + "name": "last_sync_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "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": { + "request_indexer_managers_name_lower_uidx": { + "name": "request_indexer_managers_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": { + "request_indexer_managers_type_chk": { + "name": "request_indexer_managers_type_chk", + "value": "\"request_indexer_managers\".\"type\" in ('prowlarr')" + }, + "request_indexer_managers_timeout_chk": { + "name": "request_indexer_managers_timeout_chk", + "value": "\"request_indexer_managers\".\"per_indexer_timeout_seconds\" between 5 and 180" + }, + "request_indexer_managers_budget_chk": { + "name": "request_indexer_managers_budget_chk", + "value": "\"request_indexer_managers\".\"overall_search_budget_seconds\" between 5 and 300" + } + }, + "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 + }, + "last_tested_at": { + "name": "last_tested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_ok": { + "name": "last_test_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_test_error": { + "name": "last_test_error", + "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": { + "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 + }, + "read_aloud_delivery": { + "name": "read_aloud_delivery", + "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', 'djvu')" + } + }, + "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 + }, + "delivery_policy": { + "name": "delivery_policy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "source_revision": { + "name": "source_revision", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "artifact_id": { + "name": "artifact_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "delivery_metadata": { + "name": "delivery_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "actual_variant": { + "name": "actual_variant", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "device_type": { + "name": "device_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "recipient_id": { + "name": "recipient_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dispatch_phase": { + "name": "dispatch_phase", + "type": "varchar(32)", + "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 + }, + "group_key": { + "name": "group_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "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": {} + }, + "notifications_user_updated_at_idx": { + "name": "notifications_user_updated_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_updated_at_idx": { + "name": "notifications_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notifications_user_unread_group_key_idx": { + "name": "notifications_user_unread_group_key_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"notifications\".\"read\" = false and \"notifications\".\"group_key\" is not null", + "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'" + }, + "weight_min": { + "name": "weight_min", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "weight_max": { + "name": "weight_max", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "instances": { + "name": "instances", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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\" >= 1 and \"server_fonts\".\"weight\" <= 1000" + }, + "server_fonts_style_chk": { + "name": "server_fonts_style_chk", + "value": "\"server_fonts\".\"style\" in ('normal', 'italic')" + }, + "server_fonts_weight_range_chk": { + "name": "server_fonts_weight_range_chk", + "value": "(\"server_fonts\".\"weight_min\" is null) = (\"server_fonts\".\"weight_max\" is null) and (\"server_fonts\".\"weight_min\" is null or (\"server_fonts\".\"weight_min\" >= 1 and \"server_fonts\".\"weight_max\" <= 1000 and \"server_fonts\".\"weight_min\" < \"server_fonts\".\"weight_max\"))" + } + }, + "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'" + }, + "weight_min": { + "name": "weight_min", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "weight_max": { + "name": "weight_max", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "instances": { + "name": "instances", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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\" >= 1 and \"user_fonts\".\"weight\" <= 1000" + }, + "user_fonts_style_chk": { + "name": "user_fonts_style_chk", + "value": "\"user_fonts\".\"style\" in ('normal', 'italic')" + }, + "user_fonts_weight_range_chk": { + "name": "user_fonts_weight_range_chk", + "value": "(\"user_fonts\".\"weight_min\" is null) = (\"user_fonts\".\"weight_max\" is null) and (\"user_fonts\".\"weight_min\" is null or (\"user_fonts\".\"weight_min\" >= 1 and \"user_fonts\".\"weight_max\" <= 1000 and \"user_fonts\".\"weight_min\" < \"user_fonts\".\"weight_max\"))" + } + }, + "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', 'kobo_download')" + } + }, + "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_delivery_settings": { + "name": "koreader_device_delivery_settings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "read_aloud_delivery": { + "name": "read_aloud_delivery", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "koreader_device_delivery_settings_user_id_users_id_fk": { + "name": "koreader_device_delivery_settings_user_id_users_id_fk", + "tableFrom": "koreader_device_delivery_settings", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "koreader_device_delivery_settings_credential_id_koreader_users_id_fk": { + "name": "koreader_device_delivery_settings_credential_id_koreader_users_id_fk", + "tableFrom": "koreader_device_delivery_settings", + "tableTo": "koreader_users", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "koreader_device_delivery_settings_user_id_device_id_pk": { + "name": "koreader_device_delivery_settings_user_id_device_id_pk", + "columns": ["user_id", "device_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "koreader_device_delivery_chk": { + "name": "koreader_device_delivery_chk", + "value": "\"koreader_device_delivery_settings\".\"read_aloud_delivery\" in ('original', 'audioless_epub')" + } + }, + "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_session_tombstones": { + "name": "koreader_session_tombstones", + "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_key": { + "name": "device_key", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "start_time": { + "name": "start_time", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "end_time": { + "name": "end_time", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kst_user_file_device_start_idx": { + "name": "kst_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_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "start_time", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "koreader_session_tombstones_book_file_id_idx": { + "name": "koreader_session_tombstones_book_file_id_idx", + "columns": [ + { + "expression": "book_file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "koreader_session_tombstones_user_id_users_id_fk": { + "name": "koreader_session_tombstones_user_id_users_id_fk", + "tableFrom": "koreader_session_tombstones", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "koreader_session_tombstones_book_file_id_book_files_id_fk": { + "name": "koreader_session_tombstones_book_file_id_book_files_id_fk", + "tableFrom": "koreader_session_tombstones", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "koreader_session_tombstones_range_chk": { + "name": "koreader_session_tombstones_range_chk", + "value": "\"koreader_session_tombstones\".\"end_time\" > \"koreader_session_tombstones\".\"start_time\"" + } + }, + "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 + }, + "manual_link_requested": { + "name": "manual_link_requested", + "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 + }, + "read_aloud_delivery": { + "name": "read_aloud_delivery", + "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": { + "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": { + "koreader_users_delivery_chk": { + "name": "koreader_users_delivery_chk", + "value": "\"koreader_users\".\"read_aloud_delivery\" in ('original', 'audioless_epub')" + } + }, + "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 + }, + "celebration_claim_id": { + "name": "celebration_claim_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "celebration_claimed_at": { + "name": "celebration_claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "celebrated_at": { + "name": "celebrated_at", + "type": "timestamp with time zone", + "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": {} + }, + "user_achievements_pending_celebration_idx": { + "name": "user_achievements_pending_celebration_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "celebrated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "awarded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_achievements_celebration_claim_uidx": { + "name": "user_achievements_celebration_claim_uidx", + "columns": [ + { + "expression": "celebration_claim_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "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_basis": { + "name": "match_basis", + "type": "text", + "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 + }, + "public.tts_book_preferences": { + "name": "tts_book_preferences", + "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 + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "voice_id": { + "name": "voice_id", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "speed": { + "name": "speed", + "type": "real", + "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": { + "tts_book_prefs_user_book_uidx": { + "name": "tts_book_prefs_user_book_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "book_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tts_book_preferences_user_id_users_id_fk": { + "name": "tts_book_preferences_user_id_users_id_fk", + "tableFrom": "tts_book_preferences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tts_book_preferences_book_id_books_id_fk": { + "name": "tts_book_preferences_book_id_books_id_fk", + "tableFrom": "tts_book_preferences", + "tableTo": "books", + "columnsFrom": ["book_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tts_book_preferences_provider_id_tts_providers_id_fk": { + "name": "tts_book_preferences_provider_id_tts_providers_id_fk", + "tableFrom": "tts_book_preferences", + "tableTo": "tts_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tts_providers": { + "name": "tts_providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "base_url": { + "name": "base_url", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "api_key": { + "name": "api_key", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "default_model": { + "name": "default_model", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "static_voices": { + "name": "static_voices", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "supports_voice_discovery": { + "name": "supports_voice_discovery", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tts_reading_position": { + "name": "tts_reading_position", + "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 + }, + "cfi": { + "name": "cfi", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chapter_index": { + "name": "chapter_index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tts_reading_pos_user_file_uidx": { + "name": "tts_reading_pos_user_file_uidx", + "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": {} + } + }, + "foreignKeys": { + "tts_reading_position_user_id_users_id_fk": { + "name": "tts_reading_position_user_id_users_id_fk", + "tableFrom": "tts_reading_position", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tts_reading_position_book_file_id_book_files_id_fk": { + "name": "tts_reading_position_book_file_id_book_files_id_fk", + "tableFrom": "tts_reading_position", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tts_user_preferences": { + "name": "tts_user_preferences", + "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 + }, + "voice_id": { + "name": "voice_id", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "speed": { + "name": "speed", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "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": { + "tts_user_prefs_user_uidx": { + "name": "tts_user_prefs_user_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tts_user_preferences_user_id_users_id_fk": { + "name": "tts_user_preferences_user_id_users_id_fk", + "tableFrom": "tts_user_preferences", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tts_user_preferences_provider_id_tts_providers_id_fk": { + "name": "tts_user_preferences_provider_id_tts_providers_id_fk", + "tableFrom": "tts_user_preferences", + "tableTo": "tts_providers", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.upload_sessions": { + "name": "upload_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "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 + }, + "target_book_id": { + "name": "target_book_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "filename": { + "name": "filename", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "received_bytes": { + "name": "received_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "staging_path": { + "name": "staging_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'receiving'" + }, + "expected_sha256": { + "name": "expected_sha256", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_book_id": { + "name": "result_book_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "result_book_dock_file_id": { + "name": "result_book_dock_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "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()" + }, + "updated_at": { + "name": "updated_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": { + "upload_sessions_user_idempotency_uidx": { + "name": "upload_sessions_user_idempotency_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "upload_sessions_user_created_idx": { + "name": "upload_sessions_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": {} + }, + "upload_sessions_status_expires_idx": { + "name": "upload_sessions_status_expires_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "upload_sessions_user_id_users_id_fk": { + "name": "upload_sessions_user_id_users_id_fk", + "tableFrom": "upload_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "upload_sessions_target_library_id_libraries_id_fk": { + "name": "upload_sessions_target_library_id_libraries_id_fk", + "tableFrom": "upload_sessions", + "tableTo": "libraries", + "columnsFrom": ["target_library_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "upload_sessions_target_folder_id_library_folders_id_fk": { + "name": "upload_sessions_target_folder_id_library_folders_id_fk", + "tableFrom": "upload_sessions", + "tableTo": "library_folders", + "columnsFrom": ["target_folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "upload_sessions_target_book_id_books_id_fk": { + "name": "upload_sessions_target_book_id_books_id_fk", + "tableFrom": "upload_sessions", + "tableTo": "books", + "columnsFrom": ["target_book_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "upload_sessions_result_book_id_books_id_fk": { + "name": "upload_sessions_result_book_id_books_id_fk", + "tableFrom": "upload_sessions", + "tableTo": "books", + "columnsFrom": ["result_book_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "upload_sessions_staging_path_unique": { + "name": "upload_sessions_staging_path_unique", + "nullsNotDistinct": false, + "columns": ["staging_path"] + } + }, + "policies": {}, + "checkConstraints": { + "upload_sessions_target_kind_chk": { + "name": "upload_sessions_target_kind_chk", + "value": "\"upload_sessions\".\"target_kind\" in ('library', 'existing_book', 'book_dock')" + }, + "upload_sessions_status_chk": { + "name": "upload_sessions_status_chk", + "value": "\"upload_sessions\".\"status\" in ('receiving', 'processing', 'completed', 'failed', 'cancelled', 'expired')" + }, + "upload_sessions_size_positive_chk": { + "name": "upload_sessions_size_positive_chk", + "value": "\"upload_sessions\".\"size_bytes\" > 0" + }, + "upload_sessions_received_range_chk": { + "name": "upload_sessions_received_range_chk", + "value": "\"upload_sessions\".\"received_bytes\" >= 0 and \"upload_sessions\".\"received_bytes\" <= \"upload_sessions\".\"size_bytes\"" + } + }, + "isRLSEnabled": false + }, + "public.podcast_bookmarks": { + "name": "podcast_bookmarks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "episode_id": { + "name": "episode_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position_seconds": { + "name": "position_seconds", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "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": { + "podcast_bookmarks_user_episode_idx": { + "name": "podcast_bookmarks_user_episode_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "episode_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_bookmarks_user_episode_position_idx": { + "name": "podcast_bookmarks_user_episode_position_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "episode_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position_seconds", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "podcast_bookmarks_user_id_users_id_fk": { + "name": "podcast_bookmarks_user_id_users_id_fk", + "tableFrom": "podcast_bookmarks", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "podcast_bookmarks_episode_id_podcast_episodes_id_fk": { + "name": "podcast_bookmarks_episode_id_podcast_episodes_id_fk", + "tableFrom": "podcast_bookmarks", + "tableTo": "podcast_episodes", + "columnsFrom": ["episode_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "podcast_bookmarks_position_nonnegative_chk": { + "name": "podcast_bookmarks_position_nonnegative_chk", + "value": "\"podcast_bookmarks\".\"position_seconds\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.podcast_episode_media": { + "name": "podcast_episode_media", + "schema": "", + "columns": { + "episode_id": { + "name": "episode_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'remote'" + }, + "local_path": { + "name": "local_path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": false + }, + "file_name": { + "name": "file_name", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "varchar(30)", + "primaryKey": false, + "notNull": false + }, + "mime_type": { + "name": "mime_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "checksum": { + "name": "checksum", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "varchar(2000)", + "primaryKey": false, + "notNull": false + }, + "downloaded_at": { + "name": "downloaded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_remote_validated_at": { + "name": "last_remote_validated_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": { + "podcast_episode_media_status_idx": { + "name": "podcast_episode_media_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_episode_media_downloaded_idx": { + "name": "podcast_episode_media_downloaded_idx", + "columns": [ + { + "expression": "downloaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "podcast_episode_media_episode_id_podcast_episodes_id_fk": { + "name": "podcast_episode_media_episode_id_podcast_episodes_id_fk", + "tableFrom": "podcast_episode_media", + "tableTo": "podcast_episodes", + "columnsFrom": ["episode_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "podcast_episode_media_status_chk": { + "name": "podcast_episode_media_status_chk", + "value": "\"podcast_episode_media\".\"status\" in ('remote', 'queued', 'downloading', 'local', 'failed', 'unavailable')" + }, + "podcast_episode_media_size_nonnegative_chk": { + "name": "podcast_episode_media_size_nonnegative_chk", + "value": "\"podcast_episode_media\".\"size_bytes\" is null or \"podcast_episode_media\".\"size_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.podcast_episodes": { + "name": "podcast_episodes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "podcast_id": { + "name": "podcast_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "identity_hash": { + "name": "identity_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'feed'" + }, + "guid": { + "name": "guid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar(2000)", + "primaryKey": false, + "notNull": true + }, + "subtitle": { + "name": "subtitle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "season": { + "name": "season", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "episode": { + "name": "episode", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "episode_type": { + "name": "episode_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "duration_seconds": { + "name": "duration_seconds", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "explicit": { + "name": "explicit", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enclosure_url_encrypted": { + "name": "enclosure_url_encrypted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enclosure_url_hash": { + "name": "enclosure_url_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enclosure_type": { + "name": "enclosure_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "enclosure_size_bytes": { + "name": "enclosure_size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "chapters": { + "name": "chapters", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "transcripts": { + "name": "transcripts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "locked_fields": { + "name": "locked_fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "in_feed": { + "name": "in_feed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "unavailable_at": { + "name": "unavailable_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": { + "podcast_episodes_podcast_identity_uidx": { + "name": "podcast_episodes_podcast_identity_uidx", + "columns": [ + { + "expression": "podcast_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "identity_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_episodes_podcast_id_idx": { + "name": "podcast_episodes_podcast_id_idx", + "columns": [ + { + "expression": "podcast_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_episodes_podcast_created_idx": { + "name": "podcast_episodes_podcast_created_idx", + "columns": [ + { + "expression": "podcast_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_episodes_podcast_published_id_idx": { + "name": "podcast_episodes_podcast_published_id_idx", + "columns": [ + { + "expression": "podcast_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"published_at\" desc nulls last", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_episodes_title_trgm_idx": { + "name": "podcast_episodes_title_trgm_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "podcast_episodes_duration_idx": { + "name": "podcast_episodes_duration_idx", + "columns": [ + { + "expression": "duration_seconds", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_episodes_enclosure_hash_idx": { + "name": "podcast_episodes_enclosure_hash_idx", + "columns": [ + { + "expression": "enclosure_url_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_episodes_in_feed_idx": { + "name": "podcast_episodes_in_feed_idx", + "columns": [ + { + "expression": "podcast_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "in_feed", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "podcast_episodes_podcast_id_podcasts_id_fk": { + "name": "podcast_episodes_podcast_id_podcasts_id_fk", + "tableFrom": "podcast_episodes", + "tableTo": "podcasts", + "columnsFrom": ["podcast_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "podcast_episodes_origin_chk": { + "name": "podcast_episodes_origin_chk", + "value": "\"podcast_episodes\".\"origin\" in ('feed', 'local')" + }, + "podcast_episodes_feed_origin_requires_enclosure_chk": { + "name": "podcast_episodes_feed_origin_requires_enclosure_chk", + "value": "\"podcast_episodes\".\"origin\" = 'local' or \"podcast_episodes\".\"enclosure_url_hash\" is not null" + }, + "podcast_episodes_duration_nonnegative_chk": { + "name": "podcast_episodes_duration_nonnegative_chk", + "value": "\"podcast_episodes\".\"duration_seconds\" is null or \"podcast_episodes\".\"duration_seconds\" >= 0" + }, + "podcast_episodes_size_nonnegative_chk": { + "name": "podcast_episodes_size_nonnegative_chk", + "value": "\"podcast_episodes\".\"enclosure_size_bytes\" is null or \"podcast_episodes\".\"enclosure_size_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.podcast_feed_aliases": { + "name": "podcast_feed_aliases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "podcast_id": { + "name": "podcast_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url_hash": { + "name": "url_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "url_encrypted": { + "name": "url_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "podcast_feed_aliases_podcast_hash_uidx": { + "name": "podcast_feed_aliases_podcast_hash_uidx", + "columns": [ + { + "expression": "podcast_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_feed_aliases_hash_idx": { + "name": "podcast_feed_aliases_hash_idx", + "columns": [ + { + "expression": "url_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "podcast_feed_aliases_podcast_id_podcasts_id_fk": { + "name": "podcast_feed_aliases_podcast_id_podcasts_id_fk", + "tableFrom": "podcast_feed_aliases", + "tableTo": "podcasts", + "columnsFrom": ["podcast_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.podcast_jobs": { + "name": "podcast_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "podcast_id": { + "name": "podcast_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "episode_id": { + "name": "episode_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "download_batch_id": { + "name": "download_batch_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "progress_current": { + "name": "progress_current", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "progress_total": { + "name": "progress_total", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "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()" + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "varchar(2000)", + "primaryKey": false, + "notNull": false + }, + "cancel_requested": { + "name": "cancel_requested", + "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": { + "podcast_jobs_active_dedupe_uidx": { + "name": "podcast_jobs_active_dedupe_uidx", + "columns": [ + { + "expression": "dedupe_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"podcast_jobs\".\"status\" in ('queued', 'processing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_jobs_due_idx": { + "name": "podcast_jobs_due_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_jobs_library_status_idx": { + "name": "podcast_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": {} + }, + "podcast_jobs_status_updated_idx": { + "name": "podcast_jobs_status_updated_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_jobs_download_batch_idx": { + "name": "podcast_jobs_download_batch_idx", + "columns": [ + { + "expression": "requested_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "download_batch_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"podcast_jobs\".\"type\" = 'download' and \"podcast_jobs\".\"download_batch_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_jobs_active_podcast_type_idx": { + "name": "podcast_jobs_active_podcast_type_idx", + "columns": [ + { + "expression": "podcast_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"podcast_jobs\".\"status\" in ('queued', 'processing') and \"podcast_jobs\".\"podcast_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_jobs_active_episode_type_idx": { + "name": "podcast_jobs_active_episode_type_idx", + "columns": [ + { + "expression": "episode_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"podcast_jobs\".\"status\" in ('queued', 'processing') and \"podcast_jobs\".\"episode_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "podcast_jobs_library_id_libraries_id_fk": { + "name": "podcast_jobs_library_id_libraries_id_fk", + "tableFrom": "podcast_jobs", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "podcast_jobs_podcast_id_podcasts_id_fk": { + "name": "podcast_jobs_podcast_id_podcasts_id_fk", + "tableFrom": "podcast_jobs", + "tableTo": "podcasts", + "columnsFrom": ["podcast_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "podcast_jobs_episode_id_podcast_episodes_id_fk": { + "name": "podcast_jobs_episode_id_podcast_episodes_id_fk", + "tableFrom": "podcast_jobs", + "tableTo": "podcast_episodes", + "columnsFrom": ["episode_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "podcast_jobs_requested_by_user_id_users_id_fk": { + "name": "podcast_jobs_requested_by_user_id_users_id_fk", + "tableFrom": "podcast_jobs", + "tableTo": "users", + "columnsFrom": ["requested_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "podcast_jobs_type_chk": { + "name": "podcast_jobs_type_chk", + "value": "\"podcast_jobs\".\"type\" in ('refresh', 'reparse', 'download', 'retention', 'purge', 'opml_import', 'import_scan', 'local_import', 'merge', 'digest', 'file_cleanup')" + }, + "podcast_jobs_status_chk": { + "name": "podcast_jobs_status_chk", + "value": "\"podcast_jobs\".\"status\" in ('queued', 'processing', 'completed', 'failed', 'cancelled')" + }, + "podcast_jobs_attempt_nonnegative_chk": { + "name": "podcast_jobs_attempt_nonnegative_chk", + "value": "\"podcast_jobs\".\"attempt_count\" >= 0" + }, + "podcast_jobs_progress_nonnegative_chk": { + "name": "podcast_jobs_progress_nonnegative_chk", + "value": "\"podcast_jobs\".\"progress_current\" >= 0 and (\"podcast_jobs\".\"progress_total\" is null or \"podcast_jobs\".\"progress_total\" >= 0)" + } + }, + "isRLSEnabled": false + }, + "public.podcast_library_settings": { + "name": "podcast_library_settings", + "schema": "", + "columns": { + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "storage_quota_bytes": { + "name": "storage_quota_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "107374182400" + }, + "minimum_free_space_bytes": { + "name": "minimum_free_space_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "5368709120" + }, + "default_refresh_interval_minutes": { + "name": "default_refresh_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "completion_remaining_seconds": { + "name": "completion_remaining_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "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": { + "podcast_library_settings_library_id_libraries_id_fk": { + "name": "podcast_library_settings_library_id_libraries_id_fk", + "tableFrom": "podcast_library_settings", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "podcast_library_storage_quota_nonnegative_chk": { + "name": "podcast_library_storage_quota_nonnegative_chk", + "value": "\"podcast_library_settings\".\"storage_quota_bytes\" >= 0" + }, + "podcast_library_free_space_nonnegative_chk": { + "name": "podcast_library_free_space_nonnegative_chk", + "value": "\"podcast_library_settings\".\"minimum_free_space_bytes\" >= 0" + }, + "podcast_library_refresh_positive_chk": { + "name": "podcast_library_refresh_positive_chk", + "value": "\"podcast_library_settings\".\"default_refresh_interval_minutes\" > 0" + }, + "podcast_library_completion_nonnegative_chk": { + "name": "podcast_library_completion_nonnegative_chk", + "value": "\"podcast_library_settings\".\"completion_remaining_seconds\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.podcast_listening_sessions": { + "name": "podcast_listening_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "episode_id": { + "name": "episode_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "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 + }, + "end_position_seconds": { + "name": "end_position_seconds", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "podcast_sessions_user_session_uidx": { + "name": "podcast_sessions_user_session_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": {} + }, + "podcast_sessions_user_started_idx": { + "name": "podcast_sessions_user_started_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcast_sessions_episode_started_idx": { + "name": "podcast_sessions_episode_started_idx", + "columns": [ + { + "expression": "episode_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "podcast_listening_sessions_user_id_users_id_fk": { + "name": "podcast_listening_sessions_user_id_users_id_fk", + "tableFrom": "podcast_listening_sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "podcast_listening_sessions_episode_id_podcast_episodes_id_fk": { + "name": "podcast_listening_sessions_episode_id_podcast_episodes_id_fk", + "tableFrom": "podcast_listening_sessions", + "tableTo": "podcast_episodes", + "columnsFrom": ["episode_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "podcast_sessions_duration_nonnegative_chk": { + "name": "podcast_sessions_duration_nonnegative_chk", + "value": "\"podcast_listening_sessions\".\"duration_seconds\" >= 0" + }, + "podcast_sessions_position_nonnegative_chk": { + "name": "podcast_sessions_position_nonnegative_chk", + "value": "\"podcast_listening_sessions\".\"end_position_seconds\" >= 0" + }, + "podcast_sessions_time_order_chk": { + "name": "podcast_sessions_time_order_chk", + "value": "\"podcast_listening_sessions\".\"ended_at\" >= \"podcast_listening_sessions\".\"started_at\"" + } + }, + "isRLSEnabled": false + }, + "public.podcasts": { + "name": "podcasts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "library_id": { + "name": "library_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": true + }, + "author": { + "name": "author", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'feed'" + }, + "feed_url_encrypted": { + "name": "feed_url_encrypted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feed_url_hash": { + "name": "feed_url_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "local_folder_path": { + "name": "local_folder_path", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": false + }, + "image_url_encrypted": { + "name": "image_url_encrypted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "site_url": { + "name": "site_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "podcast_type": { + "name": "podcast_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "explicit": { + "name": "explicit", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "categories": { + "name": "categories", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "acquisition_policy": { + "name": "acquisition_policy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'remote_only'" + }, + "auto_download_limit": { + "name": "auto_download_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auto_download_window_days": { + "name": "auto_download_window_days", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "download_cleanup": { + "name": "download_cleanup", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'keep'" + }, + "download_cleanup_delay_hours": { + "name": "download_cleanup_delay_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "refresh_interval_minutes": { + "name": "refresh_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "next_refresh_at": { + "name": "next_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_refresh_at": { + "name": "last_refresh_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_refresh_success_at": { + "name": "last_refresh_success_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "varchar(2000)", + "primaryKey": false, + "notNull": false + }, + "last_http_status": { + "name": "last_http_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "etag": { + "name": "etag", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "last_modified": { + "name": "last_modified", + "type": "varchar(1000)", + "primaryKey": false, + "notNull": false + }, + "locked_fields": { + "name": "locked_fields", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_artwork_at": { + "name": "custom_artwork_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "feed_snapshot_at": { + "name": "feed_snapshot_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "missing_at": { + "name": "missing_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": { + "podcasts_library_feed_hash_uidx": { + "name": "podcasts_library_feed_hash_uidx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "feed_url_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"podcasts\".\"feed_url_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcasts_library_local_folder_uidx": { + "name": "podcasts_library_local_folder_uidx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "local_folder_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"podcasts\".\"local_folder_path\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcasts_library_archived_title_idx": { + "name": "podcasts_library_archived_title_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcasts_title_trgm_idx": { + "name": "podcasts_title_trgm_idx", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "podcasts_author_trgm_idx": { + "name": "podcasts_author_trgm_idx", + "columns": [ + { + "expression": "author", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "podcasts_due_refresh_idx": { + "name": "podcasts_due_refresh_idx", + "columns": [ + { + "expression": "next_refresh_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"podcasts\".\"archived_at\" is null and \"podcasts\".\"origin\" = 'feed'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "podcasts_library_cleanup_idx": { + "name": "podcasts_library_cleanup_idx", + "columns": [ + { + "expression": "library_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"podcasts\".\"download_cleanup\" <> 'keep'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "podcasts_library_id_libraries_id_fk": { + "name": "podcasts_library_id_libraries_id_fk", + "tableFrom": "podcasts", + "tableTo": "libraries", + "columnsFrom": ["library_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "podcasts_origin_chk": { + "name": "podcasts_origin_chk", + "value": "\"podcasts\".\"origin\" in ('feed', 'local')" + }, + "podcasts_feed_origin_requires_feed_chk": { + "name": "podcasts_feed_origin_requires_feed_chk", + "value": "\"podcasts\".\"origin\" = 'local' or \"podcasts\".\"feed_url_hash\" is not null" + }, + "podcasts_acquisition_policy_chk": { + "name": "podcasts_acquisition_policy_chk", + "value": "\"podcasts\".\"acquisition_policy\" in ('remote_only', 'manual', 'newest', 'window')" + }, + "podcasts_download_cleanup_chk": { + "name": "podcasts_download_cleanup_chk", + "value": "\"podcasts\".\"download_cleanup\" in ('keep', 'after_finished')" + }, + "podcasts_download_cleanup_delay_chk": { + "name": "podcasts_download_cleanup_delay_chk", + "value": "\"podcasts\".\"download_cleanup_delay_hours\" >= 0" + }, + "podcasts_refresh_interval_positive_chk": { + "name": "podcasts_refresh_interval_positive_chk", + "value": "\"podcasts\".\"refresh_interval_minutes\" > 0" + }, + "podcasts_failures_nonnegative_chk": { + "name": "podcasts_failures_nonnegative_chk", + "value": "\"podcasts\".\"consecutive_failures\" >= 0" + }, + "podcasts_auto_download_limit_chk": { + "name": "podcasts_auto_download_limit_chk", + "value": "\"podcasts\".\"auto_download_limit\" is null or \"podcasts\".\"auto_download_limit\" > 0" + }, + "podcasts_auto_download_window_chk": { + "name": "podcasts_auto_download_window_chk", + "value": "\"podcasts\".\"auto_download_window_days\" is null or \"podcasts\".\"auto_download_window_days\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.user_podcast_episode_state": { + "name": "user_podcast_episode_state", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "episode_id": { + "name": "episode_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position_seconds": { + "name": "position_seconds", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "progress_percent": { + "name": "progress_percent", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "finished": { + "name": "finished", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_listened_at": { + "name": "last_listened_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": { + "user_podcast_episode_state_user_finished_idx": { + "name": "user_podcast_episode_state_user_finished_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "finished", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_podcast_episode_state_episode_active_idx": { + "name": "user_podcast_episode_state_episode_active_idx", + "columns": [ + { + "expression": "episode_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "finished", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position_seconds", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_podcast_episode_state_user_id_users_id_fk": { + "name": "user_podcast_episode_state_user_id_users_id_fk", + "tableFrom": "user_podcast_episode_state", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_podcast_episode_state_episode_id_podcast_episodes_id_fk": { + "name": "user_podcast_episode_state_episode_id_podcast_episodes_id_fk", + "tableFrom": "user_podcast_episode_state", + "tableTo": "podcast_episodes", + "columnsFrom": ["episode_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_podcast_episode_state_user_id_episode_id_pk": { + "name": "user_podcast_episode_state_user_id_episode_id_pk", + "columns": ["user_id", "episode_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_podcast_episode_state_position_chk": { + "name": "user_podcast_episode_state_position_chk", + "value": "\"user_podcast_episode_state\".\"position_seconds\" >= 0" + }, + "user_podcast_episode_state_progress_chk": { + "name": "user_podcast_episode_state_progress_chk", + "value": "\"user_podcast_episode_state\".\"progress_percent\" >= 0 and \"user_podcast_episode_state\".\"progress_percent\" <= 100" + } + }, + "isRLSEnabled": false + }, + "public.user_podcast_follows": { + "name": "user_podcast_follows", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "podcast_id": { + "name": "podcast_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "notification_mode": { + "name": "notification_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'off'" + }, + "last_notified_at": { + "name": "last_notified_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()" + } + }, + "indexes": { + "user_podcast_follows_podcast_idx": { + "name": "user_podcast_follows_podcast_idx", + "columns": [ + { + "expression": "podcast_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_podcast_follows_due_digest_idx": { + "name": "user_podcast_follows_due_digest_idx", + "columns": [ + { + "expression": "notification_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_notified_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "podcast_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_podcast_follows\".\"notification_mode\" in ('daily', 'weekly')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_podcast_follows_user_id_users_id_fk": { + "name": "user_podcast_follows_user_id_users_id_fk", + "tableFrom": "user_podcast_follows", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_podcast_follows_podcast_id_podcasts_id_fk": { + "name": "user_podcast_follows_podcast_id_podcasts_id_fk", + "tableFrom": "user_podcast_follows", + "tableTo": "podcasts", + "columnsFrom": ["podcast_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_podcast_follows_user_id_podcast_id_pk": { + "name": "user_podcast_follows_user_id_podcast_id_pk", + "columns": ["user_id", "podcast_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_podcast_follows_notification_chk": { + "name": "user_podcast_follows_notification_chk", + "value": "\"user_podcast_follows\".\"notification_mode\" in ('off', 'immediate', 'daily', 'weekly')" + } + }, + "isRLSEnabled": false + }, + "public.user_podcast_queue": { + "name": "user_podcast_queue", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "episode_id": { + "name": "episode_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_podcast_queue_user_position_uidx": { + "name": "user_podcast_queue_user_position_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_podcast_queue_user_id_users_id_fk": { + "name": "user_podcast_queue_user_id_users_id_fk", + "tableFrom": "user_podcast_queue", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_podcast_queue_episode_id_podcast_episodes_id_fk": { + "name": "user_podcast_queue_episode_id_podcast_episodes_id_fk", + "tableFrom": "user_podcast_queue", + "tableTo": "podcast_episodes", + "columnsFrom": ["episode_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_podcast_queue_user_id_episode_id_pk": { + "name": "user_podcast_queue_user_id_episode_id_pk", + "columns": ["user_id", "episode_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_podcast_queue_position_nonnegative_chk": { + "name": "user_podcast_queue_position_nonnegative_chk", + "value": "\"user_podcast_queue\".\"position\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.provider_metadata_cache": { + "name": "provider_metadata_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "char(64)", + "primaryKey": false, + "notNull": true + }, + "namespace": { + "name": "namespace", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_version": { + "name": "provider_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "config_fingerprint": { + "name": "config_fingerprint", + "type": "char(16)", + "primaryKey": false, + "notNull": true + }, + "max_candidates": { + "name": "max_candidates", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "covers_validated": { + "name": "covers_validated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_bytes": { + "name": "payload_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "upstream_ms": { + "name": "upstream_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "provider_metadata_cache_ns_provider_fetched_idx": { + "name": "provider_metadata_cache_ns_provider_fetched_idx", + "columns": [ + { + "expression": "namespace", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "fetched_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_metadata_cache_last_accessed_idx": { + "name": "provider_metadata_cache_last_accessed_idx", + "columns": [ + { + "expression": "last_accessed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_metadata_cache_key_hash_unique": { + "name": "provider_metadata_cache_key_hash_unique", + "nullsNotDistinct": false, + "columns": ["key_hash"] + } + }, + "policies": {}, + "checkConstraints": { + "provider_metadata_cache_namespace_chk": { + "name": "provider_metadata_cache_namespace_chk", + "value": "\"provider_metadata_cache\".\"namespace\" in ('book', 'author')" + }, + "provider_metadata_cache_operation_chk": { + "name": "provider_metadata_cache_operation_chk", + "value": "\"provider_metadata_cache\".\"operation\" in ('search', 'lookup')" + }, + "provider_metadata_cache_status_chk": { + "name": "provider_metadata_cache_status_chk", + "value": "\"provider_metadata_cache\".\"status\" in ('results', 'empty', 'found', 'not_found')" + }, + "provider_metadata_cache_payload_bytes_nonnegative_chk": { + "name": "provider_metadata_cache_payload_bytes_nonnegative_chk", + "value": "\"provider_metadata_cache\".\"payload_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.delivery_artifacts": { + "name": "delivery_artifacts", + "schema": "", + "columns": { + "selection_id": { + "name": "selection_id", + "type": "varchar(64)", + "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 + }, + "scope": { + "name": "scope", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "delivery_artifacts_owner_file_idx": { + "name": "delivery_artifacts_owner_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": {} + } + }, + "foreignKeys": { + "delivery_artifacts_user_id_users_id_fk": { + "name": "delivery_artifacts_user_id_users_id_fk", + "tableFrom": "delivery_artifacts", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "delivery_artifacts_book_file_id_book_files_id_fk": { + "name": "delivery_artifacts_book_file_id_book_files_id_fk", + "tableFrom": "delivery_artifacts", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.delivery_inspections": { + "name": "delivery_inspections", + "schema": "", + "columns": { + "book_file_id": { + "name": "book_file_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "source_revision": { + "name": "source_revision", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "detector_version": { + "name": "detector_version", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "varchar(24)", + "primaryKey": false, + "notNull": true + }, + "retry_at": { + "name": "retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "checked_at": { + "name": "checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "delivery_inspections_book_file_id_book_files_id_fk": { + "name": "delivery_inspections_book_file_id_book_files_id_fk", + "tableFrom": "delivery_inspections", + "tableTo": "book_files", + "columnsFrom": ["book_file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.server_identity": { + "name": "server_identity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "default": 1 + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "server_identity_singleton": { + "name": "server_identity_singleton", + "value": "\"server_identity\".\"id\" = 1" + } + }, + "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"] + }, + "public.book_request_media_kind": { + "name": "book_request_media_kind", + "schema": "public", + "values": ["ebook", "audiobook", "comic"] + } + }, + "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 604cf7ed0..738389234 100644 --- a/server/src/db/migrations/meta/_journal.json +++ b/server/src/db/migrations/meta/_journal.json @@ -820,6 +820,13 @@ "when": 1792185442568, "tag": "0116_regex_metadata", "breakpoints": true + }, + { + "idx": 117, + "version": "7", + "when": 1792191772317, + "tag": "0117_add_reading_session_sync_interval", + "breakpoints": true } ] } diff --git a/server/src/db/schema/reader.ts b/server/src/db/schema/reader.ts index b5006dad5..5a374af86 100644 --- a/server/src/db/schema/reader.ts +++ b/server/src/db/schema/reader.ts @@ -308,6 +308,8 @@ export const readingSessions = pgTable( // Nullable: CBX with no percentage tracking may omit these. progressDelta: real('progress_delta'), endProgress: real('end_progress'), + // Counter-derived sync estimates only: the previous counter push, where the interval the estimate stands for begins. + syncIntervalStartedAt: timestamp('sync_interval_started_at', { withTimezone: true }), }, (t) => [ uniqueIndex('rs_user_session_id_uidx').on(t.userId, t.sessionId), diff --git a/server/src/modules/kobo/kobo-statistics-session.util.ts b/server/src/modules/kobo/kobo-statistics-session.util.ts index f5791e11d..07d6b367b 100644 --- a/server/src/modules/kobo/kobo-statistics-session.util.ts +++ b/server/src/modules/kobo/kobo-statistics-session.util.ts @@ -1,6 +1,6 @@ /** * Identifiers for reading sessions derived from the reading counter a Kobo reports alongside - * its bookmark, rather than measured from the analytics events it no longer sends. + * its bookmark, rather than measured from the analytics events only some devices still send. * * A reading session carries no column saying where it came from beyond `source`, and `source` * is `kobo` for both kinds. The prefix is what lets the derivation find, and exclude itself diff --git a/server/src/modules/kobo/services/kobo-reading-state.service.ts b/server/src/modules/kobo/services/kobo-reading-state.service.ts index 9328aad74..b8d76e986 100644 --- a/server/src/modules/kobo/services/kobo-reading-state.service.ts +++ b/server/src/modules/kobo/services/kobo-reading-state.service.ts @@ -312,16 +312,19 @@ export class KoboReadingStateService { /** * Turns the reading time a device reports alongside its bookmark into a reading session. * - * Current Kobo firmware no longer emits the `LeaveContent` analytics events the reading log - * was built on, so `POST /v1/analytics/event` never arrives and every Kobo statistic reads - * zero however much the device is read. What the device does still send, on every state push, - * is its own `SpentReadingMinutes` - the counter it keeps as `content.TimeSpentReading` on - * device, and the only reading time these devices offer at all. + * Some Kobo devices no longer emit the `LeaveContent` analytics events the reading log was + * built on, so `POST /v1/analytics/event` never arrives and every Kobo statistic reads zero + * however much the device is read. What every device does still send, on every state push, is + * its own `SpentReadingMinutes` - the counter it keeps as `content.TimeSpentReading` on device, + * and the only reading time those devices offer at all. Others still send both: a Libra Colour + * on firmware 4.45.23792 does, and after offline reading it pushes the counter and the events + * together with its next sync, long after the reading. * * Each device has an independent durable cursor. Session insertion, daily aggregation, and * cursor advancement share one transaction so a failed write is retried from the same counter. * Measured analytics sessions and counter-derived sessions are serialized by the same lock and - * reconciled by overlap, regardless of which endpoint arrives first. + * reconciled on the counter interval each estimate stands for, regardless of which endpoint + * arrives first. * * Failures are swallowed. A device retries the entire push when the response is not a clean * acknowledgement, so a session that cannot be stored must not take the bookmark down with it. diff --git a/server/src/modules/reading-session/reading-session-cumulative.repository.test.ts b/server/src/modules/reading-session/reading-session-cumulative.repository.test.ts index edc15533b..5c0a2ec38 100644 --- a/server/src/modules/reading-session/reading-session-cumulative.repository.test.ts +++ b/server/src/modules/reading-session/reading-session-cumulative.repository.test.ts @@ -1,3 +1,6 @@ +import type { SQL } from 'drizzle-orm'; +import { PgDialect } from 'drizzle-orm/pg-core'; + import { bookFiles, readingSessionSyncCursors, readingSessions, userReadingDailyStats } from '../../db/schema'; import { ReadingSessionRepository, type RecordCumulativeReadingSessionParams } from './reading-session.repository'; @@ -10,19 +13,19 @@ function makeLimitChain(rows: unknown[]) { function makeHarness( options: { cursor?: { counter: number; generation: number; lastModified: Date } | null; - measured?: boolean; + measuredSeconds?: number; file?: { bookId: number; libraryId: number } | null; inserted?: boolean; } = {}, ) { const cursorChain = makeLimitChain(options.cursor ? [options.cursor] : []); - const measuredChain = makeLimitChain(options.measured ? [{ id: 88 }] : []); + const measuredWhere = vi.fn().mockResolvedValue([{ seconds: options.measuredSeconds ?? 0 }]); const fileChain = makeLimitChain(options.file === null ? [] : [options.file ?? { bookId: 46, libraryId: 9 }]); const select = vi.fn().mockImplementation(() => ({ from: vi.fn().mockImplementation((table: unknown) => { if (table === readingSessionSyncCursors) return { where: cursorChain.where }; - if (table === readingSessions) return { where: measuredChain.where }; + if (table === readingSessions) return { where: measuredWhere }; if (table === bookFiles) return { innerJoin: vi.fn().mockReturnValue({ where: fileChain.where }) }; throw new Error('Unexpected table in select'); }), @@ -47,7 +50,11 @@ function makeHarness( const transaction = vi.fn(async (callback: (trx: typeof tx) => Promise) => callback(tx)); const repo = new ReadingSessionRepository({ transaction } as never); - return { repo, tx, transaction, cursorInsertValues, cursorUpdateSet, sessionValues, dailyValues }; + return { repo, tx, transaction, cursorInsertValues, cursorUpdateSet, sessionValues, dailyValues, measuredWhere }; +} + +function boundParams(where: ReturnType): unknown[] { + return new PgDialect().sqlToQuery(where.mock.calls[0]![0] as SQL).params; } const params = (overrides: Partial = {}): RecordCumulativeReadingSessionParams => ({ @@ -85,6 +92,7 @@ describe('ReadingSessionRepository.recordCumulativeSyncedSession', () => { await expect(repo.recordCumulativeSyncedSession(params())).resolves.toEqual({ kind: 'saved', durationSeconds: 3600, + progressDelta: 7, sessionId: 'kst:30:53:2:860', }); @@ -97,6 +105,7 @@ describe('ReadingSessionRepository.recordCumulativeSyncedSession', () => { sourceDeviceKey: '30', sessionId: 'kst:30:53:2:860', durationSeconds: 3600, + syncIntervalStartedAt: new Date('2026-08-19T10:00:00.000Z'), }), ); expect(cursorUpdateSet).toHaveBeenCalledWith(expect.objectContaining({ counter: 860, generation: 2 })); @@ -117,10 +126,10 @@ describe('ReadingSessionRepository.recordCumulativeSyncedSession', () => { expect(sessionValues).not.toHaveBeenCalled(); }); - it('advances without an estimate when overlapping measured analytics already exist', async () => { + it('advances without an estimate when measured analytics already cover the counter delta', async () => { const { repo, cursorUpdateSet, sessionValues } = makeHarness({ cursor: { counter: 800, generation: 0, lastModified: new Date('2026-08-19T10:00:00.000Z') }, - measured: true, + measuredSeconds: 3590, }); await expect(repo.recordCumulativeSyncedSession(params())).resolves.toEqual({ @@ -132,6 +141,41 @@ describe('ReadingSessionRepository.recordCumulativeSyncedSession', () => { expect(sessionValues).not.toHaveBeenCalled(); }); + it('keeps the part of the counter delta the device did not measure', async () => { + const { repo, sessionValues, dailyValues } = makeHarness({ + cursor: { counter: 800, generation: 0, lastModified: new Date('2026-08-19T10:00:00.000Z') }, + measuredSeconds: 1500, + }); + + await expect(repo.recordCumulativeSyncedSession(params())).resolves.toEqual({ + kind: 'saved', + durationSeconds: 2100, + progressDelta: 4.08, + sessionId: 'kst:30:53:0:860', + }); + + expect(sessionValues).toHaveBeenCalledWith( + expect.objectContaining({ + startedAt: new Date('2026-08-20T18:55:00.000Z'), + endedAt: new Date('2026-08-20T19:30:00.000Z'), + durationSeconds: 2100, + progressDelta: 4.08, + }), + ); + expect(dailyValues).toHaveBeenCalledWith([expect.objectContaining({ readingSeconds: 2100, progressDelta: 4.08 })]); + }); + + it('measures against the whole counter interval rather than the minutes before the push', async () => { + const { repo, measuredWhere } = makeHarness({ + cursor: { counter: 800, generation: 0, lastModified: new Date('2026-08-19T10:00:00.000Z') }, + }); + + await repo.recordCumulativeSyncedSession(params()); + + expect(boundParams(measuredWhere)).toEqual(expect.arrayContaining(['2026-08-19T10:00:00.000Z', '2026-08-20T19:30:00.000Z'])); + expect(boundParams(measuredWhere)).not.toContain('2026-08-20T18:30:00.000Z'); + }); + it('starts a new generation when a newer device counter resets', async () => { const { repo, cursorUpdateSet, sessionValues } = makeHarness({ cursor: { counter: 860, generation: 4, lastModified: new Date('2026-08-19T10:00:00.000Z') }, @@ -167,42 +211,42 @@ describe('ReadingSessionRepository.recordCumulativeSyncedSession', () => { }); describe('ReadingSessionRepository measured-session reconciliation', () => { - it('stores the device key and removes an overlapping estimate in the same transaction', async () => { + type EstimateRow = { id: number; startedAt: Date; endedAt: Date; durationSeconds: number; progressDelta: number | null }; + type IntervalEstimateRow = EstimateRow & { syncIntervalStartedAt: Date }; + type SurvivingRow = Omit; + + const estimateLookup = (rows: EstimateRow[]) => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockReturnValue({ orderBy: vi.fn().mockReturnValue({ limit: vi.fn().mockResolvedValue(rows) }) }), + }), + }); + const intervalLookup = (rows: IntervalEstimateRow[]) => ({ + from: vi.fn().mockReturnValue({ where: vi.fn().mockReturnValue({ orderBy: vi.fn().mockResolvedValue(rows) }) }), + }); + const survivingLookup = (rows: SurvivingRow[]) => ({ + from: vi.fn().mockReturnValue({ innerJoin: vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue(rows) }) }), + }); + const existingLookup = (rows: { id: number; durationSeconds: number; progressDelta: number | null }[]) => ({ + from: vi.fn().mockReturnValue({ where: vi.fn().mockReturnValue({ limit: vi.fn().mockResolvedValue(rows) }) }), + }); + + const measured = { + startedAt: new Date('2026-08-20T19:20:00.000Z'), + endedAt: new Date('2026-08-20T19:30:00.000Z'), + durationSeconds: 600, + progressDelta: 2, + }; + + function makeMeasuredHarness(selects: unknown[], options: { inserted?: boolean } = {}) { const outerLimit = vi.fn().mockResolvedValue([{ bookId: 46, libraryId: 9 }]); const outerSelect = vi.fn().mockReturnValue({ from: vi.fn().mockReturnValue({ innerJoin: vi.fn().mockReturnValue({ where: vi.fn().mockReturnValue({ limit: outerLimit }) }) }), }); - - const estimate = { - id: 20, - startedAt: new Date('2026-08-20T18:30:00.000Z'), - endedAt: new Date('2026-08-20T19:30:00.000Z'), - durationSeconds: 3600, - progressDelta: 7, - }; - const estimateSelect = { - from: vi.fn().mockReturnValue({ - where: vi.fn().mockReturnValue({ orderBy: vi.fn().mockReturnValue({ limit: vi.fn().mockResolvedValue([estimate]) }) }), - }), - }; - const survivingSelect = { - from: vi.fn().mockReturnValue({ - innerJoin: vi.fn().mockReturnValue({ - where: vi.fn().mockResolvedValue([ - { - startedAt: new Date('2026-08-20T19:20:00.000Z'), - endedAt: new Date('2026-08-20T19:30:00.000Z'), - durationSeconds: 600, - progressDelta: 2, - }, - ]), - }), - }), - }; - const txSelect = vi.fn().mockReturnValueOnce(estimateSelect).mockReturnValueOnce(survivingSelect); + const txSelect = vi.fn(); + for (const select of selects) txSelect.mockReturnValueOnce(select); const sessionValues = vi.fn().mockReturnValue({ - onConflictDoNothing: vi.fn().mockReturnValue({ returning: vi.fn().mockResolvedValue([{ id: 21 }]) }), + onConflictDoNothing: vi.fn().mockReturnValue({ returning: vi.fn().mockResolvedValue(options.inserted === false ? [] : [{ id: 21 }]) }), }); const dailyValues = vi.fn().mockReturnValue({ onConflictDoUpdate: vi.fn().mockResolvedValue(undefined) }); const insert = vi.fn().mockImplementation((table: unknown) => { @@ -210,32 +254,145 @@ describe('ReadingSessionRepository measured-session reconciliation', () => { if (table === userReadingDailyStats) return { values: dailyValues }; throw new Error('Unexpected table in insert'); }); + const updateSet = vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue(undefined) }); + const update = vi.fn().mockReturnValue({ set: updateSet }); const deleteFn = vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue(undefined) }); - const tx = { execute: vi.fn().mockResolvedValue(undefined), insert, select: txSelect, delete: deleteFn }; + const tx = { execute: vi.fn().mockResolvedValue(undefined), insert, update, select: txSelect, delete: deleteFn }; const transaction = vi.fn(async (callback: (trx: typeof tx) => Promise) => callback(tx)); const repo = new ReadingSessionRepository({ select: outerSelect, transaction } as never); - await expect( - repo.saveSession( - 7, - 53, - 'measured-event', - new Date('2026-08-20T19:20:00.000Z'), - new Date('2026-08-20T19:30:00.000Z'), - 600, - 2, - 29, - 'kobo', - 'UTC', - { sourceDeviceKey: '30', estimateSessionIdPrefix: 'kst:30:' }, - ), - ).resolves.toEqual({ kind: 'saved' }); + const saveMeasured = () => + repo.saveSession(7, 53, 'measured-event', measured.startedAt, measured.endedAt, measured.durationSeconds, 2, 29, 'kobo', 'UTC', { + sourceDeviceKey: '30', + estimateSessionIdPrefix: 'kst:30:', + }); + + return { saveMeasured, transaction, txSelect, sessionValues, dailyValues, updateSet, deleteFn }; + } + + it('stores the device key and removes an overlapping estimate that predates interval tracking', async () => { + const legacyEstimate = { + id: 20, + startedAt: new Date('2026-08-20T18:30:00.000Z'), + endedAt: new Date('2026-08-20T19:30:00.000Z'), + durationSeconds: 3600, + progressDelta: 7, + }; + const { saveMeasured, transaction, sessionValues, dailyValues, updateSet, deleteFn } = makeMeasuredHarness([ + estimateLookup([legacyEstimate]), + intervalLookup([]), + survivingLookup([measured]), + ]); + + await expect(saveMeasured()).resolves.toEqual({ kind: 'saved' }); expect(transaction).toHaveBeenCalledOnce(); expect(sessionValues).toHaveBeenCalledWith(expect.objectContaining({ sessionId: 'measured-event', sourceDeviceKey: '30' })); expect(deleteFn).toHaveBeenCalledWith(readingSessions); + expect(updateSet).not.toHaveBeenCalled(); expect(dailyValues).toHaveBeenLastCalledWith([ expect.objectContaining({ day: '2026-08-20', readingSeconds: 600, progressDelta: 2, sessionsCount: 1 }), ]); }); + + it('removes the estimate of a later push once the measured session covers its counter interval', async () => { + const lateEstimate = { + id: 20, + startedAt: new Date('2026-08-21T06:50:00.000Z'), + endedAt: new Date('2026-08-21T07:00:00.000Z'), + durationSeconds: 600, + progressDelta: 2, + syncIntervalStartedAt: new Date('2026-08-20T07:00:00.000Z'), + }; + const { saveMeasured, dailyValues, updateSet, deleteFn } = makeMeasuredHarness([ + estimateLookup([]), + intervalLookup([lateEstimate]), + survivingLookup([measured]), + ]); + + await expect(saveMeasured()).resolves.toEqual({ kind: 'saved' }); + + expect(deleteFn).toHaveBeenCalledWith(readingSessions); + expect(deleteFn).toHaveBeenCalledWith(userReadingDailyStats); + expect(updateSet).not.toHaveBeenCalled(); + expect(dailyValues).toHaveBeenLastCalledWith([ + expect.objectContaining({ day: '2026-08-20', readingSeconds: 600, progressDelta: 2, sessionsCount: 1 }), + ]); + }); + + it('shortens an estimate the measured session covers only in part', async () => { + const lateEstimate = { + id: 20, + startedAt: new Date('2026-08-21T06:00:00.000Z'), + endedAt: new Date('2026-08-21T07:00:00.000Z'), + durationSeconds: 3600, + progressDelta: 6, + syncIntervalStartedAt: new Date('2026-08-20T07:00:00.000Z'), + }; + const shortened = { + startedAt: new Date('2026-08-21T06:10:00.000Z'), + endedAt: new Date('2026-08-21T07:00:00.000Z'), + durationSeconds: 3000, + progressDelta: 5, + }; + const { saveMeasured, dailyValues, updateSet, deleteFn } = makeMeasuredHarness([ + estimateLookup([]), + intervalLookup([lateEstimate]), + survivingLookup([measured, shortened]), + ]); + + await expect(saveMeasured()).resolves.toEqual({ kind: 'saved' }); + + expect(updateSet).toHaveBeenCalledWith({ startedAt: shortened.startedAt, durationSeconds: 3000, progressDelta: 5 }); + expect(deleteFn).not.toHaveBeenCalledWith(readingSessions); + expect(dailyValues).toHaveBeenLastCalledWith([ + expect.objectContaining({ day: '2026-08-21', readingSeconds: 3000, progressDelta: 5, sessionsCount: 1 }), + ]); + }); + + it('splits a measured session across the estimates whose counter intervals it overlaps', async () => { + const pushedMidSession = new Date('2026-08-20T19:25:00.000Z'); + const before = { + id: 20, + startedAt: new Date('2026-08-20T19:05:00.000Z'), + endedAt: pushedMidSession, + durationSeconds: 1200, + progressDelta: null, + syncIntervalStartedAt: new Date('2026-08-20T07:00:00.000Z'), + }; + const after = { + id: 22, + startedAt: new Date('2026-08-21T06:40:00.000Z'), + endedAt: new Date('2026-08-21T07:00:00.000Z'), + durationSeconds: 1200, + progressDelta: null, + syncIntervalStartedAt: pushedMidSession, + }; + const { saveMeasured, updateSet, deleteFn } = makeMeasuredHarness([ + estimateLookup([]), + intervalLookup([before, after]), + survivingLookup([measured]), + ]); + + await expect(saveMeasured()).resolves.toEqual({ kind: 'saved' }); + + expect(updateSet).toHaveBeenCalledTimes(2); + expect(updateSet).toHaveBeenNthCalledWith(1, { startedAt: new Date('2026-08-20T19:10:00.000Z'), durationSeconds: 900, progressDelta: null }); + expect(updateSet).toHaveBeenNthCalledWith(2, { startedAt: new Date('2026-08-21T06:45:00.000Z'), durationSeconds: 900, progressDelta: null }); + expect(deleteFn).not.toHaveBeenCalledWith(readingSessions); + }); + + it('takes nothing off an estimate when the measured session is a replay', async () => { + const { saveMeasured, txSelect, updateSet, deleteFn } = makeMeasuredHarness( + [existingLookup([{ id: 21, durationSeconds: 600, progressDelta: 2 }]), estimateLookup([])], + { inserted: false }, + ); + + await expect(saveMeasured()).resolves.toEqual({ kind: 'skipped', reason: 'duplicate_session_id' }); + + expect(txSelect).toHaveBeenCalledTimes(2); + expect(updateSet).toHaveBeenCalledOnce(); + expect(updateSet).toHaveBeenCalledWith({ sourceDeviceKey: '30' }); + expect(deleteFn).not.toHaveBeenCalled(); + }); }); diff --git a/server/src/modules/reading-session/reading-session.repository.ts b/server/src/modules/reading-session/reading-session.repository.ts index f66567053..13d5d36b2 100644 --- a/server/src/modules/reading-session/reading-session.repository.ts +++ b/server/src/modules/reading-session/reading-session.repository.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; -import { and, asc, count, desc, eq, gt, gte, inArray, isNotNull, isNull, like, lt, lte, max, min, notLike, sql } from 'drizzle-orm'; +import { and, asc, count, desc, eq, gt, gte, inArray, isNotNull, isNull, like, lt, lte, max, min, notLike, sql, type SQL } from 'drizzle-orm'; import type { NodePgDatabase } from 'drizzle-orm/node-postgres'; import type { @@ -36,6 +36,32 @@ function toReadingSessionType(value: string | null | undefined): ReadingSessionT } const ESTIMATE_CLEANUP_PAGE_SIZE = 500; const CLEANUP_DAILY_STATS_MAX_SPAN_DAYS = 31; +// Cumulative device counters tick in whole minutes, so less than one tick left is rounding, not reading. +const SYNC_COUNTER_RESOLUTION_SECONDS = 60; + +// A device can push its counter while a book is open, on waking or on a manual sync, so a counter +// interval can begin or end inside a measured session. The session's seconds are split by how much +// of its wall-clock span falls in each interval rather than given whole to the one holding its start. +// `measuredSecondsInWindow` and `measuredShareOfWindow` must apply the same rule. +function measuredSecondsInWindow(windowStart: Date, windowEnd: Date): SQL { + return sql`case when ${readingSessions.endedAt} > ${readingSessions.startedAt} then round( + ${readingSessions.durationSeconds} * greatest(0, extract(epoch from ( + least(${readingSessions.endedAt}, ${windowEnd}::timestamptz) - greatest(${readingSessions.startedAt}, ${windowStart}::timestamptz) + ))) / extract(epoch from (${readingSessions.endedAt} - ${readingSessions.startedAt})) + ) else ${readingSessions.durationSeconds} end`; +} + +function measuredShareOfWindow(session: { seconds: number; startedAt: Date; endedAt: Date }, windowStart: Date, windowEnd: Date): number { + const spanMs = session.endedAt.getTime() - session.startedAt.getTime(); + if (spanMs <= 0) return session.seconds; + const overlapMs = Math.min(session.endedAt.getTime(), windowEnd.getTime()) - Math.max(session.startedAt.getTime(), windowStart.getTime()); + return Math.round((session.seconds * Math.max(0, overlapMs)) / spanMs); +} + +function scaleProgressDelta(progressDelta: number | null, keptSeconds: number, totalSeconds: number): number | null { + if (progressDelta === null || keptSeconds === totalSeconds) return progressDelta; + return Math.round(((progressDelta * keptSeconds) / totalSeconds) * 100) / 100; +} function groupDateKeysByMaxSpan(days: Iterable): string[][] { const sorted = [...new Set(days)].sort(); @@ -70,7 +96,7 @@ export type SaveReadingSessionResult = export type RecordCumulativeReadingSessionResult = | { kind: 'baseline' | 'unchanged' | 'reset' | 'stale' } - | { kind: 'saved'; durationSeconds: number; sessionId: string } + | { kind: 'saved'; durationSeconds: number; progressDelta: number | null; sessionId: string } | { kind: 'skipped'; reason: 'book_file_not_found' | 'duplicate_session_id' | 'measured_session_present'; @@ -236,7 +262,7 @@ export class ReadingSessionRepository { } if (sync?.estimateSessionIdPrefix) { - await this.deleteOverlappingSyncEstimates(tx, { + await this.reconcileSyncEstimates(tx, { userId, bookId, libraryId, @@ -246,6 +272,7 @@ export class ReadingSessionRepository { startedAt, endedAt, timeZone, + newlyMeasuredSeconds: result.kind === 'saved' ? durationSeconds : result.kind === 'superseded' ? result.addedSeconds : 0, }); } @@ -300,11 +327,14 @@ export class ReadingSessionRepository { return { kind: 'reset' }; } - const durationSeconds = (params.counter - cursor.counter) * 60; - const startedAt = new Date(params.endedAt.getTime() - durationSeconds * 1000); + // The counter covers everything read since the previous push, however long ago that reading + // was: a device that read offline only pushes with its next sync. The share of measured + // sessions that falls in that interval is subtracted rather than taken as proof the whole + // delta is covered, so reading the device did not measure is kept. + const counterSeconds = (params.counter - cursor.counter) * 60; const [measured] = await tx - .select({ id: readingSessions.id }) + .select({ seconds: sql`coalesce(sum(${measuredSecondsInWindow(cursor.lastModified, params.endedAt)}), 0)::int` }) .from(readingSessions) .where( and( @@ -314,16 +344,19 @@ export class ReadingSessionRepository { eq(readingSessions.sourceDeviceKey, params.sourceDeviceKey), notLike(readingSessions.sessionId, `${params.sessionIdPrefix}%`), lt(readingSessions.startedAt, params.endedAt), - gt(readingSessions.endedAt, startedAt), + gt(readingSessions.endedAt, cursor.lastModified), ), - ) - .limit(1); + ); - if (measured) { + const durationSeconds = counterSeconds - (measured?.seconds ?? 0); + if (durationSeconds < SYNC_COUNTER_RESOLUTION_SECONDS) { await this.updateSyncCursor(tx, params, cursor.generation); return { kind: 'skipped', reason: 'measured_session_present' }; } + const startedAt = new Date(params.endedAt.getTime() - durationSeconds * 1000); + const progressDelta = scaleProgressDelta(params.progressDelta, durationSeconds, counterSeconds); + if (params.bookFileId === null) return { kind: 'skipped', reason: 'book_file_not_found' }; const sessionId = params.buildSessionId(params.bookFileId, cursor.generation, params.counter); @@ -349,8 +382,9 @@ export class ReadingSessionRepository { startedAt, endedAt: params.endedAt, durationSeconds, - progressDelta: params.progressDelta, + progressDelta, endProgress: params.endProgress, + syncIntervalStartedAt: cursor.lastModified, }) .onConflictDoNothing({ target: [readingSessions.userId, readingSessions.sessionId] }) .returning({ id: readingSessions.id }); @@ -364,11 +398,11 @@ export class ReadingSessionRepository { startedAt, endedAt: params.endedAt, durationSeconds, - progressDelta: params.progressDelta, + progressDelta, timeZone: params.timeZone, }); - return { kind: 'saved', durationSeconds, sessionId }; + return { kind: 'saved', durationSeconds, progressDelta, sessionId }; }); } @@ -823,7 +857,20 @@ export class ReadingSessionRepository { await tx.execute(sql`select pg_advisory_xact_lock(${userId}::int, hashtext(${`${sourceDeviceKey}:${bookId}`})::int)`); } - private async deleteOverlappingSyncEstimates( + /** + * Reconciles the counter-derived estimates of a device with a session it also measured. + * + * An estimate stands for whatever was read between two counter pushes, and it is dated on the + * minutes leading up to the push. That is only when the reading happened if the device pushed + * right after it; after offline reading the push comes with the next sync. So the measured + * session is matched to every estimate whose counter interval it overlaps, and each takes the + * share of its seconds that falls in that interval. They are taken once, when first stored: a + * replay takes nothing, and the shares never add up to more than the session. What is left + * stays as reading the device did not measure, unless it is below the counter resolution. + * + * Estimates written before the interval was recorded keep the overlap rule they were created under. + */ + private async reconcileSyncEstimates( tx: Tx, params: { userId: number; @@ -835,28 +882,37 @@ export class ReadingSessionRepository { startedAt: Date; endedAt: Date; timeZone: string; + newlyMeasuredSeconds: number; }, ): Promise { const affectedDays = new Set(); + const estimateColumns = { + id: readingSessions.id, + startedAt: readingSessions.startedAt, + endedAt: readingSessions.endedAt, + durationSeconds: readingSessions.durationSeconds, + progressDelta: readingSessions.progressDelta, + }; + const sameDeviceEstimate = and( + eq(readingSessions.userId, params.userId), + eq(readingSessions.bookId, params.bookId), + eq(readingSessions.source, params.source), + eq(readingSessions.sourceDeviceKey, params.sourceDeviceKey), + like(readingSessions.sessionId, `${params.estimateSessionIdPrefix}%`), + ); + const collectAffectedDays = (row: { startedAt: Date; endedAt: Date; durationSeconds: number; progressDelta: number | null }) => { + for (const day of getReadingSessionDayKeys(row, params.timeZone)) affectedDays.add(day); + }; let cursor = 0; for (;;) { const rows = await tx - .select({ - id: readingSessions.id, - startedAt: readingSessions.startedAt, - endedAt: readingSessions.endedAt, - durationSeconds: readingSessions.durationSeconds, - progressDelta: readingSessions.progressDelta, - }) + .select(estimateColumns) .from(readingSessions) .where( and( - eq(readingSessions.userId, params.userId), - eq(readingSessions.bookId, params.bookId), - eq(readingSessions.source, params.source), - eq(readingSessions.sourceDeviceKey, params.sourceDeviceKey), - like(readingSessions.sessionId, `${params.estimateSessionIdPrefix}%`), + sameDeviceEstimate, + isNull(readingSessions.syncIntervalStartedAt), lt(readingSessions.startedAt, params.endedAt), gt(readingSessions.endedAt, params.startedAt), gt(readingSessions.id, cursor), @@ -868,19 +924,7 @@ export class ReadingSessionRepository { if (rows.length === 0) break; cursor = rows[rows.length - 1]!.id; - for (const row of rows) { - for (const day of getReadingSessionDayKeys( - { - startedAt: row.startedAt, - endedAt: row.endedAt, - durationSeconds: row.durationSeconds, - progressDelta: row.progressDelta ?? null, - }, - params.timeZone, - )) { - affectedDays.add(day); - } - } + for (const row of rows) collectAffectedDays(row); await tx.delete(readingSessions).where( and( @@ -895,6 +939,47 @@ export class ReadingSessionRepository { if (rows.length < ESTIMATE_CLEANUP_PAGE_SIZE) break; } + if (params.newlyMeasuredSeconds > 0) { + const measured = { seconds: params.newlyMeasuredSeconds, startedAt: params.startedAt, endedAt: params.endedAt }; + const overlapping = await tx + .select({ ...estimateColumns, syncIntervalStartedAt: readingSessions.syncIntervalStartedAt }) + .from(readingSessions) + .where( + and( + sameDeviceEstimate, + isNotNull(readingSessions.syncIntervalStartedAt), + lt(readingSessions.syncIntervalStartedAt, params.endedAt), + gt(readingSessions.endedAt, params.startedAt), + ), + ) + .orderBy(asc(readingSessions.endedAt), asc(readingSessions.id)); + + for (const estimate of overlapping) { + const share = measuredShareOfWindow(measured, estimate.syncIntervalStartedAt!, estimate.endedAt); + if (share <= 0) continue; + + const ownEstimate = and(eq(readingSessions.userId, params.userId), eq(readingSessions.id, estimate.id)); + const remainingSeconds = estimate.durationSeconds - share; + collectAffectedDays(estimate); + + if (remainingSeconds < SYNC_COUNTER_RESOLUTION_SECONDS) { + await tx.delete(readingSessions).where(ownEstimate); + } else { + const shortened = { + startedAt: new Date(estimate.endedAt.getTime() - remainingSeconds * 1000), + endedAt: estimate.endedAt, + durationSeconds: remainingSeconds, + progressDelta: scaleProgressDelta(estimate.progressDelta, remainingSeconds, estimate.durationSeconds), + }; + await tx + .update(readingSessions) + .set({ startedAt: shortened.startedAt, durationSeconds: shortened.durationSeconds, progressDelta: shortened.progressDelta }) + .where(ownEstimate); + collectAffectedDays(shortened); + } + } + } + if (affectedDays.size > 0) { await this.recomputeDailyStats(tx, params.userId, params.libraryId, [...affectedDays], params.timeZone); } diff --git a/server/src/modules/reading-session/reading-session.service.ts b/server/src/modules/reading-session/reading-session.service.ts index 8ae8eb251..b8e23c4e8 100644 --- a/server/src/modules/reading-session/reading-session.service.ts +++ b/server/src/modules/reading-session/reading-session.service.ts @@ -127,7 +127,7 @@ export class ReadingSessionService { durationSeconds: result.durationSeconds, startedAt, endedAt: params.endedAt, - progressDelta: params.progressDelta, + progressDelta: result.progressDelta, endProgress: params.endProgress, timezone: params.timeZone, }); diff --git a/server/test/kobo-reading-time-reconciliation.e2e-spec.ts b/server/test/kobo-reading-time-reconciliation.e2e-spec.ts new file mode 100644 index 000000000..aeef8d87e --- /dev/null +++ b/server/test/kobo-reading-time-reconciliation.e2e-spec.ts @@ -0,0 +1,479 @@ +import { randomUUID } from 'crypto'; +import { and, asc, eq, inArray } 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 ReaderStateIsolationE2EContext, +} from './e2e/reader-state-isolation/reader-state-isolation-harness'; + +type KoboDevice = { id: number; token: string }; +type Book = { bookId: number; bookFileId: number; entitlementId: string }; +type MeasuredSession = { id: string; startedAt: string; endedAt: string; seconds: number }; + +const BOOK_COUNT = 13; +const RUN = randomUUID().slice(0, 8); + +/** Day `n` of a calendar that starts on 2026-03-01, so every scenario owns its own dates. */ +function day(n: number): string { + return new Date(Date.UTC(2026, 2, n)).toISOString().slice(0, 10); +} + +function at(n: number, time: string): string { + return `${day(n)}T${time}.000Z`; +} + +function measured(name: string, n: number, from: string, to: string): MeasuredSession { + const startedAt = at(n, from); + const endedAt = at(n, to); + return { id: `${RUN}-${name}`, startedAt, endedAt, seconds: (Date.parse(endedAt) - Date.parse(startedAt)) / 1000 }; +} + +describe('Kobo reading time reconciliation (e2e)', { timeout: 180_000 }, () => { + let ctx!: ReaderStateIsolationE2EContext; + let library!: CreatedLibrary; + let userId!: number; + let books!: Book[]; + let deviceA!: KoboDevice; + let deviceB!: KoboDevice; + + async function createDevice(name: string): Promise { + const response = await ctx.app.inject({ + method: 'POST', + url: '/api/v1/kobo/devices', + headers: authHeader(ctx.adminToken), + payload: { name }, + }); + expect([200, 201]).toContain(response.statusCode); + return response.json() as KoboDevice; + } + + async function drainLibrary(device: KoboDevice): Promise { + let syncToken: string | undefined; + for (let page = 0; page < 10; page += 1) { + const response = await ctx.app.inject({ + method: 'GET', + url: `/api/v1/kobo/${device.token}/v1/library/sync`, + headers: syncToken ? { 'x-kobo-synctoken': syncToken } : undefined, + }); + expect(response.statusCode).toBe(200); + if (response.headers['x-kobo-sync'] !== 'continue') return; + syncToken = response.headers['x-kobo-synctoken'] as string; + } + throw new Error(`Kobo device ${device.id} did not finish syncing within ten pages`); + } + + /** A state push carrying the device reading-time counter, as sent when a book is closed or a sync starts. */ + async function pushCounter(device: KoboDevice, book: Book, minutes: number, lastModified: string, progressPercent = 10): Promise { + const response = await ctx.app.inject({ + method: 'PUT', + url: `/api/v1/kobo/${device.token}/v1/library/${book.entitlementId}/state`, + payload: { + ReadingStates: [ + { + EntitlementId: book.entitlementId, + LastModified: lastModified, + PriorityTimestamp: lastModified, + CurrentBookmark: { LastModified: lastModified, ProgressPercent: progressPercent }, + Statistics: { LastModified: lastModified, SpentReadingMinutes: minutes }, + StatusInfo: { LastModified: lastModified, Status: 'Reading' }, + }, + ], + }, + }); + expect(response.statusCode).toBe(200); + } + + /** The analytics batch a device that still measures its sessions sends with a library sync. */ + async function sendMeasured(device: KoboDevice, book: Book, sessions: MeasuredSession[]): Promise { + const response = await ctx.app.inject({ + method: 'POST', + url: `/api/v1/kobo/${device.token}/v1/analytics/event`, + payload: { + Events: sessions.flatMap((session) => [ + { Id: `${session.id}-open`, EventType: 'OpenContent', Timestamp: session.startedAt, Attributes: { volumeid: book.entitlementId } }, + { + Id: session.id, + EventType: 'LeaveContent', + Timestamp: session.endedAt, + Metrics: { SecondsRead: session.seconds }, + Attributes: { volumeid: book.entitlementId }, + }, + ]), + }, + }); + expect(response.statusCode).toBe(200); + } + + async function storedSessions(book: Book) { + const rows = await ctx.db + .select({ + sessionId: schema.readingSessions.sessionId, + sourceDeviceKey: schema.readingSessions.sourceDeviceKey, + startedAt: schema.readingSessions.startedAt, + endedAt: schema.readingSessions.endedAt, + durationSeconds: schema.readingSessions.durationSeconds, + progressDelta: schema.readingSessions.progressDelta, + syncIntervalStartedAt: schema.readingSessions.syncIntervalStartedAt, + }) + .from(schema.readingSessions) + .where(and(eq(schema.readingSessions.userId, userId), eq(schema.readingSessions.bookId, book.bookId))) + .orderBy(asc(schema.readingSessions.startedAt), asc(schema.readingSessions.id)); + + return rows.map((row) => ({ + sessionId: row.sessionId, + device: row.sourceDeviceKey, + startedAt: row.startedAt.toISOString(), + endedAt: row.endedAt.toISOString(), + seconds: row.durationSeconds, + progressDelta: row.progressDelta, + intervalStartedAt: row.syncIntervalStartedAt?.toISOString() ?? null, + })); + } + + function estimate( + device: KoboDevice, + book: Book, + counter: number, + endedAt: string, + seconds: number, + intervalStartedAt: string, + extra: { generation?: number; progressDelta?: number | null } = {}, + ) { + return { + sessionId: `kst:${device.id}:${book.bookFileId}:${extra.generation ?? 0}:${counter}`, + device: String(device.id), + startedAt: new Date(Date.parse(endedAt) - seconds * 1000).toISOString(), + endedAt, + seconds, + progressDelta: extra.progressDelta === undefined ? 0 : extra.progressDelta, + intervalStartedAt, + }; + } + + function stored(device: KoboDevice, session: MeasuredSession) { + return { + sessionId: session.id, + device: String(device.id), + startedAt: session.startedAt, + endedAt: session.endedAt, + seconds: session.seconds, + progressDelta: null, + intervalStartedAt: null, + }; + } + + /** Reading seconds per day over the given days of the scenario calendar; days without a row are absent. */ + async function dailySeconds(...days: number[]): Promise> { + const rows = await ctx.db + .select({ day: schema.userReadingDailyStats.day, readingSeconds: schema.userReadingDailyStats.readingSeconds }) + .from(schema.userReadingDailyStats) + .where( + and( + eq(schema.userReadingDailyStats.userId, userId), + eq(schema.userReadingDailyStats.libraryId, library.libraryId), + inArray(schema.userReadingDailyStats.day, days.map(day)), + ), + ); + return Object.fromEntries(rows.map((row) => [row.day, row.readingSeconds])); + } + + beforeAll(async () => { + ctx = await createReaderStateIsolationE2EContext(); + library = await createLibraryWithFolder(ctx, { name: `kobo-reading-time-${RUN}` }); + + const paths = await Promise.all( + Array.from({ length: BOOK_COUNT }, (_, index) => + createEpubFixture(library.folderPath, `kobo-reading-time-${index + 1}.epub`, { + title: `Kobo Reading Time ${index + 1}`, + uid: `urn:uuid:${randomUUID()}`, + }), + ), + ); + await triggerAndWaitForLibraryScan(ctx, library.libraryId); + const located = await Promise.all(paths.map((path) => locateBookByAbsolutePath(ctx, path))); + + const [user] = await ctx.db.select({ id: schema.users.id }).from(schema.users).where(eq(schema.users.username, 'reader-state-e2e-admin')); + userId = user!.id; + + const collectionResponse = await ctx.app.inject({ + method: 'POST', + url: '/api/v1/collections', + headers: authHeader(ctx.adminToken), + payload: { name: `Kobo Reading Time ${RUN}`, icon: 'book', syncToKobo: true }, + }); + expect([200, 201]).toContain(collectionResponse.statusCode); + const collectionId = (collectionResponse.json() as { id: number }).id; + + const addBooksResponse = await ctx.app.inject({ + method: 'POST', + url: `/api/v1/collections/${collectionId}/books`, + headers: authHeader(ctx.adminToken), + payload: { bookIds: located.map((book) => book.bookId) }, + }); + expect([200, 201]).toContain(addBooksResponse.statusCode); + + deviceA = await createDevice(`Kobo A ${RUN}`); + deviceB = await createDevice(`Kobo B ${RUN}`); + await drainLibrary(deviceA); + await drainLibrary(deviceB); + + const entitlements = await ctx.db + .select({ bookId: schema.koboBookEntitlements.bookId, entitlementId: schema.koboBookEntitlements.entitlementId }) + .from(schema.koboBookEntitlements) + .where(eq(schema.koboBookEntitlements.userId, userId)); + books = located.map((book) => ({ + bookId: book.bookId, + bookFileId: book.bookFileId, + entitlementId: entitlements.find((entitlement) => entitlement.bookId === book.bookId)!.entitlementId, + })); + }, 180_000); + + afterAll(async () => { + if (ctx) await closeReaderStateIsolationE2EContext(ctx); + }); + + describe('reading done offline, well before the sync that reports it', () => { + const evening = [ + measured('offline-1', 2, '20:16:11', '20:26:36'), + measured('offline-2', 2, '20:31:36', '20:54:35'), + measured('offline-3', 2, '20:54:55', '21:09:57'), + ]; + + it('retires the estimate when the counter push arrives before the measured sessions', async () => { + const book = books[0]!; + await pushCounter(deviceA, book, 9, at(1, '20:52:44')); + await pushCounter(deviceA, book, 57, at(3, '07:41:58')); + + expect(await storedSessions(book)).toEqual([estimate(deviceA, book, 57, at(3, '07:41:58'), 2880, at(1, '20:52:44'))]); + expect(await dailySeconds(1, 2, 3)).toEqual({ [day(3)]: 2880 }); + + await sendMeasured(deviceA, book, evening); + + expect(await storedSessions(book)).toEqual(evening.map((session) => stored(deviceA, session))); + expect(await dailySeconds(1, 2, 3)).toEqual({ [day(2)]: 2906 }); + }); + + it('derives no estimate when the measured sessions arrive before the counter push', async () => { + const book = books[1]!; + const sessions = evening.map((session) => measured(`${session.id}-first`, 6, session.startedAt.slice(11, 19), session.endedAt.slice(11, 19))); + await pushCounter(deviceA, book, 9, at(5, '20:52:44')); + await sendMeasured(deviceA, book, sessions); + await pushCounter(deviceA, book, 57, at(7, '07:41:58')); + + expect(await storedSessions(book)).toEqual(sessions.map((session) => stored(deviceA, session))); + expect(await dailySeconds(5, 6, 7)).toEqual({ [day(6)]: 2906 }); + }); + }); + + it('matches each delayed measured session to its own counter interval when several estimates exist', async () => { + const book = books[2]!; + await pushCounter(deviceA, book, 0, at(9, '07:00:00')); + await pushCounter(deviceA, book, 30, at(10, '07:00:00')); + await pushCounter(deviceA, book, 60, at(11, '07:00:00')); + await pushCounter(deviceA, book, 80, at(12, '07:00:00')); + + const unmeasured = estimate(deviceA, book, 80, at(12, '07:00:00'), 1200, at(11, '07:00:00')); + expect(await storedSessions(book)).toEqual([ + estimate(deviceA, book, 30, at(10, '07:00:00'), 1800, at(9, '07:00:00')), + estimate(deviceA, book, 60, at(11, '07:00:00'), 1800, at(10, '07:00:00')), + unmeasured, + ]); + + const delayed = [ + measured('delayed-1', 9, '20:00:00', '20:20:00'), + measured('delayed-2', 9, '20:30:00', '20:40:00'), + measured('delayed-3', 10, '20:00:00', '20:15:00'), + measured('delayed-4', 10, '20:20:00', '20:35:00'), + ]; + await sendMeasured(deviceA, book, delayed); + + expect(await storedSessions(book)).toEqual([...delayed.map((session) => stored(deviceA, session)), unmeasured]); + expect(await dailySeconds(9, 10, 11, 12)).toEqual({ [day(9)]: 1800, [day(10)]: 1800, [day(12)]: 1200 }); + }); + + describe('a counter delta the measured sessions cover only in part', () => { + it('keeps the unmeasured remainder and takes nothing more off it on a replay', async () => { + const book = books[3]!; + const partial = [measured('partial', 15, '20:00:00', '20:15:00')]; + await pushCounter(deviceA, book, 0, at(14, '07:00:00'), 10); + await pushCounter(deviceA, book, 40, at(16, '07:00:00'), 30); + + expect(await storedSessions(book)).toEqual([estimate(deviceA, book, 40, at(16, '07:00:00'), 2400, at(14, '07:00:00'), { progressDelta: 20 })]); + + await sendMeasured(deviceA, book, partial); + + const remainder = estimate(deviceA, book, 40, at(16, '07:00:00'), 1500, at(14, '07:00:00'), { progressDelta: 12.5 }); + expect(await storedSessions(book)).toEqual([stored(deviceA, partial[0]!), remainder]); + expect(await dailySeconds(14, 15, 16)).toEqual({ [day(15)]: 900, [day(16)]: 1500 }); + + await sendMeasured(deviceA, book, partial); + + expect(await storedSessions(book)).toEqual([stored(deviceA, partial[0]!), remainder]); + expect(await dailySeconds(14, 15, 16)).toEqual({ [day(15)]: 900, [day(16)]: 1500 }); + }); + + it('reaches the same remainder when the measured session arrives first', async () => { + const book = books[4]!; + const partial = [measured('partial-first', 19, '20:00:00', '20:15:00')]; + await pushCounter(deviceA, book, 0, at(18, '07:00:00'), 10); + await sendMeasured(deviceA, book, partial); + await pushCounter(deviceA, book, 40, at(20, '07:00:00'), 30); + + expect(await storedSessions(book)).toEqual([ + stored(deviceA, partial[0]!), + estimate(deviceA, book, 40, at(20, '07:00:00'), 1500, at(18, '07:00:00'), { progressDelta: 12.5 }), + ]); + expect(await dailySeconds(18, 19, 20)).toEqual({ [day(19)]: 900, [day(20)]: 1500 }); + }); + }); + + it('keeps measured sessions from before a counter reset away from the estimates after it', async () => { + const book = books[5]!; + const beforeReset = measured('before-reset', 22, '20:00:00', '20:10:00'); + const beforeResetLate = measured('before-reset-late', 22, '21:00:00', '21:05:00'); + await pushCounter(deviceA, book, 100, at(22, '07:00:00')); + await sendMeasured(deviceA, book, [beforeReset]); + await pushCounter(deviceA, book, 5, at(23, '07:00:00')); + + expect(await storedSessions(book)).toEqual([stored(deviceA, beforeReset)]); + + await pushCounter(deviceA, book, 25, at(24, '07:00:00')); + await sendMeasured(deviceA, book, [beforeResetLate]); + + expect(await storedSessions(book)).toEqual([ + stored(deviceA, beforeReset), + stored(deviceA, beforeResetLate), + estimate(deviceA, book, 25, at(24, '07:00:00'), 1200, at(23, '07:00:00'), { generation: 1 }), + ]); + expect(await dailySeconds(22, 23, 24)).toEqual({ [day(22)]: 900, [day(24)]: 1200 }); + }); + + it('leaves the estimate of another device alone', async () => { + const book = books[6]!; + const onDeviceA = measured('device-a', 27, '20:00:00', '20:20:00'); + await pushCounter(deviceA, book, 0, at(27, '07:00:00')); + await pushCounter(deviceB, book, 0, at(27, '07:01:00')); + await pushCounter(deviceA, book, 20, at(28, '07:00:00')); + await pushCounter(deviceB, book, 20, at(28, '07:05:00')); + + const otherDevice = estimate(deviceB, book, 20, at(28, '07:05:00'), 1200, at(27, '07:01:00')); + expect(await storedSessions(book)).toEqual([estimate(deviceA, book, 20, at(28, '07:00:00'), 1200, at(27, '07:00:00')), otherDevice]); + + await sendMeasured(deviceA, book, [onDeviceA]); + + expect(await storedSessions(book)).toEqual([stored(deviceA, onDeviceA), otherDevice]); + expect(await dailySeconds(27, 28)).toEqual({ [day(27)]: 1200, [day(28)]: 1200 }); + }); + + it('recomputes the daily totals of an estimate that spans midnight as it shrinks and disappears', async () => { + const book = books[7]!; + const first = measured('midnight-1', 33, '12:00:00', '12:30:00'); + const second = measured('midnight-2', 33, '13:00:00', '13:30:00'); + await pushCounter(deviceA, book, 0, at(32, '07:00:00')); + await pushCounter(deviceA, book, 60, at(34, '00:20:00')); + + expect(await dailySeconds(32, 33, 34)).toEqual({ [day(33)]: 2400, [day(34)]: 1200 }); + + await sendMeasured(deviceA, book, [first]); + + expect(await storedSessions(book)).toEqual([stored(deviceA, first), estimate(deviceA, book, 60, at(34, '00:20:00'), 1800, at(32, '07:00:00'))]); + expect(await dailySeconds(32, 33, 34)).toEqual({ [day(33)]: 2400, [day(34)]: 1200 }); + + await sendMeasured(deviceA, book, [second]); + + expect(await storedSessions(book)).toEqual([stored(deviceA, first), stored(deviceA, second)]); + expect(await dailySeconds(32, 33, 34)).toEqual({ [day(33)]: 3600 }); + }); + + describe('a counter push made while the book is open', () => { + it('retires the estimate when the device syncs on waking, seconds after the book reopens', async () => { + const book = books[8]!; + const session = measured('wake-sync', 37, '20:00:00', '20:30:00'); + await pushCounter(deviceA, book, 100, at(36, '07:00:00')); + await pushCounter(deviceA, book, 100, at(37, '20:00:05')); + await pushCounter(deviceA, book, 130, at(37, '20:30:10')); + + expect(await storedSessions(book)).toEqual([estimate(deviceA, book, 130, at(37, '20:30:10'), 1800, at(37, '20:00:05'))]); + + await sendMeasured(deviceA, book, [session]); + + expect(await storedSessions(book)).toEqual([stored(deviceA, session)]); + expect(await dailySeconds(36, 37, 38)).toEqual({ [day(37)]: 1800 }); + }); + + it('derives no estimate after a wake-up sync when the measured session arrives first', async () => { + const book = books[9]!; + const session = measured('wake-sync-first', 40, '20:00:00', '20:30:00'); + await pushCounter(deviceA, book, 100, at(39, '07:00:00')); + await pushCounter(deviceA, book, 100, at(40, '20:00:05')); + await sendMeasured(deviceA, book, [session]); + await pushCounter(deviceA, book, 130, at(40, '20:30:10')); + + expect(await storedSessions(book)).toEqual([stored(deviceA, session)]); + expect(await dailySeconds(39, 40, 41)).toEqual({ [day(40)]: 1800 }); + }); + + it('shares the session between the estimates on both sides of a push made while reading', async () => { + const book = books[10]!; + const session = measured('mid-session', 43, '20:00:00', '20:30:00'); + await pushCounter(deviceA, book, 100, at(42, '07:00:00')); + await pushCounter(deviceA, book, 110, at(43, '20:10:00')); + await pushCounter(deviceA, book, 130, at(43, '20:30:05')); + + expect(await storedSessions(book)).toEqual([ + estimate(deviceA, book, 110, at(43, '20:10:00'), 600, at(42, '07:00:00')), + estimate(deviceA, book, 130, at(43, '20:30:05'), 1200, at(43, '20:10:00')), + ]); + + await sendMeasured(deviceA, book, [session]); + + expect(await storedSessions(book)).toEqual([stored(deviceA, session)]); + expect(await dailySeconds(42, 43, 44)).toEqual({ [day(43)]: 1800 }); + }); + + it('retires the estimate of reading that carried on after the sync that reported the evening', async () => { + const book = books[11]!; + const evening = measured('carried-on-evening', 46, '20:16:11', '21:09:57'); + const morning = measured('carried-on-morning', 47, '07:41:50', '08:10:00'); + await pushCounter(deviceA, book, 9, at(45, '20:52:44')); + await pushCounter(deviceA, book, 63, at(47, '07:41:58')); + await sendMeasured(deviceA, book, [evening]); + await pushCounter(deviceA, book, 91, at(47, '12:00:00')); + + expect(await storedSessions(book)).toEqual([ + stored(deviceA, evening), + estimate(deviceA, book, 91, at(47, '12:00:00'), 1680, at(47, '07:41:58')), + ]); + + await sendMeasured(deviceA, book, [morning]); + + expect(await storedSessions(book)).toEqual([stored(deviceA, evening), stored(deviceA, morning)]); + expect(await dailySeconds(45, 46, 47)).toEqual({ [day(46)]: 3226, [day(47)]: 1690 }); + }); + + it('keeps the unmeasured reading of an earlier interval that the session only grazes', async () => { + const book = books[12]!; + const session = measured('grazing', 50, '19:59:50', '20:20:00'); + await pushCounter(deviceA, book, 100, at(49, '07:00:00')); + await pushCounter(deviceA, book, 105, at(50, '19:59:55')); + await pushCounter(deviceA, book, 125, at(51, '08:00:00')); + + await sendMeasured(deviceA, book, [session]); + + // Five of the session's 1210 seconds fall before the push, so the earlier estimate gives up only those. + expect(await storedSessions(book)).toEqual([ + estimate(deviceA, book, 105, at(50, '19:59:55'), 295, at(49, '07:00:00')), + stored(deviceA, session), + ]); + expect(await dailySeconds(49, 50, 51)).toEqual({ [day(50)]: 1505 }); + }); + }); +});