Database model¶
This section presents the database schema for Papyrus, showing table structures, relationships, constraints, and design principles.
Schema overview¶
The database consists of 16 tables organized into functional groups:
| Group | Tables |
|---|---|
| User Management | USER |
| Book Management | BOOK, SHELF, TAG, SERIES |
| Reading Experience | ANNOTATION, NOTE, BOOKMARK |
| Progress & Goals | READING_SESSION, READING_GOAL |
| Configuration | READING_PROFILE, METADATA_SERVER_CONFIG, FILE_STORAGE_BACKEND, SAVED_FILTER |
| Junction Tables | BOOK_SHELF, BOOK_TAG |
Entity relationship diagram¶
erDiagram
USER {
uuid user_id PK
varchar email UK
varchar password_hash
varchar display_name
varchar avatar_url
varchar google_id UK
boolean is_anonymous
boolean is_active
boolean email_verified
timestamp created_at
timestamp updated_at
timestamp last_login_at
jsonb preferences
}
BOOK {
uuid book_id PK
uuid user_id FK
varchar title
varchar subtitle
varchar author
jsonb co_authors
varchar isbn
varchar isbn13
date publication_date
varchar publisher
varchar language
integer page_count
text description
varchar cover_image_url
uuid series_id FK
decimal series_number
varchar file_path
varchar file_format
bigint file_size
varchar file_hash
uuid storage_backend_id FK
boolean is_physical
varchar physical_location
varchar lent_to
timestamp lent_at
boolean is_favorite
smallint rating
varchar reading_status
integer current_page
decimal current_position
varchar current_cfi
timestamp started_at
timestamp completed_at
timestamp added_at
timestamp last_read_at
jsonb custom_metadata
boolean is_ocr_processed
decimal ocr_confidence
}
SHELF {
uuid shelf_id PK
uuid user_id FK
uuid parent_shelf_id FK
varchar name
text description
varchar color
varchar icon
boolean is_default
boolean is_smart
varchar smart_query
integer sort_order
timestamp created_at
timestamp updated_at
}
TAG {
uuid tag_id PK
uuid user_id FK
varchar name
varchar color
text description
integer usage_count
timestamp created_at
}
SERIES {
uuid series_id PK
uuid user_id FK
varchar name
text description
varchar author
integer total_books
boolean is_complete
timestamp created_at
timestamp updated_at
}
ANNOTATION {
uuid annotation_id PK
uuid book_id FK
uuid user_id FK
text selected_text
text note
varchar highlight_color
varchar start_position
varchar end_position
varchar chapter_title
integer chapter_index
integer page_number
timestamp created_at
timestamp updated_at
}
NOTE {
uuid note_id PK
uuid book_id FK
uuid user_id FK
varchar title
text content
boolean is_pinned
timestamp created_at
timestamp updated_at
}
BOOKMARK {
uuid bookmark_id PK
uuid book_id FK
uuid user_id FK
varchar position
integer page_number
varchar chapter_title
varchar note
varchar color
timestamp created_at
}
READING_SESSION {
uuid session_id PK
uuid book_id FK
uuid user_id FK
timestamp start_time
timestamp end_time
decimal start_position
decimal end_position
integer pages_read
integer duration_minutes
varchar device_type
varchar device_name
timestamp created_at
}
READING_GOAL {
uuid goal_id PK
uuid user_id FK
varchar title
text description
varchar goal_type
integer target_value
integer current_value
varchar time_period
date start_date
date end_date
boolean is_active
boolean is_completed
timestamp completed_at
timestamp created_at
timestamp updated_at
}
READING_PROFILE {
uuid profile_id PK
uuid user_id FK
varchar name
boolean is_default
varchar font_family
smallint font_size
smallint font_weight
decimal line_height
decimal letter_spacing
decimal paragraph_spacing
varchar text_align
smallint margin_horizontal
smallint margin_vertical
varchar background_color
varchar text_color
varchar link_color
varchar selection_color
varchar theme_mode
varchar reading_mode
boolean page_turn_animation
smallint column_count
boolean hyphenation
timestamp created_at
timestamp updated_at
}
METADATA_SERVER_CONFIG {
uuid config_id PK
uuid user_id FK "unique"
varchar server_url
varchar server_type
text auth_token
text refresh_token
boolean is_connected
boolean sync_enabled
integer sync_interval_seconds
timestamp last_sync_at
varchar sync_status
varchar sync_error_message
timestamp created_at
timestamp updated_at
}
FILE_STORAGE_BACKEND {
uuid backend_id PK
uuid user_id FK
varchar backend_type
varchar name
boolean is_primary
boolean is_active
text connection_config
text credentials
varchar base_path
bigint storage_used_bytes
bigint storage_quota_bytes
timestamp last_accessed_at
varchar connection_status
varchar error_message
timestamp created_at
timestamp updated_at
}
SAVED_FILTER {
uuid filter_id PK
uuid user_id FK
varchar name
text description
varchar query
varchar filter_type
varchar icon
varchar color
boolean is_pinned
integer usage_count
timestamp last_used_at
timestamp created_at
timestamp updated_at
}
BOOK_SHELF {
uuid book_id FK
uuid shelf_id FK
timestamp added_at
integer sort_order
}
BOOK_TAG {
uuid book_id FK
uuid tag_id FK
timestamp created_at
}
USER ||--o{ BOOK : owns
USER ||--o{ SHELF : creates
USER ||--o{ TAG : creates
USER ||--o{ SERIES : creates
USER ||--o{ ANNOTATION : makes
USER ||--o{ NOTE : writes
USER ||--o{ BOOKMARK : creates
USER ||--o{ READING_SESSION : has
USER ||--o{ READING_GOAL : sets
USER ||--o{ READING_PROFILE : configures
USER ||--o| METADATA_SERVER_CONFIG : connects_to
USER ||--o{ FILE_STORAGE_BACKEND : configures
USER ||--o{ SAVED_FILTER : saves
BOOK }o--o| FILE_STORAGE_BACKEND : stored_in
BOOK ||--o{ ANNOTATION : contains
BOOK ||--o{ NOTE : has
BOOK ||--o{ BOOKMARK : has
BOOK ||--o{ READING_SESSION : tracked_in
BOOK }o--o| SERIES : belongs_to
SHELF }o--o| SHELF : parent_of
BOOK }o--o{ SHELF : organized_in
BOOK }o--o{ TAG : tagged_with
BOOK_SHELF }o--|| BOOK : references
BOOK_SHELF }o--|| SHELF : references
BOOK_TAG }o--|| BOOK : references
BOOK_TAG }o--|| TAG : references Table definitions¶
USER¶
CREATE TABLE "user" (
user_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE,
password_hash VARCHAR(255),
display_name VARCHAR(100) NOT NULL,
avatar_url VARCHAR(500),
google_id VARCHAR(100) UNIQUE,
is_anonymous BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_login_at TIMESTAMPTZ,
preferences JSONB NOT NULL DEFAULT '{}'::jsonb,
CONSTRAINT email_or_anonymous CHECK (
(email IS NOT NULL AND is_anonymous = FALSE) OR
(email IS NULL AND is_anonymous = TRUE)
)
);
CREATE INDEX idx_user_email ON "user"(email);
CREATE INDEX idx_user_google_id ON "user"(google_id);
BOOK¶
CREATE TABLE book (
book_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
subtitle VARCHAR(500),
author VARCHAR(255) NOT NULL,
co_authors JSONB NOT NULL DEFAULT '[]'::jsonb,
isbn VARCHAR(13),
isbn13 VARCHAR(17),
publication_date DATE,
publisher VARCHAR(255),
language VARCHAR(10) NOT NULL DEFAULT 'en',
page_count INTEGER,
description TEXT,
cover_image_url VARCHAR(1000),
series_id UUID REFERENCES series(series_id) ON DELETE SET NULL,
series_number DECIMAL(5,2),
file_path VARCHAR(1000),
file_format VARCHAR(20),
file_size BIGINT,
file_hash VARCHAR(64), -- SHA-256 hash
storage_backend_id UUID REFERENCES file_storage_backend(backend_id) ON DELETE SET NULL,
is_physical BOOLEAN NOT NULL DEFAULT FALSE,
physical_location VARCHAR(255),
lent_to VARCHAR(255),
lent_at TIMESTAMPTZ,
is_favorite BOOLEAN NOT NULL DEFAULT FALSE,
rating SMALLINT CHECK (rating >= 1 AND rating <= 5),
reading_status VARCHAR(20) NOT NULL DEFAULT 'not_started'
CHECK (reading_status IN ('not_started', 'in_progress', 'completed', 'paused', 'abandoned')),
current_page INTEGER,
current_position DECIMAL(5,4) CHECK (current_position >= 0 AND current_position <= 1),
current_cfi VARCHAR(500),
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_read_at TIMESTAMPTZ,
custom_metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
is_ocr_processed BOOLEAN NOT NULL DEFAULT FALSE,
ocr_confidence DECIMAL(3,2) CHECK (ocr_confidence >= 0 AND ocr_confidence <= 1)
);
CREATE INDEX idx_book_user_id ON book(user_id);
CREATE INDEX idx_book_title ON book(title);
CREATE INDEX idx_book_author ON book(author);
CREATE INDEX idx_book_reading_status ON book(reading_status);
CREATE INDEX idx_book_series_id ON book(series_id);
CREATE INDEX idx_book_storage_backend ON book(storage_backend_id);
CREATE INDEX idx_book_added_at ON book(added_at DESC);
CREATE INDEX idx_book_last_read_at ON book(last_read_at DESC NULLS LAST);
SHELF¶
CREATE TABLE shelf (
shelf_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
parent_shelf_id UUID REFERENCES shelf(shelf_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
color VARCHAR(7),
icon VARCHAR(50),
is_default BOOLEAN NOT NULL DEFAULT FALSE,
is_smart BOOLEAN NOT NULL DEFAULT FALSE,
smart_query VARCHAR(500),
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT unique_shelf_name_per_parent UNIQUE (user_id, parent_shelf_id, name)
);
CREATE INDEX idx_shelf_user_id ON shelf(user_id);
CREATE INDEX idx_shelf_parent_id ON shelf(parent_shelf_id);
TAG¶
CREATE TABLE tag (
tag_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
name VARCHAR(50) NOT NULL,
color VARCHAR(7) NOT NULL,
description TEXT,
usage_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT unique_tag_name_per_user UNIQUE (user_id, name)
);
CREATE INDEX idx_tag_user_id ON tag(user_id);
CREATE INDEX idx_tag_name ON tag(name);
SERIES¶
CREATE TABLE series (
series_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
description TEXT,
author VARCHAR(255),
total_books INTEGER,
is_complete BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT unique_series_name_per_user UNIQUE (user_id, name)
);
CREATE INDEX idx_series_user_id ON series(user_id);
ANNOTATION¶
CREATE TABLE annotation (
annotation_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
book_id UUID NOT NULL REFERENCES book(book_id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
selected_text TEXT NOT NULL,
note TEXT,
highlight_color VARCHAR(7) NOT NULL,
start_position VARCHAR(200) NOT NULL,
end_position VARCHAR(200) NOT NULL,
chapter_title VARCHAR(255),
chapter_index INTEGER,
page_number INTEGER,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_annotation_book_id ON annotation(book_id);
CREATE INDEX idx_annotation_user_id ON annotation(user_id);
CREATE INDEX idx_annotation_created_at ON annotation(created_at DESC);
NOTE¶
CREATE TABLE note (
note_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
book_id UUID NOT NULL REFERENCES book(book_id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
is_pinned BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_note_book_id ON note(book_id);
CREATE INDEX idx_note_user_id ON note(user_id);
BOOKMARK¶
CREATE TABLE bookmark (
bookmark_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
book_id UUID NOT NULL REFERENCES book(book_id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
position VARCHAR(200) NOT NULL,
page_number INTEGER,
chapter_title VARCHAR(255),
note VARCHAR(500),
color VARCHAR(7) NOT NULL DEFAULT '#FF5722',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_bookmark_book_id ON bookmark(book_id);
CREATE INDEX idx_bookmark_user_id ON bookmark(user_id);
READING_SESSION¶
CREATE TABLE reading_session (
session_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
book_id UUID NOT NULL REFERENCES book(book_id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
start_time TIMESTAMPTZ NOT NULL,
end_time TIMESTAMPTZ,
start_position DECIMAL(5,4),
end_position DECIMAL(5,4),
pages_read INTEGER,
duration_minutes INTEGER GENERATED ALWAYS AS (
CASE WHEN end_time IS NOT NULL
THEN EXTRACT(EPOCH FROM (end_time - start_time)) / 60
ELSE NULL END
) STORED,
device_type VARCHAR(50),
device_name VARCHAR(100),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_reading_session_book_id ON reading_session(book_id);
CREATE INDEX idx_reading_session_user_id ON reading_session(user_id);
CREATE INDEX idx_reading_session_start_time ON reading_session(start_time DESC);
READING_GOAL¶
CREATE TABLE reading_goal (
goal_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
description TEXT,
goal_type VARCHAR(20) NOT NULL
CHECK (goal_type IN ('books_count', 'pages_count', 'reading_time')),
target_value INTEGER NOT NULL CHECK (target_value > 0),
current_value INTEGER NOT NULL DEFAULT 0,
time_period VARCHAR(20) NOT NULL
CHECK (time_period IN ('daily', 'weekly', 'monthly', 'yearly', 'custom')),
start_date DATE NOT NULL,
end_date DATE NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
is_completed BOOLEAN NOT NULL DEFAULT FALSE,
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT valid_date_range CHECK (end_date > start_date)
);
CREATE INDEX idx_reading_goal_user_id ON reading_goal(user_id);
CREATE INDEX idx_reading_goal_is_active ON reading_goal(is_active);
READING_PROFILE¶
CREATE TABLE reading_profile (
profile_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
is_default BOOLEAN NOT NULL DEFAULT FALSE,
font_family VARCHAR(100) NOT NULL DEFAULT 'Georgia',
font_size SMALLINT NOT NULL DEFAULT 16 CHECK (font_size >= 8 AND font_size <= 72),
font_weight SMALLINT NOT NULL DEFAULT 400,
line_height DECIMAL(3,2) NOT NULL DEFAULT 1.5,
letter_spacing DECIMAL(3,2) NOT NULL DEFAULT 0,
paragraph_spacing DECIMAL(3,2) NOT NULL DEFAULT 1.0,
text_align VARCHAR(10) NOT NULL DEFAULT 'left'
CHECK (text_align IN ('left', 'right', 'center', 'justify')),
margin_horizontal SMALLINT NOT NULL DEFAULT 20,
margin_vertical SMALLINT NOT NULL DEFAULT 20,
background_color VARCHAR(7) NOT NULL DEFAULT '#FFFFFF',
text_color VARCHAR(7) NOT NULL DEFAULT '#000000',
link_color VARCHAR(7) NOT NULL DEFAULT '#0066CC',
selection_color VARCHAR(7) NOT NULL DEFAULT '#B3D4FC',
theme_mode VARCHAR(10) NOT NULL DEFAULT 'light'
CHECK (theme_mode IN ('light', 'dark', 'sepia', 'custom')),
reading_mode VARCHAR(15) NOT NULL DEFAULT 'paginated'
CHECK (reading_mode IN ('paginated', 'continuous')),
page_turn_animation BOOLEAN NOT NULL DEFAULT TRUE,
column_count SMALLINT NOT NULL DEFAULT 1 CHECK (column_count >= 1 AND column_count <= 3),
hyphenation BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_reading_profile_user_id ON reading_profile(user_id);
METADATA_SERVER_CONFIG¶
Stores the user's connection to a metadata server for cross-device synchronization. Each user has at most one configuration.
CREATE TABLE metadata_server_config (
config_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL UNIQUE REFERENCES "user"(user_id) ON DELETE CASCADE,
server_url VARCHAR(500) NOT NULL,
server_type VARCHAR(20) NOT NULL DEFAULT 'official'
CHECK (server_type IN ('official', 'self_hosted')),
auth_token TEXT, -- Encrypted JWT
refresh_token TEXT, -- Encrypted
is_connected BOOLEAN NOT NULL DEFAULT FALSE,
sync_enabled BOOLEAN NOT NULL DEFAULT TRUE,
sync_interval_seconds INTEGER NOT NULL DEFAULT 30,
last_sync_at TIMESTAMPTZ,
sync_status VARCHAR(20) NOT NULL DEFAULT 'idle'
CHECK (sync_status IN ('idle', 'syncing', 'error')),
sync_error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Unique constraint ensures one config per user
CREATE UNIQUE INDEX idx_metadata_server_config_user ON metadata_server_config(user_id);
FILE_STORAGE_BACKEND¶
Stores configurations for file storage backends. Users can have multiple backends configured.
CREATE TABLE file_storage_backend (
backend_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
backend_type VARCHAR(20) NOT NULL
CHECK (backend_type IN ('local', 'google_drive', 'onedrive', 'dropbox', 'webdav', 'minio', 's3', 'papyrus_server')),
name VARCHAR(100) NOT NULL,
is_primary BOOLEAN NOT NULL DEFAULT FALSE,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
connection_config TEXT, -- Encrypted JSON (type-specific settings)
credentials TEXT, -- Encrypted JSON (OAuth tokens or API keys)
base_path VARCHAR(500),
storage_used_bytes BIGINT,
storage_quota_bytes BIGINT,
last_accessed_at TIMESTAMPTZ,
connection_status VARCHAR(20) NOT NULL DEFAULT 'disconnected'
CHECK (connection_status IN ('connected', 'disconnected', 'error')),
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_file_storage_backend_user_id ON file_storage_backend(user_id);
-- Ensure only one primary backend per user
CREATE UNIQUE INDEX idx_file_storage_backend_primary
ON file_storage_backend(user_id)
WHERE is_primary = TRUE;
SAVED_FILTER¶
CREATE TABLE saved_filter (
filter_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES "user"(user_id) ON DELETE CASCADE,
name VARCHAR(100) NOT NULL,
description TEXT,
query VARCHAR(1000) NOT NULL,
filter_type VARCHAR(20) NOT NULL DEFAULT 'custom'
CHECK (filter_type IN ('search', 'shelf', 'custom')),
icon VARCHAR(50),
color VARCHAR(7),
is_pinned BOOLEAN NOT NULL DEFAULT FALSE,
usage_count INTEGER NOT NULL DEFAULT 0,
last_used_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_saved_filter_user_id ON saved_filter(user_id);
Junction tables¶
CREATE TABLE book_shelf (
book_id UUID NOT NULL REFERENCES book(book_id) ON DELETE CASCADE,
shelf_id UUID NOT NULL REFERENCES shelf(shelf_id) ON DELETE CASCADE,
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
sort_order INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (book_id, shelf_id)
);
CREATE INDEX idx_book_shelf_shelf_id ON book_shelf(shelf_id);
CREATE TABLE book_tag (
book_id UUID NOT NULL REFERENCES book(book_id) ON DELETE CASCADE,
tag_id UUID NOT NULL REFERENCES tag(tag_id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (book_id, tag_id)
);
CREATE INDEX idx_book_tag_tag_id ON book_tag(tag_id);
Design principles¶
Data integrity¶
- Foreign key constraints ensure referential integrity
- Check constraints validate data ranges and enums
- Unique constraints prevent duplicate entries
- NOT NULL constraints ensure required fields
Performance optimization¶
- UUID primary keys for distributed systems
- Indexes on frequently queried columns
- JSONB for flexible metadata storage
- Partial indexes for filtered queries
- Generated columns for computed values
Scalability¶
- Cascade deletes for clean data removal
- Nullable foreign keys for optional relationships
- Timestamp fields for audit trails
- Soft deletes via is_active flags where appropriate
Security¶
- Encrypted columns for credentials
- Hashed passwords (stored externally via auth)
- No sensitive data in plain text
- User isolation via user_id foreign keys
Migration notes¶
Initial setup¶
- Create tables in dependency order (USER first)
- Create indexes after data population for speed
- Insert default shelves per user on registration
Version history¶
| Version | Changes |
|---|---|
| 1.0.0 | Initial schema |
| 1.1.0 | Added SERIES, BOOKMARK, SAVED_FILTER tables |
| 1.2.0 | Added parent_shelf_id for nested shelves |