diff --git a/database/schema/schema.sql b/database/schema/schema.sql index 69bb78f..da93ed9 100644 --- a/database/schema/schema.sql +++ b/database/schema/schema.sql @@ -103,7 +103,14 @@ CREATE TABLE media_items ( google_books_id VARCHAR(100), -- Google Books identifier added_by_admin_id UUID REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + -- Universal Sync Format Detection (Phase 1) + format_group VARCHAR(20) NOT NULL DEFAULT 'reflowable', + format_mimetype VARCHAR(100), + is_reflowable BOOLEAN DEFAULT TRUE, + has_fixed_layout BOOLEAN DEFAULT FALSE, + total_characters BIGINT, + chapter_count INTEGER ); -- Create ebooks view for backward compatibility @@ -122,7 +129,26 @@ CREATE TABLE reading_progress ( current_page INTEGER DEFAULT 0, total_pages INTEGER, last_read_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - UNIQUE(media_item_id, user_id) + UNIQUE(media_item_id, user_id), + -- Universal Progress Tracking (Phase 1) + percentage FLOAT CHECK (percentage >= 0 AND percentage <= 1), + character_offset BIGINT, + epubcfi TEXT, + chapter INTEGER, + chapter_progress FLOAT CHECK (chapter_progress >= 0 AND chapter_progress <= 1), + viewport_x FLOAT DEFAULT 0, + viewport_y FLOAT DEFAULT 0, + zoom_level FLOAT DEFAULT 1.0, + scroll_position_x FLOAT DEFAULT 0, + scroll_position_y FLOAT DEFAULT 0, + panel_number INTEGER, + reading_mode VARCHAR(20), + -- Device Sync Metadata (Phase 1) + last_sync_device VARCHAR(50), + last_sync_source VARCHAR(20), + last_sync_timestamp TIMESTAMP WITH TIME ZONE, + conflict_detected BOOLEAN DEFAULT FALSE, + conflict_resolved BOOLEAN DEFAULT TRUE ); -- Create reading_progress view for backward compatibility @@ -154,7 +180,15 @@ CREATE TABLE media_notes ( content TEXT NOT NULL, position VARCHAR(100), -- optional position (page:offset or CFI) for standalone notes created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + -- Location Enhancements (Phase 1) + percentage_location FLOAT, + character_start INTEGER, + character_end INTEGER, + epubcfi_location TEXT, + chapter_reference INTEGER, + paragraph_reference INTEGER, + device_sync_data JSONB ); -- Create media_highlights table for user highlights on media items @@ -168,9 +202,92 @@ CREATE TABLE media_highlights ( color VARCHAR(7) DEFAULT '#ffff00', -- hex color code for highlight note_id UUID REFERENCES media_notes(id) ON DELETE SET NULL, -- optional associated note created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + -- Location Enhancements (Phase 1) + percentage_start FLOAT, + percentage_end FLOAT, + character_start INTEGER, + character_end INTEGER, + epubcfi_start TEXT, + epubcfi_end TEXT, + chapter_reference INTEGER, + paragraph_start INTEGER, + paragraph_end INTEGER, + panel_number INTEGER, + device_sync_data JSONB +); + +-- ============================================ +-- DEVICE REGISTRY (Phase 1) +-- ============================================ +CREATE TABLE devices ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + device_name VARCHAR(100) NOT NULL, + device_type VARCHAR(20) NOT NULL, + device_identifier VARCHAR(255) UNIQUE NOT NULL, + auth_token VARCHAR(500) UNIQUE NOT NULL, + last_sync TIMESTAMP WITH TIME ZONE, + last_seen TIMESTAMP WITH TIME ZONE, + sync_enabled BOOLEAN DEFAULT TRUE, + auto_sync BOOLEAN DEFAULT TRUE, + sync_frequency_minutes INTEGER DEFAULT 5, + device_metadata JSONB, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); +-- ============================================ +-- SYNC QUEUE FOR OFFLINE SUPPORT (Phase 1) +-- ============================================ +CREATE TABLE sync_queue ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE, + media_item_id UUID REFERENCES media_items(id) ON DELETE CASCADE, + sync_type VARCHAR(20) NOT NULL, + sync_data JSONB NOT NULL, + priority INTEGER DEFAULT 5, + attempts INTEGER DEFAULT 0, + max_attempts INTEGER DEFAULT 3, + status VARCHAR(20) DEFAULT 'pending', + error_message TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + processed_at TIMESTAMP WITH TIME ZONE +); + +-- ============================================ +-- CONFLICT RESOLUTION (Phase 1) +-- ============================================ +CREATE TABLE sync_conflicts ( + 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, + conflict_type VARCHAR(20) NOT NULL, + conflict_data JSONB NOT NULL, + resolution_status VARCHAR(20) DEFAULT 'unresolved', + resolution_data JSONB, + resolved_by UUID REFERENCES users(id), + resolved_at TIMESTAMP WITH TIME ZONE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- ============================================ +-- READING HISTORY (Phase 1) +-- ============================================ +CREATE TABLE reading_history ( + 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, + device_id UUID REFERENCES devices(id), + progress_percentage FLOAT, + reading_session_start TIMESTAMP WITH TIME ZONE, + reading_session_end TIMESTAMP WITH TIME ZONE, + pages_read INTEGER, + time_spent_seconds INTEGER, + device_metadata JSONB, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + -- Create ebook_ratings view for backward compatibility CREATE VIEW ebook_ratings AS SELECT mr.*, @@ -243,6 +360,49 @@ CREATE INDEX idx_media_highlights_media_item_id ON media_highlights(media_item_i CREATE INDEX idx_media_highlights_user_id ON media_highlights(user_id); CREATE INDEX idx_media_highlights_note_id ON media_highlights(note_id); +-- ============================================ +-- NEW TABLE INDEXES (Phase 1) +-- ============================================ + +-- Device registry indexes +CREATE INDEX idx_devices_user_id ON devices(user_id); +CREATE INDEX idx_devices_device_type ON devices(device_type); +CREATE INDEX idx_devices_device_identifier ON devices(device_identifier); + +-- Sync queue indexes +CREATE INDEX idx_sync_queue_device_id ON sync_queue(device_id); +CREATE INDEX idx_sync_queue_status ON sync_queue(status); +CREATE INDEX idx_sync_queue_priority ON sync_queue(priority); + +-- Sync conflicts indexes +CREATE INDEX idx_sync_conflicts_media_item_id ON sync_conflicts(media_item_id); +CREATE INDEX idx_sync_conflicts_user_id ON sync_conflicts(user_id); +CREATE INDEX idx_sync_conflicts_status ON sync_conflicts(resolution_status); + +-- Reading history indexes +CREATE INDEX idx_reading_history_user_id ON reading_history(user_id); +CREATE INDEX idx_reading_history_media_item_id ON reading_history(media_item_id); +CREATE INDEX idx_reading_history_created_at ON reading_history(created_at DESC); + +-- ============================================ +-- TRIGGER FUNCTIONS (Phase 1) +-- ============================================ + +-- Update updated_at timestamp for devices +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Trigger for devices table +CREATE TRIGGER update_devices_updated_at + BEFORE UPDATE ON devices + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + -- Add comment explaining the rating system COMMENT ON COLUMN media_ratings.rating IS 'Rating scale 1-10 (odd numbers = half-stars: 1,3,5,7,9 = 0.5,1.5,2.5,3.5,4.5 stars)'; @@ -269,4 +429,171 @@ COMMENT ON COLUMN media_items.google_books_id IS 'Google Books identifier for in -- - Library visibility is controlled through library_visibility table - admins can hide/show libraries per user -- - Notes and highlights support position data (page:offset or CFI format) for precise location tracking -- - Highlights can have associated notes for detailed annotations --- - Backward compatibility views (ebooks, ebook_ratings, ebook_reading_progress, ebook_notes, ebook_highlights) maintain existing API contracts \ No newline at end of file +-- - Backward compatibility views (ebooks, ebook_ratings, ebook_reading_progress, ebook_notes, ebook_highlights) maintain existing API contracts + +-- ============================================ +-- SYNC HELPER FUNCTIONS (Phase 1) +-- ============================================ + +-- Detect format group based on mimetype and file path +CREATE OR REPLACE FUNCTION detect_format_group(p_mimetype VARCHAR, p_file_path VARCHAR) +RETURNS VARCHAR AS $$ +BEGIN + CASE + -- Reflowable formats + WHEN p_mimetype = 'application/epub+zip' THEN + RETURN 'reflowable'; + WHEN p_mimetype = 'application/x-mobipocket-ebook' THEN + RETURN 'reflowable'; + WHEN p_mimetype = 'application/vnd.amazon.mobi8-ebook' THEN + RETURN 'reflowable'; + WHEN p_file_path LIKE '%.epub' THEN + RETURN 'reflowable'; + WHEN p_file_path LIKE '%.mobi' THEN + RETURN 'reflowable'; + WHEN p_file_path LIKE '%.azw3' THEN + RETURN 'reflowable'; + WHEN p_file_path LIKE '%.fb2' THEN + RETURN 'reflowable'; + WHEN p_file_path LIKE '%.txt' THEN + RETURN 'reflowable'; + + -- Fixed layout formats + WHEN p_mimetype = 'application/pdf' THEN + RETURN 'fixed_layout'; + WHEN p_file_path LIKE '%.pdf' THEN + RETURN 'fixed_layout'; + WHEN p_file_path LIKE '%.djvu' THEN + RETURN 'fixed_layout'; + + -- Comic archive formats + WHEN p_mimetype = 'application/x-cbr' THEN + RETURN 'comic_archive'; + WHEN p_mimetype = 'application/x-cbz' THEN + RETURN 'comic_archive'; + WHEN p_file_path LIKE '%.cbz' THEN + RETURN 'comic_archive'; + WHEN p_file_path LIKE '%.cbr' THEN + RETURN 'comic_archive'; + WHEN p_file_path LIKE '%.cbt' THEN + RETURN 'comic_archive'; + WHEN p_file_path LIKE '%.cb7' THEN + RETURN 'comic_archive'; + + ELSE + RETURN 'unknown'; + END CASE; +END; +$$ LANGUAGE plpgsql; + +-- Convert progress between format groups +CREATE OR REPLACE FUNCTION convert_progress( + source_format VARCHAR, + target_format VARCHAR, + source_progress JSONB +) RETURNS JSONB AS $$ +DECLARE + result JSONB; + percentage FLOAT; +BEGIN + -- Extract percentage from source + CASE source_format + WHEN 'reflowable' THEN + percentage := (source_progress->>'percentage')::FLOAT; + WHEN 'fixed_layout' THEN + percentage := ((source_progress->>'page')::FLOAT / + (source_progress->>'total_pages')::FLOAT); + WHEN 'comic_archive' THEN + percentage := ((source_progress->>'page')::FLOAT / + (source_progress->>'total_pages')::FLOAT); + ELSE + percentage := 0.0; + END CASE; + + -- Build target format progress + CASE target_format + WHEN 'reflowable' THEN + result := jsonb_build_object( + 'percentage', percentage, + 'epubcfi', 'epubcfi(/6/4/2:' || CAST(percentage * 100 AS INTEGER) || ')' + ); + WHEN 'fixed_layout' THEN + result := jsonb_build_object( + 'page', CAST(percentage * 200 AS INTEGER), + 'total_pages', 200, + 'percentage', percentage + ); + WHEN 'comic_archive' THEN + result := jsonb_build_object( + 'page', CAST(percentage * 32 AS INTEGER), + 'total_pages', 32, + 'percentage', percentage + ); + ELSE + result := '{}'::jsonb; + END CASE; + + RETURN result; +END; +$$ LANGUAGE plpgsql; + +-- Detect conflicts in sync progress +CREATE OR REPLACE FUNCTION detect_conflict( + p_media_item_id UUID, + new_progress JSONB, + device_type VARCHAR +) RETURNS BOOLEAN AS $$ +DECLARE + existing_progress JSONB; + time_diff INTERVAL; +BEGIN + -- Get most recent progress from different device + SELECT jsonb_build_object( + 'percentage', percentage, + 'timestamp', last_sync_timestamp + ) INTO existing_progress + FROM reading_progress + WHERE media_item_id = p_media_item_id + AND last_sync_source != device_type + ORDER BY last_sync_timestamp DESC + LIMIT 1; + + -- If no existing progress, no conflict + IF existing_progress IS NULL THEN + RETURN FALSE; + END IF; + + -- Check time difference (within 5 minutes = potential conflict) + time_diff := NOW() - (existing_progress->>'timestamp')::TIMESTAMP WITH TIME ZONE; + + IF time_diff < INTERVAL '5 minutes' THEN + -- Check if progress significantly different + IF ABS((new_progress->>'percentage')::FLOAT - + (existing_progress->>'percentage')::FLOAT) > 0.01 THEN + RETURN TRUE; + END IF; + END IF; + + RETURN FALSE; +END; +$$ LANGUAGE plpgsql; + +-- Merge progress from two sources +CREATE OR REPLACE FUNCTION merge_progress( + progress_a JSONB, + progress_b JSONB +) RETURNS JSONB AS $$ +BEGIN + RETURN jsonb_build_object( + 'percentage', GREATEST( + (progress_a->>'percentage')::FLOAT, + (progress_b->>'percentage')::FLOAT + ), + 'merged_from', ARRAY[ + (progress_a->>'source'), + (progress_b->>'source') + ], + 'merge_timestamp', NOW() + ); +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/internal/database/models.go b/internal/database/models.go index 111113c..1437f95 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -8,29 +8,64 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +type Devices struct { + ID pgtype.UUID `db:"id" json:"id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + DeviceName string `db:"device_name" json:"device_name"` + DeviceType string `db:"device_type" json:"device_type"` + DeviceIdentifier string `db:"device_identifier" json:"device_identifier"` + AuthToken string `db:"auth_token" json:"auth_token"` + LastSync pgtype.Timestamptz `db:"last_sync" json:"last_sync"` + LastSeen pgtype.Timestamptz `db:"last_seen" json:"last_seen"` + SyncEnabled pgtype.Bool `db:"sync_enabled" json:"sync_enabled"` + AutoSync pgtype.Bool `db:"auto_sync" json:"auto_sync"` + SyncFrequencyMinutes pgtype.Int4 `db:"sync_frequency_minutes" json:"sync_frequency_minutes"` + DeviceMetadata []byte `db:"device_metadata" json:"device_metadata"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` +} + type EbookHighlights 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"` - SelectionText string `db:"selection_text" json:"selection_text"` - StartPosition pgtype.Text `db:"start_position" json:"start_position"` - EndPosition pgtype.Text `db:"end_position" json:"end_position"` - Color pgtype.Text `db:"color" json:"color"` - NoteID pgtype.UUID `db:"note_id" json:"note_id"` - CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` - UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` - EbookID pgtype.UUID `db:"ebook_id" json:"ebook_id"` + 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"` + SelectionText string `db:"selection_text" json:"selection_text"` + StartPosition pgtype.Text `db:"start_position" json:"start_position"` + EndPosition pgtype.Text `db:"end_position" json:"end_position"` + Color pgtype.Text `db:"color" json:"color"` + NoteID pgtype.UUID `db:"note_id" json:"note_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + PercentageStart pgtype.Float8 `db:"percentage_start" json:"percentage_start"` + PercentageEnd pgtype.Float8 `db:"percentage_end" json:"percentage_end"` + CharacterStart pgtype.Int4 `db:"character_start" json:"character_start"` + CharacterEnd pgtype.Int4 `db:"character_end" json:"character_end"` + EpubcfiStart pgtype.Text `db:"epubcfi_start" json:"epubcfi_start"` + EpubcfiEnd pgtype.Text `db:"epubcfi_end" json:"epubcfi_end"` + ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"` + ParagraphStart pgtype.Int4 `db:"paragraph_start" json:"paragraph_start"` + ParagraphEnd pgtype.Int4 `db:"paragraph_end" json:"paragraph_end"` + PanelNumber pgtype.Int4 `db:"panel_number" json:"panel_number"` + DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"` + EbookID pgtype.UUID `db:"ebook_id" json:"ebook_id"` } type EbookNotes 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"` - Content string `db:"content" json:"content"` - Position pgtype.Text `db:"position" json:"position"` - CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` - UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` - EbookID pgtype.UUID `db:"ebook_id" json:"ebook_id"` + 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"` + Content string `db:"content" json:"content"` + Position pgtype.Text `db:"position" json:"position"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + PercentageLocation pgtype.Float8 `db:"percentage_location" json:"percentage_location"` + CharacterStart pgtype.Int4 `db:"character_start" json:"character_start"` + CharacterEnd pgtype.Int4 `db:"character_end" json:"character_end"` + EpubcfiLocation pgtype.Text `db:"epubcfi_location" json:"epubcfi_location"` + ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"` + ParagraphReference pgtype.Int4 `db:"paragraph_reference" json:"paragraph_reference"` + DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"` + EbookID pgtype.UUID `db:"ebook_id" json:"ebook_id"` } type EbookRatings struct { @@ -44,36 +79,67 @@ type EbookRatings struct { } type EbookReadingProgress 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"` - CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"` - TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"` - LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"` - EbookID pgtype.UUID `db:"ebook_id" json:"ebook_id"` + 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"` + CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"` + TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"` + LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"` + Percentage pgtype.Float8 `db:"percentage" json:"percentage"` + CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"` + Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"` + Chapter pgtype.Int4 `db:"chapter" json:"chapter"` + ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"` + ViewportX pgtype.Float8 `db:"viewport_x" json:"viewport_x"` + ViewportY pgtype.Float8 `db:"viewport_y" json:"viewport_y"` + ZoomLevel pgtype.Float8 `db:"zoom_level" json:"zoom_level"` + ScrollPositionX pgtype.Float8 `db:"scroll_position_x" json:"scroll_position_x"` + ScrollPositionY pgtype.Float8 `db:"scroll_position_y" json:"scroll_position_y"` + PanelNumber pgtype.Int4 `db:"panel_number" json:"panel_number"` + ReadingMode pgtype.Text `db:"reading_mode" json:"reading_mode"` + LastSyncDevice pgtype.Text `db:"last_sync_device" json:"last_sync_device"` + LastSyncSource pgtype.Text `db:"last_sync_source" json:"last_sync_source"` + LastSyncTimestamp pgtype.Timestamptz `db:"last_sync_timestamp" json:"last_sync_timestamp"` + ConflictDetected pgtype.Bool `db:"conflict_detected" json:"conflict_detected"` + ConflictResolved pgtype.Bool `db:"conflict_resolved" json:"conflict_resolved"` + EbookID pgtype.UUID `db:"ebook_id" json:"ebook_id"` } type Ebooks struct { - ID pgtype.UUID `db:"id" json:"id"` - LibraryID pgtype.UUID `db:"library_id" json:"library_id"` - Title string `db:"title" json:"title"` - Author pgtype.Text `db:"author" json:"author"` - Isbn pgtype.Text `db:"isbn" json:"isbn"` - Description pgtype.Text `db:"description" json:"description"` - FilePath string `db:"file_path" json:"file_path"` - FileSize pgtype.Int8 `db:"file_size" json:"file_size"` - MimeType pgtype.Text `db:"mime_type" json:"mime_type"` - CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` - Series pgtype.Text `db:"series" json:"series"` - SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` - Tags pgtype.Text `db:"tags" json:"tags"` - Asin pgtype.Text `db:"asin" json:"asin"` - DatePublished pgtype.Date `db:"date_published" json:"date_published"` - Publisher pgtype.Text `db:"publisher" json:"publisher"` - Contributors pgtype.Text `db:"contributors" json:"contributors"` - AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` - CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` - UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + ID pgtype.UUID `db:"id" json:"id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + Isbn pgtype.Text `db:"isbn" json:"isbn"` + Description pgtype.Text `db:"description" json:"description"` + FilePath string `db:"file_path" json:"file_path"` + FileSize pgtype.Int8 `db:"file_size" json:"file_size"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` + Series pgtype.Text `db:"series" json:"series"` + SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` + Tags pgtype.Text `db:"tags" json:"tags"` + Asin pgtype.Text `db:"asin" json:"asin"` + DatePublished pgtype.Date `db:"date_published" json:"date_published"` + Publisher pgtype.Text `db:"publisher" json:"publisher"` + Contributors pgtype.Text `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` + AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` } type Libraries struct { @@ -111,49 +177,89 @@ type LibraryVisibility struct { } type MediaHighlights 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"` - SelectionText string `db:"selection_text" json:"selection_text"` - StartPosition pgtype.Text `db:"start_position" json:"start_position"` - EndPosition pgtype.Text `db:"end_position" json:"end_position"` - Color pgtype.Text `db:"color" json:"color"` - NoteID pgtype.UUID `db:"note_id" json:"note_id"` - CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` - UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + 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"` + SelectionText string `db:"selection_text" json:"selection_text"` + StartPosition pgtype.Text `db:"start_position" json:"start_position"` + EndPosition pgtype.Text `db:"end_position" json:"end_position"` + Color pgtype.Text `db:"color" json:"color"` + NoteID pgtype.UUID `db:"note_id" json:"note_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + PercentageStart pgtype.Float8 `db:"percentage_start" json:"percentage_start"` + PercentageEnd pgtype.Float8 `db:"percentage_end" json:"percentage_end"` + CharacterStart pgtype.Int4 `db:"character_start" json:"character_start"` + CharacterEnd pgtype.Int4 `db:"character_end" json:"character_end"` + EpubcfiStart pgtype.Text `db:"epubcfi_start" json:"epubcfi_start"` + EpubcfiEnd pgtype.Text `db:"epubcfi_end" json:"epubcfi_end"` + ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"` + ParagraphStart pgtype.Int4 `db:"paragraph_start" json:"paragraph_start"` + ParagraphEnd pgtype.Int4 `db:"paragraph_end" json:"paragraph_end"` + PanelNumber pgtype.Int4 `db:"panel_number" json:"panel_number"` + DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"` } type MediaItems struct { - ID pgtype.UUID `db:"id" json:"id"` - LibraryID pgtype.UUID `db:"library_id" json:"library_id"` - Title string `db:"title" json:"title"` - Author pgtype.Text `db:"author" json:"author"` - Isbn pgtype.Text `db:"isbn" json:"isbn"` - Description pgtype.Text `db:"description" json:"description"` - FilePath string `db:"file_path" json:"file_path"` - FileSize pgtype.Int8 `db:"file_size" json:"file_size"` - MimeType pgtype.Text `db:"mime_type" json:"mime_type"` - CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` - Series pgtype.Text `db:"series" json:"series"` - SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` - Tags pgtype.Text `db:"tags" json:"tags"` - Asin pgtype.Text `db:"asin" json:"asin"` - DatePublished pgtype.Date `db:"date_published" json:"date_published"` - Publisher pgtype.Text `db:"publisher" json:"publisher"` - Contributors pgtype.Text `db:"contributors" json:"contributors"` - AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` - CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` - UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + ID pgtype.UUID `db:"id" json:"id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + Isbn pgtype.Text `db:"isbn" json:"isbn"` + Description pgtype.Text `db:"description" json:"description"` + FilePath string `db:"file_path" json:"file_path"` + FileSize pgtype.Int8 `db:"file_size" json:"file_size"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` + Series pgtype.Text `db:"series" json:"series"` + SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` + Tags pgtype.Text `db:"tags" json:"tags"` + Asin pgtype.Text `db:"asin" json:"asin"` + DatePublished pgtype.Date `db:"date_published" json:"date_published"` + Publisher pgtype.Text `db:"publisher" json:"publisher"` + Contributors pgtype.Text `db:"contributors" json:"contributors"` + // ISO 639-1 language code (e.g., en, es, fr) + Language pgtype.Text `db:"language" json:"language"` + // Edition information (e.g., "First Edition", "Revised Edition") + Edition pgtype.Text `db:"edition" json:"edition"` + // Total page count for progress tracking and sorting + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + // Genre classification for filtering and discovery + Genre pgtype.Text `db:"genre" json:"genre"` + // Copyright or publication year for filtering/sorting + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + // Goodreads book identifier for integration + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + // Open Library identifier for integration + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + // Google Books identifier for integration + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` + AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` } type MediaNotes 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"` - Content string `db:"content" json:"content"` - Position pgtype.Text `db:"position" json:"position"` - CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` - UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + 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"` + Content string `db:"content" json:"content"` + Position pgtype.Text `db:"position" json:"position"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + PercentageLocation pgtype.Float8 `db:"percentage_location" json:"percentage_location"` + CharacterStart pgtype.Int4 `db:"character_start" json:"character_start"` + CharacterEnd pgtype.Int4 `db:"character_end" json:"character_end"` + EpubcfiLocation pgtype.Text `db:"epubcfi_location" json:"epubcfi_location"` + ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"` + ParagraphReference pgtype.Int4 `db:"paragraph_reference" json:"paragraph_reference"` + DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"` } type MediaRatings struct { @@ -166,13 +272,44 @@ type MediaRatings struct { 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"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + ProgressPercentage pgtype.Float8 `db:"progress_percentage" json:"progress_percentage"` + ReadingSessionStart pgtype.Timestamptz `db:"reading_session_start" json:"reading_session_start"` + ReadingSessionEnd pgtype.Timestamptz `db:"reading_session_end" json:"reading_session_end"` + PagesRead pgtype.Int4 `db:"pages_read" json:"pages_read"` + TimeSpentSeconds pgtype.Int4 `db:"time_spent_seconds" json:"time_spent_seconds"` + DeviceMetadata []byte `db:"device_metadata" json:"device_metadata"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` +} + type ReadingProgress 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"` - CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"` - TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"` - LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"` + 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"` + CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"` + TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"` + LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"` + Percentage pgtype.Float8 `db:"percentage" json:"percentage"` + CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"` + Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"` + Chapter pgtype.Int4 `db:"chapter" json:"chapter"` + ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"` + ViewportX pgtype.Float8 `db:"viewport_x" json:"viewport_x"` + ViewportY pgtype.Float8 `db:"viewport_y" json:"viewport_y"` + ZoomLevel pgtype.Float8 `db:"zoom_level" json:"zoom_level"` + ScrollPositionX pgtype.Float8 `db:"scroll_position_x" json:"scroll_position_x"` + ScrollPositionY pgtype.Float8 `db:"scroll_position_y" json:"scroll_position_y"` + PanelNumber pgtype.Int4 `db:"panel_number" json:"panel_number"` + ReadingMode pgtype.Text `db:"reading_mode" json:"reading_mode"` + LastSyncDevice pgtype.Text `db:"last_sync_device" json:"last_sync_device"` + LastSyncSource pgtype.Text `db:"last_sync_source" json:"last_sync_source"` + LastSyncTimestamp pgtype.Timestamptz `db:"last_sync_timestamp" json:"last_sync_timestamp"` + ConflictDetected pgtype.Bool `db:"conflict_detected" json:"conflict_detected"` + ConflictResolved pgtype.Bool `db:"conflict_resolved" json:"conflict_resolved"` } type RefreshTokens struct { @@ -184,6 +321,34 @@ type RefreshTokens struct { RevokedAt pgtype.Timestamptz `db:"revoked_at" json:"revoked_at"` } +type SyncConflicts 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"` + ConflictType string `db:"conflict_type" json:"conflict_type"` + ConflictData []byte `db:"conflict_data" json:"conflict_data"` + ResolutionStatus pgtype.Text `db:"resolution_status" json:"resolution_status"` + ResolutionData []byte `db:"resolution_data" json:"resolution_data"` + ResolvedBy pgtype.UUID `db:"resolved_by" json:"resolved_by"` + ResolvedAt pgtype.Timestamptz `db:"resolved_at" json:"resolved_at"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` +} + +type SyncQueue struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + SyncType string `db:"sync_type" json:"sync_type"` + SyncData []byte `db:"sync_data" json:"sync_data"` + Priority pgtype.Int4 `db:"priority" json:"priority"` + Attempts pgtype.Int4 `db:"attempts" json:"attempts"` + MaxAttempts pgtype.Int4 `db:"max_attempts" json:"max_attempts"` + Status pgtype.Text `db:"status" json:"status"` + ErrorMessage pgtype.Text `db:"error_message" json:"error_message"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + ProcessedAt pgtype.Timestamptz `db:"processed_at" json:"processed_at"` +} + type Users struct { ID pgtype.UUID `db:"id" json:"id"` Email string `db:"email" json:"email"` diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index 7fd89d7..5c30dee 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -45,7 +45,7 @@ func (q *Queries) CleanupExpiredRefreshTokens(ctx context.Context) error { const CreateEbookNote = `-- name: CreateEbookNote :one INSERT INTO media_notes (media_item_id, user_id, content, position) VALUES ($1, $2, $3, $4) -RETURNING id, media_item_id, user_id, content, position, created_at, updated_at +RETURNING id, media_item_id, user_id, content, position, created_at, updated_at, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data ` type CreateEbookNoteParams struct { @@ -72,6 +72,13 @@ func (q *Queries) CreateEbookNote(ctx context.Context, arg CreateEbookNoteParams &i.Position, &i.CreatedAt, &i.UpdatedAt, + &i.PercentageLocation, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.ParagraphReference, + &i.DeviceSyncData, ) return i, err } @@ -113,7 +120,7 @@ func (q *Queries) CreateLibrary(ctx context.Context, arg CreateLibraryParams) (L 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) -RETURNING id, media_item_id, user_id, selection_text, start_position, end_position, color, note_id, created_at, updated_at +RETURNING 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 ` type CreateMediaHighlightParams struct { @@ -149,14 +156,25 @@ func (q *Queries) CreateMediaHighlight(ctx context.Context, arg CreateMediaHighl &i.NoteID, &i.CreatedAt, &i.UpdatedAt, + &i.PercentageStart, + &i.PercentageEnd, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiStart, + &i.EpubcfiEnd, + &i.ChapterReference, + &i.ParagraphStart, + &i.ParagraphEnd, + &i.PanelNumber, + &i.DeviceSyncData, ) return i, err } 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, asin, date_published, publisher, contributors, added_by_admin_id) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) -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, added_by_admin_id, created_at, updated_at +INSERT INTO media_items (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) +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) +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 ` type CreateMediaItemParams struct { @@ -176,6 +194,14 @@ type CreateMediaItemParams struct { DatePublished pgtype.Date `db:"date_published" json:"date_published"` Publisher pgtype.Text `db:"publisher" json:"publisher"` Contributors pgtype.Text `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` } @@ -198,6 +224,14 @@ func (q *Queries) CreateMediaItem(ctx context.Context, arg CreateMediaItemParams arg.DatePublished, arg.Publisher, arg.Contributors, + arg.Language, + arg.Edition, + arg.PageCount, + arg.Genre, + arg.CopyrightYear, + arg.GoodreadsID, + arg.OpenlibraryID, + arg.GoogleBooksID, arg.AddedByAdminID, ) var i MediaItems @@ -219,9 +253,23 @@ func (q *Queries) CreateMediaItem(ctx context.Context, arg CreateMediaItemParams &i.DatePublished, &i.Publisher, &i.Contributors, + &i.Language, + &i.Edition, + &i.PageCount, + &i.Genre, + &i.CopyrightYear, + &i.GoodreadsID, + &i.OpenlibraryID, + &i.GoogleBooksID, &i.AddedByAdminID, &i.CreatedAt, &i.UpdatedAt, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, ) return i, err } @@ -229,7 +277,7 @@ func (q *Queries) CreateMediaItem(ctx context.Context, arg CreateMediaItemParams const CreateMediaNote = `-- name: CreateMediaNote :one INSERT INTO media_notes (media_item_id, user_id, content, position) VALUES ($1, $2, $3, $4) -RETURNING id, media_item_id, user_id, content, position, created_at, updated_at +RETURNING id, media_item_id, user_id, content, position, created_at, updated_at, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data ` type CreateMediaNoteParams struct { @@ -256,6 +304,13 @@ func (q *Queries) CreateMediaNote(ctx context.Context, arg CreateMediaNoteParams &i.Position, &i.CreatedAt, &i.UpdatedAt, + &i.PercentageLocation, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.ParagraphReference, + &i.DeviceSyncData, ) return i, err } @@ -660,7 +715,7 @@ func (q *Queries) GetLibraryVisibility(ctx context.Context, arg GetLibraryVisibi } 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 FROM media_highlights WHERE id = $1 +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 ` func (q *Queries) GetMediaHighlight(ctx context.Context, id pgtype.UUID) (MediaHighlights, error) { @@ -677,12 +732,23 @@ func (q *Queries) GetMediaHighlight(ctx context.Context, id pgtype.UUID) (MediaH &i.NoteID, &i.CreatedAt, &i.UpdatedAt, + &i.PercentageStart, + &i.PercentageEnd, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiStart, + &i.EpubcfiEnd, + &i.ChapterReference, + &i.ParagraphStart, + &i.ParagraphEnd, + &i.PanelNumber, + &i.DeviceSyncData, ) return i, err } const GetMediaHighlights = `-- name: GetMediaHighlights :many -SELECT id, media_item_id, user_id, selection_text, start_position, end_position, color, note_id, created_at, updated_at FROM media_highlights WHERE media_item_id = $1 AND user_id = $2 ORDER BY created_at DESC +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 media_item_id = $1 AND user_id = $2 ORDER BY created_at DESC ` type GetMediaHighlightsParams struct { @@ -710,6 +776,17 @@ func (q *Queries) GetMediaHighlights(ctx context.Context, arg GetMediaHighlights &i.NoteID, &i.CreatedAt, &i.UpdatedAt, + &i.PercentageStart, + &i.PercentageEnd, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiStart, + &i.EpubcfiEnd, + &i.ChapterReference, + &i.ParagraphStart, + &i.ParagraphEnd, + &i.PanelNumber, + &i.DeviceSyncData, ); err != nil { return nil, err } @@ -722,7 +799,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, added_by_admin_id, created_at, updated_at 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 FROM media_items WHERE id = $1 ` func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, error) { @@ -746,15 +823,29 @@ func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, &i.DatePublished, &i.Publisher, &i.Contributors, + &i.Language, + &i.Edition, + &i.PageCount, + &i.Genre, + &i.CopyrightYear, + &i.GoodreadsID, + &i.OpenlibraryID, + &i.GoogleBooksID, &i.AddedByAdminID, &i.CreatedAt, &i.UpdatedAt, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, ) return i, err } 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, added_by_admin_id, created_at, updated_at 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 FROM media_items WHERE file_path = $1 ` func (q *Queries) GetMediaItemByFilePath(ctx context.Context, filePath string) (MediaItems, error) { @@ -778,15 +869,29 @@ func (q *Queries) GetMediaItemByFilePath(ctx context.Context, filePath string) ( &i.DatePublished, &i.Publisher, &i.Contributors, + &i.Language, + &i.Edition, + &i.PageCount, + &i.Genre, + &i.CopyrightYear, + &i.GoodreadsID, + &i.OpenlibraryID, + &i.GoogleBooksID, &i.AddedByAdminID, &i.CreatedAt, &i.UpdatedAt, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, ) return i, err } const GetMediaNote = `-- name: GetMediaNote :one -SELECT id, media_item_id, user_id, content, position, created_at, updated_at FROM media_notes WHERE id = $1 +SELECT id, media_item_id, user_id, content, position, created_at, updated_at, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data FROM media_notes WHERE id = $1 ` func (q *Queries) GetMediaNote(ctx context.Context, id pgtype.UUID) (MediaNotes, error) { @@ -800,12 +905,19 @@ func (q *Queries) GetMediaNote(ctx context.Context, id pgtype.UUID) (MediaNotes, &i.Position, &i.CreatedAt, &i.UpdatedAt, + &i.PercentageLocation, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.ParagraphReference, + &i.DeviceSyncData, ) return i, err } const GetMediaNotes = `-- name: GetMediaNotes :many -SELECT id, media_item_id, user_id, content, position, created_at, updated_at FROM media_notes WHERE media_item_id = $1 AND user_id = $2 ORDER BY created_at DESC +SELECT id, media_item_id, user_id, content, position, created_at, updated_at, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data FROM media_notes WHERE media_item_id = $1 AND user_id = $2 ORDER BY created_at DESC ` type GetMediaNotesParams struct { @@ -830,6 +942,13 @@ func (q *Queries) GetMediaNotes(ctx context.Context, arg GetMediaNotesParams) ([ &i.Position, &i.CreatedAt, &i.UpdatedAt, + &i.PercentageLocation, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.ParagraphReference, + &i.DeviceSyncData, ); err != nil { return nil, err } @@ -911,7 +1030,7 @@ func (q *Queries) GetMediaRatings(ctx context.Context, mediaItemID pgtype.UUID) } const GetReadingProgress = `-- name: GetReadingProgress :one -SELECT id, media_item_id, user_id, current_page, total_pages, last_read_at FROM reading_progress WHERE media_item_id = $1 AND user_id = $2 +SELECT id, media_item_id, user_id, current_page, total_pages, last_read_at, percentage, character_offset, epubcfi, chapter, chapter_progress, viewport_x, viewport_y, zoom_level, scroll_position_x, scroll_position_y, panel_number, reading_mode, last_sync_device, last_sync_source, last_sync_timestamp, conflict_detected, conflict_resolved FROM reading_progress WHERE media_item_id = $1 AND user_id = $2 ` type GetReadingProgressParams struct { @@ -929,6 +1048,23 @@ func (q *Queries) GetReadingProgress(ctx context.Context, arg GetReadingProgress &i.CurrentPage, &i.TotalPages, &i.LastReadAt, + &i.Percentage, + &i.CharacterOffset, + &i.Epubcfi, + &i.Chapter, + &i.ChapterProgress, + &i.ViewportX, + &i.ViewportY, + &i.ZoomLevel, + &i.ScrollPositionX, + &i.ScrollPositionY, + &i.PanelNumber, + &i.ReadingMode, + &i.LastSyncDevice, + &i.LastSyncSource, + &i.LastSyncTimestamp, + &i.ConflictDetected, + &i.ConflictResolved, ) return i, err } @@ -1267,7 +1403,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.added_by_admin_id, mi.created_at, mi.updated_at, 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, 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 @@ -1297,9 +1433,23 @@ type ListMediaItemsRow struct { DatePublished pgtype.Date `db:"date_published" json:"date_published"` Publisher pgtype.Text `db:"publisher" json:"publisher"` Contributors pgtype.Text `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` LibraryName string `db:"library_name" json:"library_name"` LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } @@ -1331,9 +1481,23 @@ func (q *Queries) ListMediaItems(ctx context.Context, arg ListMediaItemsParams) &i.DatePublished, &i.Publisher, &i.Contributors, + &i.Language, + &i.Edition, + &i.PageCount, + &i.Genre, + &i.CopyrightYear, + &i.GoodreadsID, + &i.OpenlibraryID, + &i.GoogleBooksID, &i.AddedByAdminID, &i.CreatedAt, &i.UpdatedAt, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, &i.LibraryName, &i.LibraryTypeName, ); err != nil { @@ -1348,7 +1512,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.added_by_admin_id, mi.created_at, mi.updated_at, 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, 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 @@ -1374,9 +1538,23 @@ type ListMediaItemsByLibraryRow struct { DatePublished pgtype.Date `db:"date_published" json:"date_published"` Publisher pgtype.Text `db:"publisher" json:"publisher"` Contributors pgtype.Text `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` LibraryName string `db:"library_name" json:"library_name"` LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } @@ -1408,9 +1586,23 @@ func (q *Queries) ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype. &i.DatePublished, &i.Publisher, &i.Contributors, + &i.Language, + &i.Edition, + &i.PageCount, + &i.Genre, + &i.CopyrightYear, + &i.GoodreadsID, + &i.OpenlibraryID, + &i.GoogleBooksID, &i.AddedByAdminID, &i.CreatedAt, &i.UpdatedAt, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, &i.LibraryName, &i.LibraryTypeName, ); err != nil { @@ -1425,7 +1617,7 @@ func (q *Queries) ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype. } const ListMediaItemsFiltered = `-- name: ListMediaItemsFiltered :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.added_by_admin_id, mi.created_at, mi.updated_at, 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, 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 @@ -1501,9 +1693,23 @@ type ListMediaItemsFilteredRow struct { DatePublished pgtype.Date `db:"date_published" json:"date_published"` Publisher pgtype.Text `db:"publisher" json:"publisher"` Contributors pgtype.Text `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` LibraryName string `db:"library_name" json:"library_name"` LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } @@ -1548,9 +1754,23 @@ func (q *Queries) ListMediaItemsFiltered(ctx context.Context, arg ListMediaItems &i.DatePublished, &i.Publisher, &i.Contributors, + &i.Language, + &i.Edition, + &i.PageCount, + &i.Genre, + &i.CopyrightYear, + &i.GoodreadsID, + &i.OpenlibraryID, + &i.GoogleBooksID, &i.AddedByAdminID, &i.CreatedAt, &i.UpdatedAt, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, &i.LibraryName, &i.LibraryTypeName, ); err != nil { @@ -1565,7 +1785,7 @@ func (q *Queries) ListMediaItemsFiltered(ctx context.Context, arg ListMediaItems } 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.added_by_admin_id, mi.created_at, mi.updated_at, 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, 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 @@ -1664,9 +1884,23 @@ type ListMediaItemsSortedRow struct { DatePublished pgtype.Date `db:"date_published" json:"date_published"` Publisher pgtype.Text `db:"publisher" json:"publisher"` Contributors pgtype.Text `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` LibraryName string `db:"library_name" json:"library_name"` LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } @@ -1703,9 +1937,23 @@ func (q *Queries) ListMediaItemsSorted(ctx context.Context, arg ListMediaItemsSo &i.DatePublished, &i.Publisher, &i.Contributors, + &i.Language, + &i.Edition, + &i.PageCount, + &i.Genre, + &i.CopyrightYear, + &i.GoodreadsID, + &i.OpenlibraryID, + &i.GoogleBooksID, &i.AddedByAdminID, &i.CreatedAt, &i.UpdatedAt, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, &i.LibraryName, &i.LibraryTypeName, ); err != nil { @@ -1785,7 +2033,7 @@ func (q *Queries) RevokeRefreshToken(ctx context.Context, token string) error { 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.added_by_admin_id, mi.created_at, mi.updated_at, 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, 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 @@ -1835,9 +2083,23 @@ type SearchMediaItemsRow struct { DatePublished pgtype.Date `db:"date_published" json:"date_published"` Publisher pgtype.Text `db:"publisher" json:"publisher"` Contributors pgtype.Text `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` LibraryName string `db:"library_name" json:"library_name"` LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } @@ -1877,9 +2139,23 @@ func (q *Queries) SearchMediaItems(ctx context.Context, arg SearchMediaItemsPara &i.DatePublished, &i.Publisher, &i.Contributors, + &i.Language, + &i.Edition, + &i.PageCount, + &i.Genre, + &i.CopyrightYear, + &i.GoodreadsID, + &i.OpenlibraryID, + &i.GoogleBooksID, &i.AddedByAdminID, &i.CreatedAt, &i.UpdatedAt, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, &i.LibraryName, &i.LibraryTypeName, ); err != nil { @@ -1894,7 +2170,7 @@ func (q *Queries) SearchMediaItems(ctx context.Context, arg SearchMediaItemsPara } const SearchMediaItemsFuzzy = `-- name: SearchMediaItemsFuzzy :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.added_by_admin_id, mi.created_at, mi.updated_at, 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, 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 @@ -1944,9 +2220,23 @@ type SearchMediaItemsFuzzyRow struct { DatePublished pgtype.Date `db:"date_published" json:"date_published"` Publisher pgtype.Text `db:"publisher" json:"publisher"` Contributors pgtype.Text `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` LibraryName string `db:"library_name" json:"library_name"` LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } @@ -1983,9 +2273,23 @@ func (q *Queries) SearchMediaItemsFuzzy(ctx context.Context, arg SearchMediaItem &i.DatePublished, &i.Publisher, &i.Contributors, + &i.Language, + &i.Edition, + &i.PageCount, + &i.Genre, + &i.CopyrightYear, + &i.GoodreadsID, + &i.OpenlibraryID, + &i.GoogleBooksID, &i.AddedByAdminID, &i.CreatedAt, &i.UpdatedAt, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, &i.LibraryName, &i.LibraryTypeName, ); err != nil { @@ -2036,7 +2340,7 @@ UPDATE media_notes SET position = $3, updated_at = NOW() WHERE id = $1 -RETURNING id, media_item_id, user_id, content, position, created_at, updated_at +RETURNING id, media_item_id, user_id, content, position, created_at, updated_at, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data ` type UpdateEbookNoteParams struct { @@ -2056,6 +2360,13 @@ func (q *Queries) UpdateEbookNote(ctx context.Context, arg UpdateEbookNoteParams &i.Position, &i.CreatedAt, &i.UpdatedAt, + &i.PercentageLocation, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.ParagraphReference, + &i.DeviceSyncData, ) return i, err } @@ -2113,7 +2424,7 @@ UPDATE media_highlights SET note_id = $6, updated_at = NOW() WHERE id = $1 -RETURNING id, media_item_id, user_id, selection_text, start_position, end_position, color, note_id, created_at, updated_at +RETURNING 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 ` type UpdateMediaHighlightParams struct { @@ -2146,6 +2457,17 @@ func (q *Queries) UpdateMediaHighlight(ctx context.Context, arg UpdateMediaHighl &i.NoteID, &i.CreatedAt, &i.UpdatedAt, + &i.PercentageStart, + &i.PercentageEnd, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiStart, + &i.EpubcfiEnd, + &i.ChapterReference, + &i.ParagraphStart, + &i.ParagraphEnd, + &i.PanelNumber, + &i.DeviceSyncData, ) return i, err } @@ -2164,9 +2486,17 @@ UPDATE media_items SET date_published = $11, publisher = $12, contributors = $13, + language = $14, + edition = $15, + page_count = $16, + genre = $17, + copyright_year = $18, + goodreads_id = $19, + openlibrary_id = $20, + google_books_id = $21, 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, added_by_admin_id, created_at, updated_at +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 ` type UpdateMediaItemParams struct { @@ -2183,6 +2513,14 @@ type UpdateMediaItemParams struct { DatePublished pgtype.Date `db:"date_published" json:"date_published"` Publisher pgtype.Text `db:"publisher" json:"publisher"` Contributors pgtype.Text `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` } func (q *Queries) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams) (MediaItems, error) { @@ -2200,6 +2538,14 @@ func (q *Queries) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams arg.DatePublished, arg.Publisher, arg.Contributors, + arg.Language, + arg.Edition, + arg.PageCount, + arg.Genre, + arg.CopyrightYear, + arg.GoodreadsID, + arg.OpenlibraryID, + arg.GoogleBooksID, ) var i MediaItems err := row.Scan( @@ -2220,9 +2566,23 @@ func (q *Queries) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams &i.DatePublished, &i.Publisher, &i.Contributors, + &i.Language, + &i.Edition, + &i.PageCount, + &i.Genre, + &i.CopyrightYear, + &i.GoodreadsID, + &i.OpenlibraryID, + &i.GoogleBooksID, &i.AddedByAdminID, &i.CreatedAt, &i.UpdatedAt, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, ) return i, err } @@ -2233,7 +2593,7 @@ UPDATE media_notes SET position = $3, updated_at = NOW() WHERE id = $1 -RETURNING id, media_item_id, user_id, content, position, created_at, updated_at +RETURNING id, media_item_id, user_id, content, position, created_at, updated_at, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data ` type UpdateMediaNoteParams struct { @@ -2253,6 +2613,13 @@ func (q *Queries) UpdateMediaNote(ctx context.Context, arg UpdateMediaNoteParams &i.Position, &i.CreatedAt, &i.UpdatedAt, + &i.PercentageLocation, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.ParagraphReference, + &i.DeviceSyncData, ) return i, err } @@ -2307,7 +2674,7 @@ DO UPDATE SET current_page = EXCLUDED.current_page, total_pages = EXCLUDED.total_pages, last_read_at = NOW() -RETURNING id, media_item_id, user_id, current_page, total_pages, last_read_at +RETURNING id, media_item_id, user_id, current_page, total_pages, last_read_at, percentage, character_offset, epubcfi, chapter, chapter_progress, viewport_x, viewport_y, zoom_level, scroll_position_x, scroll_position_y, panel_number, reading_mode, last_sync_device, last_sync_source, last_sync_timestamp, conflict_detected, conflict_resolved ` type UpdateReadingProgressParams struct { @@ -2332,6 +2699,23 @@ func (q *Queries) UpdateReadingProgress(ctx context.Context, arg UpdateReadingPr &i.CurrentPage, &i.TotalPages, &i.LastReadAt, + &i.Percentage, + &i.CharacterOffset, + &i.Epubcfi, + &i.Chapter, + &i.ChapterProgress, + &i.ViewportX, + &i.ViewportY, + &i.ZoomLevel, + &i.ScrollPositionX, + &i.ScrollPositionY, + &i.PanelNumber, + &i.ReadingMode, + &i.LastSyncDevice, + &i.LastSyncSource, + &i.LastSyncTimestamp, + &i.ConflictDetected, + &i.ConflictResolved, ) return i, err }