feat: add reader infrastructure - Phase 0 database schema and queries
Implement Phase 0 prerequisites for reader functionality including database schema, SQL queries, and frontend dependencies. ## Database Schema (5 New Tables + 1 Column Addition) ### New Tables Added: 1. **panel_data** - Comic/manga panel detection results - Stores detected panel boundaries (x, y, width, height) - Supports grid, ML, and manual detection methods - JSONB storage for flexible panel structures 2. **reading_speed** - User reading speed statistics - Tracks pages per minute and total reading time - Per-user per-media-item tracking - Enables progress estimation and analytics 3. **dictionary_cache** - Offline dictionary word definitions - Caches external dictionary lookups - Reduces API calls and improves performance - Supports offline reading functionality 4. **reader_settings** - User reader preferences (per-user) - Stores typography, theme, and display settings - JSONB storage for flexible configuration - Per-user customization (fonts, margins, themes) 5. **media_bookmarks** - Enhanced bookmarks with chapter/CFI support - Unified bookmarking for ebooks, comics, manga, PDFs - Supports page_number, chapter_number, and epubcfi_position - Includes notes field for annotations - Unique constraint on (media_item_id, user_id, title) ### Column Addition: - **media_items.chapter_metadata** (JSONB) - Caches detected chapter structure - Stores TOC/chapter detection results - Prevents re-parsing files on every read - Populated by ReaderService.DetectChapters() ## Database Queries (12 New Queries) Added queries for all reader functionality: - Panel data: GetPanelData, UpsertPanelData - Reading speed: GetReadingSpeed, CreateReadingSpeed, UpdateReadingSpeed - Dictionary: GetDictionaryEntry, CreateDictionaryEntry, UpdateDictionaryAccessed - Settings: GetReaderSettings, UpsertReaderSettings - Bookmarks: GetMediaBookmarks, CreateMediaBookmark, DeleteMediaBookmark, UpdateMediaBookmark ## Frontend Dependencies Added to package.json: - jszip@^3.10.1 - EPUB/comic archive parsing (client-side) - pdfjs-dist@^3.11.174 - PDF rendering library (Mozilla PDF.js) ## Generated Code Ran `sqlc generate` to regenerate: - models.go - Go structs for new tables (55 lines added) - querier.go - Database query methods (14 lines added) - queries.sql.go - Compiled SQL queries (504 lines added) ## Implementation Status Phase 0 prerequisites now complete: ✅ Database schema (5 tables + 1 column) ✅ SQL queries (12 queries) ✅ Frontend dependencies (2 packages) ✅ Generated Go code (sqlc) ✅ Database recreated with new schema Ready for Phase 1: Infrastructure & Basic Reader implementation. Related to: Universal web reader for ebooks, comics, manga, PDFs
This commit is contained in:
@@ -166,7 +166,21 @@ CREATE TABLE IF NOT EXISTS media_items (
|
||||
scan_information TEXT, -- Scan information (scanner group, resolution, etc.)
|
||||
|
||||
-- Summary (distinct from description - may merge with Calibre description)
|
||||
summary TEXT -- Summary from ComicInfo.xml (may be merged with description from Calibre)
|
||||
summary TEXT, -- Summary from ComicInfo.xml (may be merged with description from Calibre)
|
||||
|
||||
-- Chapter metadata for reader navigation and progress tracking
|
||||
-- Caches detected chapter structure to avoid re-parsing files
|
||||
-- Populated by ReaderService.DetectChapters() on first read
|
||||
-- JSONB structure:
|
||||
-- {
|
||||
-- "chapters": [
|
||||
-- {"id": "chap1", "title": "Chapter 1", "start_page": 1, "page_count": 20, "level": 1},
|
||||
-- {"id": "chap2", "title": "Chapter 2", "start_page": 21, "page_count": 25, "level": 1}
|
||||
-- ],
|
||||
-- "detected_at": "2024-01-15T10:30:00Z",
|
||||
-- "detection_method": "epub-toc" | "pdf-outline" | "page-breaks"
|
||||
-- }
|
||||
chapter_metadata JSONB
|
||||
);
|
||||
|
||||
-- Add GIN indexes for fast array searches
|
||||
@@ -1122,7 +1136,7 @@ CREATE INDEX IF NOT EXISTS idx_saved_filters_name ON saved_filters(user_id, name
|
||||
-- Prevents duplicate filter names while allowing:
|
||||
-- - Same name for different users
|
||||
-- - Same name for different resources (media-items vs collections)
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_saved_filters_user_name_resource
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_saved_filters_user_name_resource
|
||||
ON saved_filters(user_id, name, resource_type);
|
||||
|
||||
-- Trigger to auto-update updated_at timestamp
|
||||
@@ -1140,3 +1154,78 @@ CREATE TRIGGER update_saved_filters_updated_at
|
||||
BEFORE UPDATE ON saved_filters
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Panel detection data
|
||||
CREATE TABLE IF NOT EXISTS panel_data (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
|
||||
page_number INTEGER NOT NULL,
|
||||
detection_method VARCHAR(20) NOT NULL, -- 'grid', 'ml', 'manual'
|
||||
panels JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(media_item_id, page_number)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_panel_data_media_item ON panel_data(media_item_id);
|
||||
|
||||
-- Reading speed tracking
|
||||
CREATE TABLE IF NOT EXISTS reading_speed (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
|
||||
words_per_minute DECIMAL(6,2),
|
||||
pages_per_minute DECIMAL(6,2),
|
||||
pages_read INTEGER DEFAULT 0,
|
||||
total_reading_minutes DECIMAL(8,2) DEFAULT 0,
|
||||
last_read_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(user_id, media_item_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_reading_speed_user ON reading_speed(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_reading_speed_item ON reading_speed(media_item_id);
|
||||
|
||||
-- Dictionary cache (for offline use)
|
||||
CREATE TABLE IF NOT EXISTS dictionary_cache (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
word VARCHAR(100) NOT NULL UNIQUE,
|
||||
definition TEXT NOT NULL,
|
||||
part_of_speech VARCHAR(20),
|
||||
example TEXT,
|
||||
etymology TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
accessed_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_dictionary_word ON dictionary_cache(word);
|
||||
|
||||
-- Reader settings (per-user preferences)
|
||||
CREATE TABLE IF NOT EXISTS reader_settings (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
setting_key VARCHAR(50) NOT NULL,
|
||||
setting_value JSONB NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(user_id, setting_key)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_reader_settings_user ON reader_settings(user_id);
|
||||
|
||||
-- PDF bookmarks (custom user bookmarks)
|
||||
CREATE TABLE IF NOT EXISTS media_bookmarks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
media_item_id UUID NOT NULL REFERENCES media_items(id) ON DELETE CASCADE,
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
page_number INTEGER,
|
||||
chapter_number INTEGER,
|
||||
cfi_position VARCHAR(255), -- For ebooks: EPUB CFI position
|
||||
title VARCHAR(255) NOT NULL,
|
||||
position VARCHAR(100), -- 'pdf:page:45', 'comic:page:12', 'chapter:3' for consistency
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(media_item_id, user_id, title)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_media_bookmarks_media ON media_bookmarks(media_item_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_media_bookmarks_user ON media_bookmarks(user_id);
|
||||
|
||||
Reference in New Issue
Block a user