feat(db): add context_text column to reading_progress

Stores surrounding text (~100 chars) at the reader's current position.
Used as fallback for CFI resolution when converting between epubcfi
and CREngine XPointer formats.

Updates:
- schema.sql: add context_text TEXT column, update stored procedure
- queries.sql: add context_text to GetUniversalProgress and
  UpdateUniversalProgress queries
- Regenerate sqlc Go code (models.go, querier.go, queries.sql.go)
This commit is contained in:
2026-06-02 19:44:31 -04:00
parent 5a76fed099
commit c82f20c3f2
5 changed files with 55 additions and 5 deletions
+4
View File
@@ -245,6 +245,7 @@ CREATE TABLE IF NOT EXISTS reading_progress (
percentage FLOAT CHECK (percentage >= 0 AND percentage <= 1), percentage FLOAT CHECK (percentage >= 0 AND percentage <= 1),
character_offset BIGINT, character_offset BIGINT,
epubcfi TEXT, epubcfi TEXT,
context_text TEXT,
chapter INTEGER, chapter INTEGER,
chapter_progress FLOAT CHECK (chapter_progress >= 0 AND chapter_progress <= 1), chapter_progress FLOAT CHECK (chapter_progress >= 0 AND chapter_progress <= 1),
viewport_x FLOAT DEFAULT 0, viewport_x FLOAT DEFAULT 0,
@@ -937,6 +938,7 @@ BEGIN
percentage = (book_record->>'percentage')::FLOAT, percentage = (book_record->>'percentage')::FLOAT,
character_offset = CASE WHEN book_record ? 'character' THEN (book_record->>'character')::BIGINT ELSE existing_progress.character_offset END, 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, 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 = CASE WHEN book_record ? 'chapter' THEN (book_record->>'chapter')::INTEGER ELSE existing_progress.chapter END,
chapter_progress = (book_record->>'percentage')::FLOAT, chapter_progress = (book_record->>'percentage')::FLOAT,
last_sync_device = 'koreader', last_sync_device = 'koreader',
@@ -955,6 +957,7 @@ BEGIN
percentage, percentage,
character_offset, character_offset,
epubcfi, epubcfi,
context_text,
chapter, chapter,
chapter_progress, chapter_progress,
last_sync_device, last_sync_device,
@@ -971,6 +974,7 @@ BEGIN
(book_record->>'percentage')::FLOAT, (book_record->>'percentage')::FLOAT,
CASE WHEN book_record ? 'character' THEN (book_record->>'character')::BIGINT ELSE NULL END, 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 ? '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, CASE WHEN book_record ? 'chapter' THEN (book_record->>'chapter')::INTEGER ELSE NULL END,
(book_record->>'percentage')::FLOAT, (book_record->>'percentage')::FLOAT,
'koreader', 'koreader',
+1
View File
@@ -380,6 +380,7 @@ type ReadingProgress struct {
Percentage pgtype.Float8 `db:"percentage" json:"percentage"` Percentage pgtype.Float8 `db:"percentage" json:"percentage"`
CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"` CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"`
Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"` Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"`
ContextText pgtype.Text `db:"context_text" json:"context_text"`
Chapter pgtype.Int4 `db:"chapter" json:"chapter"` Chapter pgtype.Int4 `db:"chapter" json:"chapter"`
ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"` ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"`
ViewportX pgtype.Float8 `db:"viewport_x" json:"viewport_x"` ViewportX pgtype.Float8 `db:"viewport_x" json:"viewport_x"`
+1
View File
@@ -280,6 +280,7 @@ type Querier interface {
// Get user reading history for analytics // Get user reading history for analytics
GetUserReadingHistory(ctx context.Context, arg GetUserReadingHistoryParams) ([]GetUserReadingHistoryRow, error) GetUserReadingHistory(ctx context.Context, arg GetUserReadingHistoryParams) ([]GetUserReadingHistoryRow, error)
GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUID) ([]GetUserVisibleLibrariesRow, 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) IncrementSyncQueueAttempts(ctx context.Context, arg IncrementSyncQueueAttemptsParams) (SyncQueue, error)
// Check if book is in collection // Check if book is in collection
IsBookInCollection(ctx context.Context, arg IsBookInCollectionParams) (bool, error) IsBookInCollection(ctx context.Context, arg IsBookInCollectionParams) (bool, error)
+36 -4
View File
@@ -5298,7 +5298,7 @@ func (q *Queries) GetReadingHistory(ctx context.Context, arg GetReadingHistoryPa
} }
const GetReadingProgress = `-- name: GetReadingProgress :one 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 { type GetReadingProgressParams struct {
@@ -5319,6 +5319,7 @@ func (q *Queries) GetReadingProgress(ctx context.Context, arg GetReadingProgress
&i.Percentage, &i.Percentage,
&i.CharacterOffset, &i.CharacterOffset,
&i.Epubcfi, &i.Epubcfi,
&i.ContextText,
&i.Chapter, &i.Chapter,
&i.ChapterProgress, &i.ChapterProgress,
&i.ViewportX, &i.ViewportX,
@@ -6000,6 +6001,7 @@ SELECT
rp.percentage, rp.percentage,
rp.character_offset, rp.character_offset,
rp.epubcfi, rp.epubcfi,
rp.context_text,
rp.chapter, rp.chapter,
rp.chapter_progress, rp.chapter_progress,
rp.viewport_x, rp.viewport_x,
@@ -6040,6 +6042,7 @@ type GetUniversalProgressRow struct {
Percentage pgtype.Float8 `db:"percentage" json:"percentage"` Percentage pgtype.Float8 `db:"percentage" json:"percentage"`
CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"` CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"`
Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"` Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"`
ContextText pgtype.Text `db:"context_text" json:"context_text"`
Chapter pgtype.Int4 `db:"chapter" json:"chapter"` Chapter pgtype.Int4 `db:"chapter" json:"chapter"`
ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"` ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"`
ViewportX pgtype.Float8 `db:"viewport_x" json:"viewport_x"` 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.Percentage,
&i.CharacterOffset, &i.CharacterOffset,
&i.Epubcfi, &i.Epubcfi,
&i.ContextText,
&i.Chapter, &i.Chapter,
&i.ChapterProgress, &i.ChapterProgress,
&i.ViewportX, &i.ViewportX,
@@ -6811,6 +6815,28 @@ func (q *Queries) GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUI
return items, nil 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 const IncrementSyncQueueAttempts = `-- name: IncrementSyncQueueAttempts :one
UPDATE sync_queue UPDATE sync_queue
SET SET
@@ -10635,7 +10661,7 @@ DO UPDATE SET
current_page = EXCLUDED.current_page, current_page = EXCLUDED.current_page,
total_pages = EXCLUDED.total_pages, total_pages = EXCLUDED.total_pages,
last_read_at = NOW() 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 { type UpdateReadingProgressParams struct {
@@ -10663,6 +10689,7 @@ func (q *Queries) UpdateReadingProgress(ctx context.Context, arg UpdateReadingPr
&i.Percentage, &i.Percentage,
&i.CharacterOffset, &i.CharacterOffset,
&i.Epubcfi, &i.Epubcfi,
&i.ContextText,
&i.Chapter, &i.Chapter,
&i.ChapterProgress, &i.ChapterProgress,
&i.ViewportX, &i.ViewportX,
@@ -10820,6 +10847,7 @@ INSERT INTO reading_progress (
percentage, percentage,
character_offset, character_offset,
epubcfi, epubcfi,
context_text,
chapter, chapter,
chapter_progress, chapter_progress,
viewport_x, viewport_x,
@@ -10837,13 +10865,14 @@ INSERT INTO reading_progress (
last_read_at last_read_at
) )
VALUES ( 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) ON CONFLICT (media_item_id, user_id)
DO UPDATE SET DO UPDATE SET
percentage = EXCLUDED.percentage, percentage = EXCLUDED.percentage,
character_offset = EXCLUDED.character_offset, character_offset = EXCLUDED.character_offset,
epubcfi = EXCLUDED.epubcfi, epubcfi = EXCLUDED.epubcfi,
context_text = EXCLUDED.context_text,
chapter = EXCLUDED.chapter, chapter = EXCLUDED.chapter,
chapter_progress = EXCLUDED.chapter_progress, chapter_progress = EXCLUDED.chapter_progress,
viewport_x = EXCLUDED.viewport_x, viewport_x = EXCLUDED.viewport_x,
@@ -10859,7 +10888,7 @@ DO UPDATE SET
current_page = EXCLUDED.current_page, current_page = EXCLUDED.current_page,
total_pages = EXCLUDED.total_pages, total_pages = EXCLUDED.total_pages,
last_read_at = NOW() 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 { type UpdateUniversalProgressParams struct {
@@ -10868,6 +10897,7 @@ type UpdateUniversalProgressParams struct {
Percentage pgtype.Float8 `db:"percentage" json:"percentage"` Percentage pgtype.Float8 `db:"percentage" json:"percentage"`
CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"` CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"`
Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"` Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"`
ContextText pgtype.Text `db:"context_text" json:"context_text"`
Chapter pgtype.Int4 `db:"chapter" json:"chapter"` Chapter pgtype.Int4 `db:"chapter" json:"chapter"`
ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"` ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"`
ViewportX pgtype.Float8 `db:"viewport_x" json:"viewport_x"` 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.Percentage,
arg.CharacterOffset, arg.CharacterOffset,
arg.Epubcfi, arg.Epubcfi,
arg.ContextText,
arg.Chapter, arg.Chapter,
arg.ChapterProgress, arg.ChapterProgress,
arg.ViewportX, arg.ViewportX,
@@ -10916,6 +10947,7 @@ func (q *Queries) UpdateUniversalProgress(ctx context.Context, arg UpdateUnivers
&i.Percentage, &i.Percentage,
&i.CharacterOffset, &i.CharacterOffset,
&i.Epubcfi, &i.Epubcfi,
&i.ContextText,
&i.Chapter, &i.Chapter,
&i.ChapterProgress, &i.ChapterProgress,
&i.ViewportX, &i.ViewportX,
+13 -1
View File
@@ -793,6 +793,7 @@ SELECT
rp.percentage, rp.percentage,
rp.character_offset, rp.character_offset,
rp.epubcfi, rp.epubcfi,
rp.context_text,
rp.chapter, rp.chapter,
rp.chapter_progress, rp.chapter_progress,
rp.viewport_x, rp.viewport_x,
@@ -825,6 +826,7 @@ INSERT INTO reading_progress (
percentage, percentage,
character_offset, character_offset,
epubcfi, epubcfi,
context_text,
chapter, chapter,
chapter_progress, chapter_progress,
viewport_x, viewport_x,
@@ -842,13 +844,14 @@ INSERT INTO reading_progress (
last_read_at last_read_at
) )
VALUES ( 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) ON CONFLICT (media_item_id, user_id)
DO UPDATE SET DO UPDATE SET
percentage = EXCLUDED.percentage, percentage = EXCLUDED.percentage,
character_offset = EXCLUDED.character_offset, character_offset = EXCLUDED.character_offset,
epubcfi = EXCLUDED.epubcfi, epubcfi = EXCLUDED.epubcfi,
context_text = EXCLUDED.context_text,
chapter = EXCLUDED.chapter, chapter = EXCLUDED.chapter,
chapter_progress = EXCLUDED.chapter_progress, chapter_progress = EXCLUDED.chapter_progress,
viewport_x = EXCLUDED.viewport_x, 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 WHERE sc.user_id = $1
ORDER BY sc.created_at DESC; 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 -- KOREADER SYNC PROTOCOL
-- ============================================ -- ============================================