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:
2026-04-02 21:01:45 -04:00
parent 40f519ce6e
commit 0667cad8a9
6 changed files with 731 additions and 25 deletions
+91 -2
View File
@@ -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);