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
+55
View File
@@ -81,6 +81,17 @@ type Devices struct {
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
}
type DictionaryCache struct {
ID pgtype.UUID `db:"id" json:"id"`
Word string `db:"word" json:"word"`
Definition string `db:"definition" json:"definition"`
PartOfSpeech pgtype.Text `db:"part_of_speech" json:"part_of_speech"`
Example pgtype.Text `db:"example" json:"example"`
Etymology pgtype.Text `db:"etymology" json:"etymology"`
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
AccessedAt pgtype.Timestamptz `db:"accessed_at" json:"accessed_at"`
}
type KoboEntitlements struct {
ID pgtype.UUID `db:"id" json:"id"`
DeviceID pgtype.UUID `db:"device_id" json:"device_id"`
@@ -143,6 +154,19 @@ type LibraryVisibility struct {
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
}
type MediaBookmarks struct {
ID pgtype.UUID `db:"id" json:"id"`
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
UserID pgtype.UUID `db:"user_id" json:"user_id"`
PageNumber pgtype.Int4 `db:"page_number" json:"page_number"`
ChapterNumber pgtype.Int4 `db:"chapter_number" json:"chapter_number"`
CfiPosition pgtype.Text `db:"cfi_position" json:"cfi_position"`
Title string `db:"title" json:"title"`
Position pgtype.Text `db:"position" json:"position"`
Notes pgtype.Text `db:"notes" json:"notes"`
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
}
type MediaHighlights struct {
ID pgtype.UUID `db:"id" json:"id"`
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
@@ -254,6 +278,7 @@ type MediaItems struct {
ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"`
// Summary from ComicInfo.xml (may be merged with description from Calibre)
Summary pgtype.Text `db:"summary" json:"summary"`
ChapterMetadata []byte `db:"chapter_metadata" json:"chapter_metadata"`
TagsSearch []string `db:"tags_search" json:"tags_search"`
ContributorsSearch []string `db:"contributors_search" json:"contributors_search"`
FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"`
@@ -298,6 +323,24 @@ type OpdsTokens struct {
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
}
type PanelData struct {
ID pgtype.UUID `db:"id" json:"id"`
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
PageNumber int32 `db:"page_number" json:"page_number"`
DetectionMethod string `db:"detection_method" json:"detection_method"`
Panels []byte `db:"panels" json:"panels"`
CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"`
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
}
type ReaderSettings struct {
ID pgtype.UUID `db:"id" json:"id"`
UserID pgtype.UUID `db:"user_id" json:"user_id"`
SettingKey string `db:"setting_key" json:"setting_key"`
SettingValue []byte `db:"setting_value" json:"setting_value"`
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
}
type ReadingHistory struct {
ID pgtype.UUID `db:"id" json:"id"`
UserID pgtype.UUID `db:"user_id" json:"user_id"`
@@ -338,6 +381,18 @@ type ReadingProgress struct {
ConflictResolved pgtype.Bool `db:"conflict_resolved" json:"conflict_resolved"`
}
type ReadingSpeed struct {
ID pgtype.UUID `db:"id" json:"id"`
UserID pgtype.UUID `db:"user_id" json:"user_id"`
MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"`
WordsPerMinute pgtype.Numeric `db:"words_per_minute" json:"words_per_minute"`
PagesPerMinute pgtype.Numeric `db:"pages_per_minute" json:"pages_per_minute"`
PagesRead pgtype.Int4 `db:"pages_read" json:"pages_read"`
TotalReadingMinutes pgtype.Numeric `db:"total_reading_minutes" json:"total_reading_minutes"`
LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"`
UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"`
}
type RefreshTokens struct {
ID pgtype.UUID `db:"id" json:"id"`
UserID pgtype.UUID `db:"user_id" json:"user_id"`