From 0667cad8a93c7d8126c7fd10bea39ea8e1c7f05e Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Thu, 2 Apr 2026 21:01:45 -0400 Subject: [PATCH] feat: add reader infrastructure - Phase 0 database schema and queries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- database/schema/schema.sql | 93 ++++- internal/database/models.go | 55 +++ internal/database/querier.go | 14 + internal/database/queries.sql.go | 504 ++++++++++++++++++++++++-- internal/database/queries/queries.sql | 86 +++++ package.json | 4 +- 6 files changed, 731 insertions(+), 25 deletions(-) diff --git a/database/schema/schema.sql b/database/schema/schema.sql index 8a6397f..aac812b 100644 --- a/database/schema/schema.sql +++ b/database/schema/schema.sql @@ -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); diff --git a/internal/database/models.go b/internal/database/models.go index c78efd4..90abeef 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -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"` diff --git a/internal/database/querier.go b/internal/database/querier.go index 7054865..3259fea 100644 --- a/internal/database/querier.go +++ b/internal/database/querier.go @@ -49,8 +49,10 @@ type Querier interface { // DEVICE SHELF MAPPINGS QUERIES // Create device shelf mapping CreateDeviceShelfMapping(ctx context.Context, arg CreateDeviceShelfMappingParams) (DeviceShelfMappings, error) + CreateDictionaryEntry(ctx context.Context, arg CreateDictionaryEntryParams) (DictionaryCache, error) // Libraries queries CreateLibrary(ctx context.Context, arg CreateLibraryParams) (Libraries, error) + CreateMediaBookmark(ctx context.Context, arg CreateMediaBookmarkParams) (MediaBookmarks, error) // Media Highlights queries CreateMediaHighlight(ctx context.Context, arg CreateMediaHighlightParams) (MediaHighlights, error) // Media Items queries @@ -70,6 +72,7 @@ type Querier interface { CreateOrUpdateKoboEntitlement(ctx context.Context, arg CreateOrUpdateKoboEntitlementParams) (KoboEntitlements, error) // Create reading history entry CreateReadingHistory(ctx context.Context, arg CreateReadingHistoryParams) (ReadingHistory, error) + CreateReadingSpeed(ctx context.Context, arg CreateReadingSpeedParams) (ReadingSpeed, error) // Refresh Tokens queries CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshTokens, error) CreateSavedFilter(ctx context.Context, arg CreateSavedFilterParams) (SavedFilters, error) @@ -98,6 +101,7 @@ type Querier interface { DeleteKoboEntitlement(ctx context.Context, arg DeleteKoboEntitlementParams) error DeleteLibrary(ctx context.Context, id pgtype.UUID) error DeleteLibraryFolder(ctx context.Context, arg DeleteLibraryFolderParams) (LibraryFolders, error) + DeleteMediaBookmark(ctx context.Context, id pgtype.UUID) error DeleteMediaHighlight(ctx context.Context, id pgtype.UUID) error DeleteMediaItem(ctx context.Context, id pgtype.UUID) error // Delete media item format @@ -156,6 +160,7 @@ type Querier interface { GetDeviceShelfMapping(ctx context.Context, arg GetDeviceShelfMappingParams) (DeviceShelfMappings, error) // Get device shelf mappings GetDeviceShelfMappings(ctx context.Context, deviceID pgtype.UUID) ([]GetDeviceShelfMappingsRow, error) + GetDictionaryEntry(ctx context.Context, word string) (DictionaryCache, error) GetFailedSyncQueueItems(ctx context.Context, limit int32) ([]SyncQueue, error) GetKoboEntitlementByContentId(ctx context.Context, arg GetKoboEntitlementByContentIdParams) (GetKoboEntitlementByContentIdRow, error) GetKoboEntitlementByEntitlementId(ctx context.Context, arg GetKoboEntitlementByEntitlementIdParams) (GetKoboEntitlementByEntitlementIdRow, error) @@ -174,6 +179,7 @@ type Querier interface { // Library Types queries GetLibraryTypes(ctx context.Context) ([]LibraryTypes, error) GetLibraryVisibility(ctx context.Context, arg GetLibraryVisibilityParams) (LibraryVisibility, error) + GetMediaBookmarks(ctx context.Context, arg GetMediaBookmarksParams) ([]MediaBookmarks, error) GetMediaHighlight(ctx context.Context, id pgtype.UUID) (MediaHighlights, error) GetMediaHighlights(ctx context.Context, arg GetMediaHighlightsParams) ([]MediaHighlights, error) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, error) @@ -206,11 +212,14 @@ type Querier interface { GetOpdsToken(ctx context.Context, token string) (GetOpdsTokenRow, error) // Get OPDS tokens by device GetOpdsTokensByDevice(ctx context.Context, deviceID pgtype.UUID) ([]OpdsTokens, error) + GetPanelData(ctx context.Context, arg GetPanelDataParams) (PanelData, error) // Get most popular books for a user GetPopularBooks(ctx context.Context, arg GetPopularBooksParams) ([]GetPopularBooksRow, error) + GetReaderSettings(ctx context.Context, userID pgtype.UUID) ([]byte, error) // Get reading history for a user and book GetReadingHistory(ctx context.Context, arg GetReadingHistoryParams) ([]ReadingHistory, error) GetReadingProgress(ctx context.Context, arg GetReadingProgressParams) (ReadingProgress, error) + GetReadingSpeed(ctx context.Context, arg GetReadingSpeedParams) (ReadingSpeed, error) GetRecentlyAddedItems(ctx context.Context, arg GetRecentlyAddedItemsParams) ([]MediaItems, error) GetRecentlyReadItems(ctx context.Context, arg GetRecentlyReadItemsParams) ([]MediaItems, error) GetRefreshToken(ctx context.Context, token pgtype.UUID) (GetRefreshTokenRow, error) @@ -309,6 +318,7 @@ type Querier interface { // Update device shelf mapping UpdateDeviceShelfMapping(ctx context.Context, arg UpdateDeviceShelfMappingParams) (DeviceShelfMappings, error) UpdateDeviceSyncTimestamp(ctx context.Context, id pgtype.UUID) (Devices, error) + UpdateDictionaryAccessed(ctx context.Context, word string) (DictionaryCache, error) UpdateEmail(ctx context.Context, arg UpdateEmailParams) error UpdateKoboEntitlementRevision(ctx context.Context, arg UpdateKoboEntitlementRevisionParams) error UpdateKoboEntitlementStatus(ctx context.Context, arg UpdateKoboEntitlementStatusParams) error @@ -316,6 +326,7 @@ type Querier interface { // Update Kobo shelves to support collections UpdateKoboShelfCollection(ctx context.Context, arg UpdateKoboShelfCollectionParams) (KoboShelves, error) UpdateLibrary(ctx context.Context, arg UpdateLibraryParams) (Libraries, error) + UpdateMediaBookmark(ctx context.Context, arg UpdateMediaBookmarkParams) (MediaBookmarks, error) UpdateMediaHighlight(ctx context.Context, arg UpdateMediaHighlightParams) (MediaHighlights, error) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams) (MediaItems, error) // Update media item format @@ -336,6 +347,7 @@ type Querier interface { UpdateMediaRating(ctx context.Context, arg UpdateMediaRatingParams) (MediaRatings, error) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error UpdateReadingProgress(ctx context.Context, arg UpdateReadingProgressParams) (ReadingProgress, error) + UpdateReadingSpeed(ctx context.Context, arg UpdateReadingSpeedParams) (ReadingSpeed, error) UpdateSavedFilter(ctx context.Context, arg UpdateSavedFilterParams) (SavedFilters, error) UpdateSyncQueueItemStatus(ctx context.Context, arg UpdateSyncQueueItemStatusParams) (SyncQueue, error) UpdateSystemSetting(ctx context.Context, arg UpdateSystemSettingParams) error @@ -347,6 +359,8 @@ type Querier interface { UpdateUserTheme(ctx context.Context, arg UpdateUserThemeParams) error UpdateUsername(ctx context.Context, arg UpdateUsernameParams) error UpsertDashboardPreferences(ctx context.Context, arg UpsertDashboardPreferencesParams) (UserDashboardPreferences, error) + UpsertPanelData(ctx context.Context, arg UpsertPanelDataParams) (PanelData, error) + UpsertReaderSettings(ctx context.Context, arg UpsertReaderSettingsParams) (ReaderSettings, error) } var _ Querier = (*Queries)(nil) diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index da266c9..a9e0f6c 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -460,6 +460,42 @@ func (q *Queries) CreateDeviceShelfMapping(ctx context.Context, arg CreateDevice return i, err } +const CreateDictionaryEntry = `-- name: CreateDictionaryEntry :one +INSERT INTO dictionary_cache (word, definition, part_of_speech, example, etymology, accessed_at) +VALUES ($1, $2, $3, $4, $5, NOW()) +RETURNING id, word, definition, part_of_speech, example, etymology, created_at, accessed_at +` + +type CreateDictionaryEntryParams struct { + 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"` +} + +func (q *Queries) CreateDictionaryEntry(ctx context.Context, arg CreateDictionaryEntryParams) (DictionaryCache, error) { + row := q.db.QueryRow(ctx, CreateDictionaryEntry, + arg.Word, + arg.Definition, + arg.PartOfSpeech, + arg.Example, + arg.Etymology, + ) + var i DictionaryCache + err := row.Scan( + &i.ID, + &i.Word, + &i.Definition, + &i.PartOfSpeech, + &i.Example, + &i.Etymology, + &i.CreatedAt, + &i.AccessedAt, + ) + return i, err +} + const CreateLibrary = `-- name: CreateLibrary :one INSERT INTO libraries (name, description, library_type_id, created_by_admin_id) VALUES ($1, $2, $3, $4) @@ -494,6 +530,50 @@ func (q *Queries) CreateLibrary(ctx context.Context, arg CreateLibraryParams) (L return i, err } +const CreateMediaBookmark = `-- name: CreateMediaBookmark :one +INSERT INTO media_bookmarks (media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +RETURNING id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at +` + +type CreateMediaBookmarkParams struct { + 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"` +} + +func (q *Queries) CreateMediaBookmark(ctx context.Context, arg CreateMediaBookmarkParams) (MediaBookmarks, error) { + row := q.db.QueryRow(ctx, CreateMediaBookmark, + arg.MediaItemID, + arg.UserID, + arg.PageNumber, + arg.ChapterNumber, + arg.CfiPosition, + arg.Title, + arg.Position, + arg.Notes, + ) + var i MediaBookmarks + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.PageNumber, + &i.ChapterNumber, + &i.CfiPosition, + &i.Title, + &i.Position, + &i.Notes, + &i.CreatedAt, + ) + return i, err +} + const CreateMediaHighlight = `-- name: CreateMediaHighlight :one INSERT INTO media_highlights (media_item_id, user_id, selection_text, start_position, end_position, color, note_id) VALUES ($1, $2, $3, $4, $5, $6, $7) @@ -551,7 +631,7 @@ func (q *Queries) CreateMediaHighlight(ctx context.Context, arg CreateMediaHighl const CreateMediaItem = `-- name: CreateMediaItem :one INSERT INTO media_items (library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, tags_search, asin, date_published, publisher, contributors, contributors_search, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $40, $41, $42) -RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence +RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence ` type CreateMediaItemParams struct { @@ -699,6 +779,7 @@ func (q *Queries) CreateMediaItem(ctx context.Context, arg CreateMediaItemParams &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -975,6 +1056,45 @@ func (q *Queries) CreateReadingHistory(ctx context.Context, arg CreateReadingHis return i, err } +const CreateReadingSpeed = `-- name: CreateReadingSpeed :one +INSERT INTO reading_speed (user_id, media_item_id, pages_per_minute, pages_read, total_reading_minutes, last_read_at) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING id, user_id, media_item_id, words_per_minute, pages_per_minute, pages_read, total_reading_minutes, last_read_at, updated_at +` + +type CreateReadingSpeedParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + 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"` +} + +func (q *Queries) CreateReadingSpeed(ctx context.Context, arg CreateReadingSpeedParams) (ReadingSpeed, error) { + row := q.db.QueryRow(ctx, CreateReadingSpeed, + arg.UserID, + arg.MediaItemID, + arg.PagesPerMinute, + arg.PagesRead, + arg.TotalReadingMinutes, + arg.LastReadAt, + ) + var i ReadingSpeed + err := row.Scan( + &i.ID, + &i.UserID, + &i.MediaItemID, + &i.WordsPerMinute, + &i.PagesPerMinute, + &i.PagesRead, + &i.TotalReadingMinutes, + &i.LastReadAt, + &i.UpdatedAt, + ) + return i, err +} + const CreateRefreshToken = `-- name: CreateRefreshToken :one INSERT INTO refresh_tokens (user_id, token, expires_at) VALUES ($1, $2, $3) @@ -1399,6 +1519,16 @@ func (q *Queries) DeleteLibraryFolder(ctx context.Context, arg DeleteLibraryFold return i, err } +const DeleteMediaBookmark = `-- name: DeleteMediaBookmark :exec +DELETE FROM media_bookmarks +WHERE id = $1 +` + +func (q *Queries) DeleteMediaBookmark(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteMediaBookmark, id) + return err +} + const DeleteMediaHighlight = `-- name: DeleteMediaHighlight :exec DELETE FROM media_highlights WHERE id = $1 ` @@ -1793,7 +1923,7 @@ func (q *Queries) GetCollectionItems(ctx context.Context, collectionID pgtype.UU } const GetCollectionItemsForDashboard = `-- name: GetCollectionItemsForDashboard :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, ci.excluded FROM media_items mi +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, ci.excluded FROM media_items mi INNER JOIN collection_items ci ON ci.media_item_id = mi.id WHERE ci.collection_id = $1 AND mi.library_id = $2 @@ -1860,6 +1990,7 @@ type GetCollectionItemsForDashboardRow struct { AlternateInfo []byte `db:"alternate_info" json:"alternate_info"` ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"` 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"` @@ -1931,6 +2062,7 @@ func (q *Queries) GetCollectionItemsForDashboard(ctx context.Context, arg GetCol &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -2081,7 +2213,7 @@ func (q *Queries) GetCollectionsForBook(ctx context.Context, mediaItemID pgtype. } const GetContinueReadingItems = `-- name: GetContinueReadingItems :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi INNER JOIN ( SELECT DISTINCT ON (media_item_id) media_item_id, last_read_at FROM reading_progress @@ -2164,6 +2296,7 @@ func (q *Queries) GetContinueReadingItems(ctx context.Context, arg GetContinueRe &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -2579,6 +2712,27 @@ func (q *Queries) GetDeviceShelfMappings(ctx context.Context, deviceID pgtype.UU return items, nil } +const GetDictionaryEntry = `-- name: GetDictionaryEntry :one +SELECT id, word, definition, part_of_speech, example, etymology, created_at, accessed_at FROM dictionary_cache +WHERE word = $1 +` + +func (q *Queries) GetDictionaryEntry(ctx context.Context, word string) (DictionaryCache, error) { + row := q.db.QueryRow(ctx, GetDictionaryEntry, word) + var i DictionaryCache + err := row.Scan( + &i.ID, + &i.Word, + &i.Definition, + &i.PartOfSpeech, + &i.Example, + &i.Etymology, + &i.CreatedAt, + &i.AccessedAt, + ) + return i, err +} + const GetFailedSyncQueueItems = `-- name: GetFailedSyncQueueItems :many SELECT id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at FROM sync_queue WHERE status = 'failed' AND attempts < max_attempts @@ -3098,7 +3252,7 @@ func (q *Queries) GetLibraryFolders(ctx context.Context, libraryID pgtype.UUID) } const GetLibraryItems = `-- name: GetLibraryItems :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi WHERE mi.library_id = $1 ORDER BY mi.created_at DESC ` @@ -3165,6 +3319,7 @@ func (q *Queries) GetLibraryItems(ctx context.Context, libraryID pgtype.UUID) ([ &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -3270,6 +3425,48 @@ func (q *Queries) GetLibraryVisibility(ctx context.Context, arg GetLibraryVisibi return i, err } +const GetMediaBookmarks = `-- name: GetMediaBookmarks :many +SELECT id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at FROM media_bookmarks +WHERE media_item_id = $1 AND user_id = $2 +ORDER BY created_at DESC +` + +type GetMediaBookmarksParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +func (q *Queries) GetMediaBookmarks(ctx context.Context, arg GetMediaBookmarksParams) ([]MediaBookmarks, error) { + rows, err := q.db.Query(ctx, GetMediaBookmarks, arg.MediaItemID, arg.UserID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []MediaBookmarks{} + for rows.Next() { + var i MediaBookmarks + if err := rows.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.PageNumber, + &i.ChapterNumber, + &i.CfiPosition, + &i.Title, + &i.Position, + &i.Notes, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetMediaHighlight = `-- name: GetMediaHighlight :one SELECT id, media_item_id, user_id, selection_text, start_position, end_position, color, note_id, created_at, updated_at, percentage_start, percentage_end, character_start, character_end, epubcfi_start, epubcfi_end, chapter_reference, paragraph_start, paragraph_end, panel_number, device_sync_data FROM media_highlights WHERE id = $1 ` @@ -3355,7 +3552,7 @@ func (q *Queries) GetMediaHighlights(ctx context.Context, arg GetMediaHighlights } const GetMediaItem = `-- name: GetMediaItem :one -SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE id = $1 +SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE id = $1 ` func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, error) { @@ -3414,6 +3611,7 @@ func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -3425,7 +3623,7 @@ func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, } const GetMediaItemByFilePath = `-- name: GetMediaItemByFilePath :one -SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 AND library_id = $2 +SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 AND library_id = $2 ` type GetMediaItemByFilePathParams struct { @@ -3489,6 +3687,7 @@ func (q *Queries) GetMediaItemByFilePath(ctx context.Context, arg GetMediaItemBy &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -3500,7 +3699,7 @@ func (q *Queries) GetMediaItemByFilePath(ctx context.Context, arg GetMediaItemBy } const GetMediaItemByFilePathAnyLibrary = `-- name: GetMediaItemByFilePathAnyLibrary :one -SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 LIMIT 1 +SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 LIMIT 1 ` func (q *Queries) GetMediaItemByFilePathAnyLibrary(ctx context.Context, filePath string) (MediaItems, error) { @@ -3559,6 +3758,7 @@ func (q *Queries) GetMediaItemByFilePathAnyLibrary(ctx context.Context, filePath &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -3571,7 +3771,7 @@ func (q *Queries) GetMediaItemByFilePathAnyLibrary(ctx context.Context, filePath const GetMediaItemByFilePathForSync = `-- name: GetMediaItemByFilePathForSync :one -SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 +SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 ` // ============================================ @@ -3633,6 +3833,7 @@ func (q *Queries) GetMediaItemByFilePathForSync(ctx context.Context, filePath st &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -3644,7 +3845,7 @@ func (q *Queries) GetMediaItemByFilePathForSync(ctx context.Context, filePath st } const GetMediaItemByKoboContentId = `-- name: GetMediaItemByKoboContentId :one -SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE kobo_content_id = $1 +SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE kobo_content_id = $1 ` func (q *Queries) GetMediaItemByKoboContentId(ctx context.Context, koboContentID pgtype.Text) (MediaItems, error) { @@ -3703,6 +3904,7 @@ func (q *Queries) GetMediaItemByKoboContentId(ctx context.Context, koboContentID &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -3714,7 +3916,7 @@ func (q *Queries) GetMediaItemByKoboContentId(ctx context.Context, koboContentID } const GetMediaItemByOPFIdentifier = `-- name: GetMediaItemByOPFIdentifier :one -SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE opf_identifier = $1 +SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE opf_identifier = $1 ` // Get media item by OPF identifier @@ -3774,6 +3976,7 @@ func (q *Queries) GetMediaItemByOPFIdentifier(ctx context.Context, opfIdentifier &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -3785,7 +3988,7 @@ func (q *Queries) GetMediaItemByOPFIdentifier(ctx context.Context, opfIdentifier } const GetMediaItemByOPFUUID = `-- name: GetMediaItemByOPFUUID :one -SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE opf_uuid = $1 +SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE opf_uuid = $1 ` // Get media item by OPF UUID @@ -3845,6 +4048,7 @@ func (q *Queries) GetMediaItemByOPFUUID(ctx context.Context, opfUuid pgtype.Text &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -3856,7 +4060,7 @@ func (q *Queries) GetMediaItemByOPFUUID(ctx context.Context, opfUuid pgtype.Text } const GetMediaItemBySHA256 = `-- name: GetMediaItemBySHA256 :one -SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 = $1 +SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 = $1 ` // Get media item by SHA-256 hash @@ -3916,6 +4120,7 @@ func (q *Queries) GetMediaItemBySHA256(ctx context.Context, fileSha256 pgtype.Te &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -4169,7 +4374,7 @@ func (q *Queries) GetNextRetryTime(ctx context.Context) (interface{}, error) { } const GetNotStartedItems = `-- name: GetNotStartedItems :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi WHERE mi.library_id = $1 AND NOT EXISTS ( SELECT 1 FROM reading_progress rp @@ -4249,6 +4454,7 @@ func (q *Queries) GetNotStartedItems(ctx context.Context, arg GetNotStartedItems &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -4333,6 +4539,31 @@ func (q *Queries) GetOpdsTokensByDevice(ctx context.Context, deviceID pgtype.UUI return items, nil } +const GetPanelData = `-- name: GetPanelData :one +SELECT id, media_item_id, page_number, detection_method, panels, created_at, updated_at FROM panel_data +WHERE media_item_id = $1 AND page_number = $2 +` + +type GetPanelDataParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + PageNumber int32 `db:"page_number" json:"page_number"` +} + +func (q *Queries) GetPanelData(ctx context.Context, arg GetPanelDataParams) (PanelData, error) { + row := q.db.QueryRow(ctx, GetPanelData, arg.MediaItemID, arg.PageNumber) + var i PanelData + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.PageNumber, + &i.DetectionMethod, + &i.Panels, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const GetPopularBooks = `-- name: GetPopularBooks :many SELECT mi.id, @@ -4391,6 +4622,18 @@ func (q *Queries) GetPopularBooks(ctx context.Context, arg GetPopularBooksParams return items, nil } +const GetReaderSettings = `-- name: GetReaderSettings :one +SELECT setting_value FROM reader_settings +WHERE user_id = $1 AND setting_key = 'reader_settings' +` + +func (q *Queries) GetReaderSettings(ctx context.Context, userID pgtype.UUID) ([]byte, error) { + row := q.db.QueryRow(ctx, GetReaderSettings, userID) + var setting_value []byte + err := row.Scan(&setting_value) + return setting_value, err +} + const GetReadingHistory = `-- name: GetReadingHistory :many SELECT id, user_id, media_item_id, device_id, progress_percentage, reading_session_start, reading_session_end, pages_read, time_spent_seconds, device_metadata, created_at FROM reading_history @@ -4478,8 +4721,35 @@ func (q *Queries) GetReadingProgress(ctx context.Context, arg GetReadingProgress return i, err } +const GetReadingSpeed = `-- name: GetReadingSpeed :one +SELECT id, user_id, media_item_id, words_per_minute, pages_per_minute, pages_read, total_reading_minutes, last_read_at, updated_at FROM reading_speed +WHERE user_id = $1 AND media_item_id = $2 +` + +type GetReadingSpeedParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` +} + +func (q *Queries) GetReadingSpeed(ctx context.Context, arg GetReadingSpeedParams) (ReadingSpeed, error) { + row := q.db.QueryRow(ctx, GetReadingSpeed, arg.UserID, arg.MediaItemID) + var i ReadingSpeed + err := row.Scan( + &i.ID, + &i.UserID, + &i.MediaItemID, + &i.WordsPerMinute, + &i.PagesPerMinute, + &i.PagesRead, + &i.TotalReadingMinutes, + &i.LastReadAt, + &i.UpdatedAt, + ) + return i, err +} + const GetRecentlyAddedItems = `-- name: GetRecentlyAddedItems :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi WHERE mi.library_id = $1 ORDER BY mi.created_at DESC LIMIT $2 @@ -4552,6 +4822,7 @@ func (q *Queries) GetRecentlyAddedItems(ctx context.Context, arg GetRecentlyAdde &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -4570,7 +4841,7 @@ func (q *Queries) GetRecentlyAddedItems(ctx context.Context, arg GetRecentlyAdde } const GetRecentlyReadItems = `-- name: GetRecentlyReadItems :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi INNER JOIN ( SELECT DISTINCT ON (media_item_id) media_item_id, last_read_at FROM reading_progress @@ -4651,6 +4922,7 @@ func (q *Queries) GetRecentlyReadItems(ctx context.Context, arg GetRecentlyReadI &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -6099,7 +6371,7 @@ func (q *Queries) ListLibraries(ctx context.Context) ([]ListLibrariesRow, error) } const ListMediaItems = `-- name: ListMediaItems :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name FROM media_items mi JOIN libraries l ON mi.library_id = l.id JOIN library_types lt ON l.library_type_id = lt.id @@ -6164,6 +6436,7 @@ type ListMediaItemsRow struct { AlternateInfo []byte `db:"alternate_info" json:"alternate_info"` ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"` 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"` @@ -6236,6 +6509,7 @@ func (q *Queries) ListMediaItems(ctx context.Context, arg ListMediaItemsParams) &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -6256,7 +6530,7 @@ func (q *Queries) ListMediaItems(ctx context.Context, arg ListMediaItemsParams) } const ListMediaItemsByLibrary = `-- name: ListMediaItemsByLibrary :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name FROM media_items mi JOIN libraries l ON mi.library_id = l.id JOIN library_types lt ON l.library_type_id = lt.id @@ -6317,6 +6591,7 @@ type ListMediaItemsByLibraryRow struct { AlternateInfo []byte `db:"alternate_info" json:"alternate_info"` ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"` 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"` @@ -6389,6 +6664,7 @@ func (q *Queries) ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype. &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -6409,7 +6685,7 @@ func (q *Queries) ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype. } const ListMediaItemsSorted = `-- name: ListMediaItemsSorted :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name FROM media_items mi JOIN libraries l ON mi.library_id = l.id JOIN library_types lt ON l.library_type_id = lt.id @@ -6543,6 +6819,7 @@ type ListMediaItemsSortedRow struct { AlternateInfo []byte `db:"alternate_info" json:"alternate_info"` ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"` 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"` @@ -6620,6 +6897,7 @@ func (q *Queries) ListMediaItemsSorted(ctx context.Context, arg ListMediaItemsSo &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -7370,7 +7648,7 @@ func (q *Queries) SearchLanguageValues(ctx context.Context, arg SearchLanguageVa } const SearchMediaItems = `-- name: SearchMediaItems :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.manga_type, mi.reading_direction, mi.series_count, mi.volume, mi.imprint, mi.age_rating, mi.web_url, mi.story_arc, mi.is_black_and_white, mi.metadata_notes, mi.community_rating, mi.alternate_info, mi.scan_information, mi.summary, mi.chapter_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, l.name as library_name, lt.name as library_type_name FROM media_items mi JOIN libraries l ON mi.library_id = l.id JOIN library_types lt ON l.library_type_id = lt.id @@ -7457,6 +7735,7 @@ type SearchMediaItemsRow struct { AlternateInfo []byte `db:"alternate_info" json:"alternate_info"` ScanInformation pgtype.Text `db:"scan_information" json:"scan_information"` 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"` @@ -7536,6 +7815,7 @@ func (q *Queries) SearchMediaItems(ctx context.Context, arg SearchMediaItemsPara &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -8421,6 +8701,29 @@ func (q *Queries) UpdateDeviceSyncTimestamp(ctx context.Context, id pgtype.UUID) return i, err } +const UpdateDictionaryAccessed = `-- name: UpdateDictionaryAccessed :one +UPDATE dictionary_cache +SET accessed_at = NOW() +WHERE word = $1 +RETURNING id, word, definition, part_of_speech, example, etymology, created_at, accessed_at +` + +func (q *Queries) UpdateDictionaryAccessed(ctx context.Context, word string) (DictionaryCache, error) { + row := q.db.QueryRow(ctx, UpdateDictionaryAccessed, word) + var i DictionaryCache + err := row.Scan( + &i.ID, + &i.Word, + &i.Definition, + &i.PartOfSpeech, + &i.Example, + &i.Etymology, + &i.CreatedAt, + &i.AccessedAt, + ) + return i, err +} + const UpdateEmail = `-- name: UpdateEmail :exec UPDATE users SET email = $2, updated_at = NOW() WHERE id = $1 ` @@ -8550,6 +8853,49 @@ func (q *Queries) UpdateLibrary(ctx context.Context, arg UpdateLibraryParams) (L return i, err } +const UpdateMediaBookmark = `-- name: UpdateMediaBookmark :one +UPDATE media_bookmarks +SET + title = $2, + notes = $3, + position = $4, + updated_at = NOW() +WHERE id = $1 AND user_id = $5 +RETURNING id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at +` + +type UpdateMediaBookmarkParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Title string `db:"title" json:"title"` + Notes pgtype.Text `db:"notes" json:"notes"` + Position pgtype.Text `db:"position" json:"position"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +func (q *Queries) UpdateMediaBookmark(ctx context.Context, arg UpdateMediaBookmarkParams) (MediaBookmarks, error) { + row := q.db.QueryRow(ctx, UpdateMediaBookmark, + arg.ID, + arg.Title, + arg.Notes, + arg.Position, + arg.UserID, + ) + var i MediaBookmarks + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.PageNumber, + &i.ChapterNumber, + &i.CfiPosition, + &i.Title, + &i.Position, + &i.Notes, + &i.CreatedAt, + ) + return i, err +} + const UpdateMediaHighlight = `-- name: UpdateMediaHighlight :one UPDATE media_highlights SET selection_text = $2, @@ -8633,7 +8979,7 @@ UPDATE media_items SET google_books_id = $23, updated_at = NOW() WHERE id = $1 -RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence +RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence ` type UpdateMediaItemParams struct { @@ -8742,6 +9088,7 @@ func (q *Queries) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -8847,7 +9194,7 @@ SET hash_confidence = $5, updated_at = NOW() WHERE id = $1 -RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence +RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence ` type UpdateMediaItemIdentifiersParams struct { @@ -8925,6 +9272,7 @@ func (q *Queries) UpdateMediaItemIdentifiers(ctx context.Context, arg UpdateMedi &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -8943,7 +9291,7 @@ SET entitlement_id = $2, kobo_metadata = $5, updated_at = NOW() WHERE id = $1 -RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence +RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata, manga_type, reading_direction, series_count, volume, imprint, age_rating, web_url, story_arc, is_black_and_white, metadata_notes, community_rating, alternate_info, scan_information, summary, chapter_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence ` type UpdateMediaItemKoboMetadataParams struct { @@ -9016,6 +9364,7 @@ func (q *Queries) UpdateMediaItemKoboMetadata(ctx context.Context, arg UpdateMed &i.AlternateInfo, &i.ScanInformation, &i.Summary, + &i.ChapterMetadata, &i.TagsSearch, &i.ContributorsSearch, &i.FileSha256, @@ -9159,6 +9508,51 @@ func (q *Queries) UpdateReadingProgress(ctx context.Context, arg UpdateReadingPr return i, err } +const UpdateReadingSpeed = `-- name: UpdateReadingSpeed :one +UPDATE reading_speed +SET + pages_per_minute = $3, + pages_read = pages_read + $4, + total_reading_minutes = total_reading_minutes + $5, + last_read_at = $6, + updated_at = NOW() +WHERE user_id = $1 AND media_item_id = $2 +RETURNING id, user_id, media_item_id, words_per_minute, pages_per_minute, pages_read, total_reading_minutes, last_read_at, updated_at +` + +type UpdateReadingSpeedParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + 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"` +} + +func (q *Queries) UpdateReadingSpeed(ctx context.Context, arg UpdateReadingSpeedParams) (ReadingSpeed, error) { + row := q.db.QueryRow(ctx, UpdateReadingSpeed, + arg.UserID, + arg.MediaItemID, + arg.PagesPerMinute, + arg.PagesRead, + arg.TotalReadingMinutes, + arg.LastReadAt, + ) + var i ReadingSpeed + err := row.Scan( + &i.ID, + &i.UserID, + &i.MediaItemID, + &i.WordsPerMinute, + &i.PagesPerMinute, + &i.PagesRead, + &i.TotalReadingMinutes, + &i.LastReadAt, + &i.UpdatedAt, + ) + return i, err +} + const UpdateSavedFilter = `-- name: UpdateSavedFilter :one UPDATE saved_filters SET name = $1, @@ -9509,3 +9903,69 @@ func (q *Queries) UpsertDashboardPreferences(ctx context.Context, arg UpsertDash ) return i, err } + +const UpsertPanelData = `-- name: UpsertPanelData :one +INSERT INTO panel_data (media_item_id, page_number, detection_method, panels) +VALUES ($1, $2, $3, $4) +ON CONFLICT (media_item_id, page_number) +DO UPDATE SET + detection_method = EXCLUDED.detection_method, + panels = EXCLUDED.panels, + updated_at = NOW() +RETURNING id, media_item_id, page_number, detection_method, panels, created_at, updated_at +` + +type UpsertPanelDataParams struct { + 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"` +} + +func (q *Queries) UpsertPanelData(ctx context.Context, arg UpsertPanelDataParams) (PanelData, error) { + row := q.db.QueryRow(ctx, UpsertPanelData, + arg.MediaItemID, + arg.PageNumber, + arg.DetectionMethod, + arg.Panels, + ) + var i PanelData + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.PageNumber, + &i.DetectionMethod, + &i.Panels, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpsertReaderSettings = `-- name: UpsertReaderSettings :one +INSERT INTO reader_settings (user_id, setting_key, setting_value) +VALUES ($1, 'reader_settings', $2) +ON CONFLICT (user_id, setting_key) +DO UPDATE SET + setting_value = EXCLUDED.setting_value, + updated_at = NOW() +RETURNING id, user_id, setting_key, setting_value, updated_at +` + +type UpsertReaderSettingsParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + SettingValue []byte `db:"setting_value" json:"setting_value"` +} + +func (q *Queries) UpsertReaderSettings(ctx context.Context, arg UpsertReaderSettingsParams) (ReaderSettings, error) { + row := q.db.QueryRow(ctx, UpsertReaderSettings, arg.UserID, arg.SettingValue) + var i ReaderSettings + err := row.Scan( + &i.ID, + &i.UserID, + &i.SettingKey, + &i.SettingValue, + &i.UpdatedAt, + ) + return i, err +} diff --git a/internal/database/queries/queries.sql b/internal/database/queries/queries.sql index 8e8c773..3907748 100644 --- a/internal/database/queries/queries.sql +++ b/internal/database/queries/queries.sql @@ -1924,3 +1924,89 @@ RETURNING *; DELETE FROM saved_filters WHERE id = @id AND user_id = @user_id RETURNING *; + +-- name: GetPanelData :one +SELECT * FROM panel_data +WHERE media_item_id = $1 AND page_number = $2; + +-- name: UpsertPanelData :one +INSERT INTO panel_data (media_item_id, page_number, detection_method, panels) +VALUES ($1, $2, $3, $4) +ON CONFLICT (media_item_id, page_number) +DO UPDATE SET + detection_method = EXCLUDED.detection_method, + panels = EXCLUDED.panels, + updated_at = NOW() +RETURNING *; + +-- name: GetReadingSpeed :one +SELECT * FROM reading_speed +WHERE user_id = $1 AND media_item_id = $2; + +-- name: CreateReadingSpeed :one +INSERT INTO reading_speed (user_id, media_item_id, pages_per_minute, pages_read, total_reading_minutes, last_read_at) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING *; + +-- name: UpdateReadingSpeed :one +UPDATE reading_speed +SET + pages_per_minute = $3, + pages_read = pages_read + $4, + total_reading_minutes = total_reading_minutes + $5, + last_read_at = $6, + updated_at = NOW() +WHERE user_id = $1 AND media_item_id = $2 +RETURNING *; + +-- name: GetDictionaryEntry :one +SELECT * FROM dictionary_cache +WHERE word = $1; + +-- name: CreateDictionaryEntry :one +INSERT INTO dictionary_cache (word, definition, part_of_speech, example, etymology, accessed_at) +VALUES ($1, $2, $3, $4, $5, NOW()) +RETURNING *; + +-- name: UpdateDictionaryAccessed :one +UPDATE dictionary_cache +SET accessed_at = NOW() +WHERE word = $1 +RETURNING *; + +-- name: GetReaderSettings :one +SELECT setting_value FROM reader_settings +WHERE user_id = $1 AND setting_key = 'reader_settings'; + +-- name: UpsertReaderSettings :one +INSERT INTO reader_settings (user_id, setting_key, setting_value) +VALUES ($1, 'reader_settings', $2) +ON CONFLICT (user_id, setting_key) +DO UPDATE SET + setting_value = EXCLUDED.setting_value, + updated_at = NOW() +RETURNING *; + +-- name: GetMediaBookmarks :many +SELECT * FROM media_bookmarks +WHERE media_item_id = $1 AND user_id = $2 +ORDER BY created_at DESC; + +-- name: CreateMediaBookmark :one +INSERT INTO media_bookmarks (media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +RETURNING *; + +-- name: DeleteMediaBookmark :exec +DELETE FROM media_bookmarks +WHERE id = $1; + +-- name: UpdateMediaBookmark :one +UPDATE media_bookmarks +SET + title = $2, + notes = $3, + position = $4, + updated_at = NOW() +WHERE id = $1 AND user_id = $5 +RETURNING *; diff --git a/package.json b/package.json index d42b030..c311cac 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "chart.js": "^4.5.1", "highlight.js": "^11.11.1", "htmx.org": "^2.0.8", - "lunr": "^2.3.9" + "jszip": "3.10.1", + "lunr": "^2.3.9", + "pdfjs-dist": "^5.6.205" }, "devDependencies": { "@tailwindcss/forms": "^0.5.11",