diff --git a/database/schema/schema.sql b/database/schema/schema.sql index 8318fcb..5ecf45d 100644 --- a/database/schema/schema.sql +++ b/database/schema/schema.sql @@ -245,6 +245,7 @@ CREATE TABLE IF NOT EXISTS reading_progress ( percentage FLOAT CHECK (percentage >= 0 AND percentage <= 1), character_offset BIGINT, epubcfi TEXT, + context_text TEXT, chapter INTEGER, chapter_progress FLOAT CHECK (chapter_progress >= 0 AND chapter_progress <= 1), viewport_x FLOAT DEFAULT 0, @@ -937,6 +938,7 @@ BEGIN percentage = (book_record->>'percentage')::FLOAT, character_offset = CASE WHEN book_record ? 'character' THEN (book_record->>'character')::BIGINT ELSE existing_progress.character_offset END, epubcfi = CASE WHEN book_record ? 'epubcfi' THEN (book_record->>'epubcfi')::TEXT ELSE existing_progress.epubcfi END, + context_text = CASE WHEN book_record ? 'context_text' THEN (book_record->>'context_text')::TEXT ELSE existing_progress.context_text END, chapter = CASE WHEN book_record ? 'chapter' THEN (book_record->>'chapter')::INTEGER ELSE existing_progress.chapter END, chapter_progress = (book_record->>'percentage')::FLOAT, last_sync_device = 'koreader', @@ -955,6 +957,7 @@ BEGIN percentage, character_offset, epubcfi, + context_text, chapter, chapter_progress, last_sync_device, @@ -971,6 +974,7 @@ BEGIN (book_record->>'percentage')::FLOAT, CASE WHEN book_record ? 'character' THEN (book_record->>'character')::BIGINT ELSE NULL END, CASE WHEN book_record ? 'epubcfi' THEN (book_record->>'epubcfi')::TEXT ELSE NULL END, + CASE WHEN book_record ? 'context_text' THEN (book_record->>'context_text')::TEXT ELSE NULL END, CASE WHEN book_record ? 'chapter' THEN (book_record->>'chapter')::INTEGER ELSE NULL END, (book_record->>'percentage')::FLOAT, 'koreader', diff --git a/internal/database/models.go b/internal/database/models.go index 31a33f1..a08dece 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -380,6 +380,7 @@ type ReadingProgress struct { Percentage pgtype.Float8 `db:"percentage" json:"percentage"` CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"` Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"` + ContextText pgtype.Text `db:"context_text" json:"context_text"` 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"` diff --git a/internal/database/querier.go b/internal/database/querier.go index 5e84d36..1d7b71d 100644 --- a/internal/database/querier.go +++ b/internal/database/querier.go @@ -280,6 +280,7 @@ type Querier interface { // Get user reading history for analytics GetUserReadingHistory(ctx context.Context, arg GetUserReadingHistoryParams) ([]GetUserReadingHistoryRow, error) GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUID) ([]GetUserVisibleLibrariesRow, error) + HasRecentConflictResolution(ctx context.Context, arg HasRecentConflictResolutionParams) (bool, error) IncrementSyncQueueAttempts(ctx context.Context, arg IncrementSyncQueueAttemptsParams) (SyncQueue, error) // Check if book is in collection IsBookInCollection(ctx context.Context, arg IsBookInCollectionParams) (bool, error) diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index 6ea72c3..03b44ea 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -5298,7 +5298,7 @@ func (q *Queries) GetReadingHistory(ctx context.Context, arg GetReadingHistoryPa } const GetReadingProgress = `-- name: GetReadingProgress :one -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 +SELECT id, media_item_id, user_id, current_page, total_pages, last_read_at, percentage, character_offset, epubcfi, context_text, 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 { @@ -5319,6 +5319,7 @@ func (q *Queries) GetReadingProgress(ctx context.Context, arg GetReadingProgress &i.Percentage, &i.CharacterOffset, &i.Epubcfi, + &i.ContextText, &i.Chapter, &i.ChapterProgress, &i.ViewportX, @@ -6000,6 +6001,7 @@ SELECT rp.percentage, rp.character_offset, rp.epubcfi, + rp.context_text, rp.chapter, rp.chapter_progress, rp.viewport_x, @@ -6040,6 +6042,7 @@ type GetUniversalProgressRow struct { Percentage pgtype.Float8 `db:"percentage" json:"percentage"` CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"` Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"` + ContextText pgtype.Text `db:"context_text" json:"context_text"` 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"` @@ -6076,6 +6079,7 @@ func (q *Queries) GetUniversalProgress(ctx context.Context, arg GetUniversalProg &i.Percentage, &i.CharacterOffset, &i.Epubcfi, + &i.ContextText, &i.Chapter, &i.ChapterProgress, &i.ViewportX, @@ -6811,6 +6815,28 @@ func (q *Queries) GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUI return items, nil } +const HasRecentConflictResolution = `-- name: HasRecentConflictResolution :one +SELECT EXISTS( + SELECT 1 FROM sync_conflicts + WHERE media_item_id = $1 + AND user_id = $2 + AND resolution_status != 'unresolved' + AND resolved_at > NOW() - INTERVAL '10 minutes' +) +` + +type HasRecentConflictResolutionParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +func (q *Queries) HasRecentConflictResolution(ctx context.Context, arg HasRecentConflictResolutionParams) (bool, error) { + row := q.db.QueryRow(ctx, HasRecentConflictResolution, arg.MediaItemID, arg.UserID) + var exists bool + err := row.Scan(&exists) + return exists, err +} + const IncrementSyncQueueAttempts = `-- name: IncrementSyncQueueAttempts :one UPDATE sync_queue SET @@ -10635,7 +10661,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, 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 +RETURNING id, media_item_id, user_id, current_page, total_pages, last_read_at, percentage, character_offset, epubcfi, context_text, 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 { @@ -10663,6 +10689,7 @@ func (q *Queries) UpdateReadingProgress(ctx context.Context, arg UpdateReadingPr &i.Percentage, &i.CharacterOffset, &i.Epubcfi, + &i.ContextText, &i.Chapter, &i.ChapterProgress, &i.ViewportX, @@ -10820,6 +10847,7 @@ INSERT INTO reading_progress ( percentage, character_offset, epubcfi, + context_text, chapter, chapter_progress, viewport_x, @@ -10837,13 +10865,14 @@ INSERT INTO reading_progress ( last_read_at ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, NOW(), $17, $18, NOW() + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, NOW(), $18, $19, NOW() ) ON CONFLICT (media_item_id, user_id) DO UPDATE SET percentage = EXCLUDED.percentage, character_offset = EXCLUDED.character_offset, epubcfi = EXCLUDED.epubcfi, + context_text = EXCLUDED.context_text, chapter = EXCLUDED.chapter, chapter_progress = EXCLUDED.chapter_progress, viewport_x = EXCLUDED.viewport_x, @@ -10859,7 +10888,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, 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 +RETURNING id, media_item_id, user_id, current_page, total_pages, last_read_at, percentage, character_offset, epubcfi, context_text, 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 UpdateUniversalProgressParams struct { @@ -10868,6 +10897,7 @@ type UpdateUniversalProgressParams struct { Percentage pgtype.Float8 `db:"percentage" json:"percentage"` CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"` Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"` + ContextText pgtype.Text `db:"context_text" json:"context_text"` 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"` @@ -10891,6 +10921,7 @@ func (q *Queries) UpdateUniversalProgress(ctx context.Context, arg UpdateUnivers arg.Percentage, arg.CharacterOffset, arg.Epubcfi, + arg.ContextText, arg.Chapter, arg.ChapterProgress, arg.ViewportX, @@ -10916,6 +10947,7 @@ func (q *Queries) UpdateUniversalProgress(ctx context.Context, arg UpdateUnivers &i.Percentage, &i.CharacterOffset, &i.Epubcfi, + &i.ContextText, &i.Chapter, &i.ChapterProgress, &i.ViewportX, diff --git a/internal/database/queries/queries.sql b/internal/database/queries/queries.sql index aee3fee..a9fd9ea 100644 --- a/internal/database/queries/queries.sql +++ b/internal/database/queries/queries.sql @@ -793,6 +793,7 @@ SELECT rp.percentage, rp.character_offset, rp.epubcfi, + rp.context_text, rp.chapter, rp.chapter_progress, rp.viewport_x, @@ -825,6 +826,7 @@ INSERT INTO reading_progress ( percentage, character_offset, epubcfi, + context_text, chapter, chapter_progress, viewport_x, @@ -842,13 +844,14 @@ INSERT INTO reading_progress ( last_read_at ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, NOW(), $17, $18, NOW() + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, NOW(), $18, $19, NOW() ) ON CONFLICT (media_item_id, user_id) DO UPDATE SET percentage = EXCLUDED.percentage, character_offset = EXCLUDED.character_offset, epubcfi = EXCLUDED.epubcfi, + context_text = EXCLUDED.context_text, chapter = EXCLUDED.chapter, chapter_progress = EXCLUDED.chapter_progress, viewport_x = EXCLUDED.viewport_x, @@ -1162,6 +1165,15 @@ JOIN media_items mi ON sc.media_item_id = mi.id WHERE sc.user_id = $1 ORDER BY sc.created_at DESC; +-- name: HasRecentConflictResolution :one +SELECT EXISTS( + SELECT 1 FROM sync_conflicts + WHERE media_item_id = $1 + AND user_id = $2 + AND resolution_status != 'unresolved' + AND resolved_at > NOW() - INTERVAL '10 minutes' +); + -- ============================================ -- KOREADER SYNC PROTOCOL -- ============================================