diff --git a/database/schema/schema.sql b/database/schema/schema.sql index 5ecf45d..9d72688 100644 --- a/database/schema/schema.sql +++ b/database/schema/schema.sql @@ -1312,3 +1312,48 @@ CREATE TABLE IF NOT EXISTS media_bookmarks ( CREATE INDEX IF NOT EXISTS idx_media_bookmarks_media ON media_bookmarks(media_item_id); CREATE INDEX IF NOT EXISTS idx_media_bookmarks_user ON media_bookmarks(user_id); + +-- ============================================ +-- ANNOTATION SYNC MIGRATIONS +-- Adds dedup_key, LWW timestamps, soft-delete, +-- and device_sync_data to annotation tables. +-- ============================================ + +ALTER TABLE media_highlights ADD COLUMN IF NOT EXISTS dedup_key VARCHAR(40); +ALTER TABLE media_highlights ADD COLUMN IF NOT EXISTS last_modified_at TIMESTAMPTZ; +ALTER TABLE media_highlights ADD COLUMN IF NOT EXISTS last_modified_source VARCHAR(30); +ALTER TABLE media_highlights ADD COLUMN IF NOT EXISTS note_text TEXT; +ALTER TABLE media_highlights ADD COLUMN IF NOT EXISTS deleted BOOLEAN DEFAULT FALSE; +ALTER TABLE media_highlights ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ; + +ALTER TABLE media_notes ADD COLUMN IF NOT EXISTS dedup_key VARCHAR(40); +ALTER TABLE media_notes ADD COLUMN IF NOT EXISTS last_modified_at TIMESTAMPTZ; +ALTER TABLE media_notes ADD COLUMN IF NOT EXISTS last_modified_source VARCHAR(30); +ALTER TABLE media_notes ADD COLUMN IF NOT EXISTS deleted BOOLEAN DEFAULT FALSE; +ALTER TABLE media_notes ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ; + +ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS dedup_key VARCHAR(40); +ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS last_modified_at TIMESTAMPTZ; +ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS last_modified_source VARCHAR(30); +ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS device_sync_data JSONB; +ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS percentage_location FLOAT; +ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS epubcfi_location TEXT; +ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS chapter_reference INTEGER; +ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS deleted BOOLEAN DEFAULT FALSE; +ALTER TABLE media_bookmarks ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ; + +CREATE UNIQUE INDEX IF NOT EXISTS idx_media_highlights_dedup + ON media_highlights (user_id, media_item_id, dedup_key) + WHERE dedup_key IS NOT NULL AND deleted = FALSE; + +CREATE UNIQUE INDEX IF NOT EXISTS idx_media_notes_dedup + ON media_notes (user_id, media_item_id, dedup_key) + WHERE dedup_key IS NOT NULL AND deleted = FALSE; + +CREATE UNIQUE INDEX IF NOT EXISTS idx_media_bookmarks_dedup + ON media_bookmarks (user_id, media_item_id, dedup_key) + WHERE dedup_key IS NOT NULL AND deleted = FALSE; + +CREATE INDEX IF NOT EXISTS idx_media_highlights_deleted_at ON media_highlights(deleted_at) WHERE deleted = TRUE; +CREATE INDEX IF NOT EXISTS idx_media_notes_deleted_at ON media_notes(deleted_at) WHERE deleted = TRUE; +CREATE INDEX IF NOT EXISTS idx_media_bookmarks_deleted_at ON media_bookmarks(deleted_at) WHERE deleted = TRUE; diff --git a/internal/database/db.go b/internal/database/db.go index 486aa36..bdf4241 100644 --- a/internal/database/db.go +++ b/internal/database/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.31.1 +// sqlc v1.30.0 package database diff --git a/internal/database/models.go b/internal/database/models.go index a08dece..fa7d6a0 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.31.1 +// sqlc v1.30.0 package database @@ -155,40 +155,55 @@ type LibraryVisibility struct { } type MediaBookmarks struct { - ID pgtype.UUID `db:"id" json:"id"` - MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` - UserID pgtype.UUID `db:"user_id" json:"user_id"` - PageNumber pgtype.Int4 `db:"page_number" json:"page_number"` - ChapterNumber pgtype.Int4 `db:"chapter_number" json:"chapter_number"` - CfiPosition pgtype.Text `db:"cfi_position" json:"cfi_position"` - Title string `db:"title" json:"title"` - Position pgtype.Text `db:"position" json:"position"` - Notes pgtype.Text `db:"notes" json:"notes"` - CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + ID pgtype.UUID `db:"id" json:"id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + PageNumber pgtype.Int4 `db:"page_number" json:"page_number"` + ChapterNumber pgtype.Int4 `db:"chapter_number" json:"chapter_number"` + CfiPosition pgtype.Text `db:"cfi_position" json:"cfi_position"` + Title string `db:"title" json:"title"` + Position pgtype.Text `db:"position" json:"position"` + Notes pgtype.Text `db:"notes" json:"notes"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` + LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"` + LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"` + DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"` + PercentageLocation pgtype.Float8 `db:"percentage_location" json:"percentage_location"` + EpubcfiLocation pgtype.Text `db:"epubcfi_location" json:"epubcfi_location"` + ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"` + Deleted pgtype.Bool `db:"deleted" json:"deleted"` + DeletedAt pgtype.Timestamptz `db:"deleted_at" json:"deleted_at"` } 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"` - 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"` + 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"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` + LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"` + LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"` + NoteText pgtype.Text `db:"note_text" json:"note_text"` + Deleted pgtype.Bool `db:"deleted" json:"deleted"` + DeletedAt pgtype.Timestamptz `db:"deleted_at" json:"deleted_at"` } type MediaItemFormats struct { @@ -304,6 +319,11 @@ type MediaNotes struct { 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"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` + LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"` + LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"` + Deleted pgtype.Bool `db:"deleted" json:"deleted"` + DeletedAt pgtype.Timestamptz `db:"deleted_at" json:"deleted_at"` } type MediaRatings struct { diff --git a/internal/database/querier.go b/internal/database/querier.go index 1412be4..4a6f720 100644 --- a/internal/database/querier.go +++ b/internal/database/querier.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.31.1 +// sqlc v1.30.0 package database @@ -34,6 +34,7 @@ type Querier interface { // Count unlinked books for a device CountUnlinkedBooks(ctx context.Context, deviceID pgtype.UUID) (int64, error) CountUserDevices(ctx context.Context, userID pgtype.UUID) (int64, error) + CreateAutoResolvedSyncConflict(ctx context.Context, arg CreateAutoResolvedSyncConflictParams) (SyncConflicts, error) // COLLECTIONS QUERIES // Create collection CreateCollection(ctx context.Context, arg CreateCollectionParams) (Collections, error) @@ -55,8 +56,10 @@ type Querier interface { // Libraries queries CreateLibrary(ctx context.Context, arg CreateLibraryParams) (Libraries, error) CreateMediaBookmark(ctx context.Context, arg CreateMediaBookmarkParams) (MediaBookmarks, error) + CreateMediaBookmarkFull(ctx context.Context, arg CreateMediaBookmarkFullParams) (MediaBookmarks, error) // Media Highlights queries CreateMediaHighlight(ctx context.Context, arg CreateMediaHighlightParams) (MediaHighlights, error) + CreateMediaHighlightFull(ctx context.Context, arg CreateMediaHighlightFullParams) (MediaHighlights, error) // Media Items queries CreateMediaItem(ctx context.Context, arg CreateMediaItemParams) (MediaItems, error) // MEDIA ITEM FORMATS QUERIES @@ -64,6 +67,7 @@ type Querier interface { CreateMediaItemFormat(ctx context.Context, arg CreateMediaItemFormatParams) (MediaItemFormats, error) // Media Notes queries CreateMediaNote(ctx context.Context, arg CreateMediaNoteParams) (MediaNotes, error) + CreateMediaNoteFull(ctx context.Context, arg CreateMediaNoteFullParams) (MediaNotes, error) CreateMediaRating(ctx context.Context, arg CreateMediaRatingParams) (MediaRatings, error) // OPDS TOKENS QUERIES // Create OPDS token @@ -126,6 +130,10 @@ type Querier interface { DeleteUser(ctx context.Context, id pgtype.UUID) error DeleteUserSystemCollection(ctx context.Context, arg DeleteUserSystemCollectionParams) error GenerateKoboEntitlementId(ctx context.Context) (interface{}, error) + // ============================================ + // ANNOTATION SERVE QUERIES + // ============================================ + GetActiveAnnotationsForBook(ctx context.Context, arg GetActiveAnnotationsForBookParams) ([]GetActiveAnnotationsForBookRow, error) // Get all system config GetAllSystemConfig(ctx context.Context) ([]SystemConfig, error) GetAllSystemSettings(ctx context.Context) ([]GetAllSystemSettingsRow, error) @@ -197,8 +205,17 @@ type Querier interface { // LIBRARY WITH TYPE INFO QUERIES // ============================================================================ GetLibraryWithType(ctx context.Context, id pgtype.UUID) (GetLibraryWithTypeRow, error) + GetMediaBookmark(ctx context.Context, id pgtype.UUID) (MediaBookmarks, error) + // ============================================ + // ANNOTATION SYNC QUERIES (bookmarks) + // ============================================ + GetMediaBookmarkByDedupKey(ctx context.Context, arg GetMediaBookmarkByDedupKeyParams) (MediaBookmarks, error) GetMediaBookmarks(ctx context.Context, arg GetMediaBookmarksParams) ([]MediaBookmarks, error) GetMediaHighlight(ctx context.Context, id pgtype.UUID) (MediaHighlights, error) + // ============================================ + // ANNOTATION SYNC QUERIES (highlights) + // ============================================ + GetMediaHighlightByDedupKey(ctx context.Context, arg GetMediaHighlightByDedupKeyParams) (MediaHighlights, error) GetMediaHighlights(ctx context.Context, arg GetMediaHighlightsParams) ([]MediaHighlights, error) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, error) GetMediaItemByFilePath(ctx context.Context, arg GetMediaItemByFilePathParams) (MediaItems, error) @@ -221,6 +238,10 @@ type Querier interface { // Get media item formats GetMediaItemFormats(ctx context.Context, mediaItemID pgtype.UUID) ([]MediaItemFormats, error) GetMediaNote(ctx context.Context, id pgtype.UUID) (MediaNotes, error) + // ============================================ + // ANNOTATION SYNC QUERIES (notes) + // ============================================ + GetMediaNoteByDedupKey(ctx context.Context, arg GetMediaNoteByDedupKeyParams) (MediaNotes, error) GetMediaNotes(ctx context.Context, arg GetMediaNotesParams) ([]MediaNotes, error) GetMediaRating(ctx context.Context, arg GetMediaRatingParams) (MediaRatings, error) GetMediaRatings(ctx context.Context, mediaItemID pgtype.UUID) ([]GetMediaRatingsRow, error) @@ -258,6 +279,7 @@ type Querier interface { // System Settings queries GetSystemSetting(ctx context.Context, settingKey string) (string, error) GetSystemTimezone(ctx context.Context) (string, error) + GetTombstonedAnnotationsForBook(ctx context.Context, arg GetTombstonedAnnotationsForBookParams) ([]GetTombstonedAnnotationsForBookRow, error) // Get universal progress for a book GetUniversalProgress(ctx context.Context, arg GetUniversalProgressParams) (GetUniversalProgressRow, error) // Get unlinked book by ContentId @@ -304,6 +326,9 @@ type Querier interface { // List unresolved unlinked books with pagination ListUnresolvedUnlinkedBooks(ctx context.Context, arg ListUnresolvedUnlinkedBooksParams) ([]ListUnresolvedUnlinkedBooksRow, error) ListUsers(ctx context.Context) ([]ListUsersRow, error) + PurgeExpiredBookmarkTombstones(ctx context.Context, deletedAt pgtype.Timestamptz) error + PurgeExpiredHighlightTombstones(ctx context.Context, deletedAt pgtype.Timestamptz) error + PurgeExpiredNoteTombstones(ctx context.Context, deletedAt pgtype.Timestamptz) error // Query media items by multiple identifiers with confidence scoring QueryMediaItemsByIdentifiers(ctx context.Context, arg QueryMediaItemsByIdentifiersParams) ([]QueryMediaItemsByIdentifiersRow, error) ReassignLibraries(ctx context.Context, arg ReassignLibrariesParams) error @@ -334,6 +359,12 @@ type Querier interface { // Set system config SetSystemConfig(ctx context.Context, arg SetSystemConfigParams) (SystemConfig, error) SyncLibraryTypeExtensions(ctx context.Context, arg SyncLibraryTypeExtensionsParams) error + TombstoneMediaBookmarkByDedupKey(ctx context.Context, arg TombstoneMediaBookmarkByDedupKeyParams) error + TombstoneMediaBookmarkByID(ctx context.Context, id pgtype.UUID) error + TombstoneMediaHighlightByDedupKey(ctx context.Context, arg TombstoneMediaHighlightByDedupKeyParams) error + TombstoneMediaHighlightByID(ctx context.Context, id pgtype.UUID) error + TombstoneMediaNoteByDedupKey(ctx context.Context, arg TombstoneMediaNoteByDedupKeyParams) error + TombstoneMediaNoteByID(ctx context.Context, id pgtype.UUID) error // Update collection UpdateCollection(ctx context.Context, arg UpdateCollectionParams) (Collections, error) UpdateDashboardPreferences(ctx context.Context, arg UpdateDashboardPreferencesParams) (UserDashboardPreferences, error) @@ -357,7 +388,9 @@ type Querier interface { UpdateKoboShelfCollection(ctx context.Context, arg UpdateKoboShelfCollectionParams) (KoboShelves, error) UpdateLibrary(ctx context.Context, arg UpdateLibraryParams) (Libraries, error) UpdateMediaBookmark(ctx context.Context, arg UpdateMediaBookmarkParams) (MediaBookmarks, error) + UpdateMediaBookmarkForSync(ctx context.Context, arg UpdateMediaBookmarkForSyncParams) (MediaBookmarks, error) UpdateMediaHighlight(ctx context.Context, arg UpdateMediaHighlightParams) (MediaHighlights, error) + UpdateMediaHighlightForSync(ctx context.Context, arg UpdateMediaHighlightForSyncParams) (MediaHighlights, error) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams) (MediaItems, error) UpdateMediaItemChapterMetadata(ctx context.Context, arg UpdateMediaItemChapterMetadataParams) (MediaItems, error) // Update media item format @@ -375,6 +408,7 @@ type Querier interface { UpdateMediaItemIdentifiers(ctx context.Context, arg UpdateMediaItemIdentifiersParams) (MediaItems, error) UpdateMediaItemKoboMetadata(ctx context.Context, arg UpdateMediaItemKoboMetadataParams) (MediaItems, error) UpdateMediaNote(ctx context.Context, arg UpdateMediaNoteParams) (MediaNotes, error) + UpdateMediaNoteForSync(ctx context.Context, arg UpdateMediaNoteForSyncParams) (MediaNotes, error) UpdateMediaRating(ctx context.Context, arg UpdateMediaRatingParams) (MediaRatings, error) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error UpdateReadingProgress(ctx context.Context, arg UpdateReadingProgressParams) (ReadingProgress, error) diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index d65967d..4d8327c 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.31.1 +// sqlc v1.30.0 // source: queries.sql package database @@ -262,6 +262,44 @@ func (q *Queries) CountUserDevices(ctx context.Context, userID pgtype.UUID) (int return count, err } +const CreateAutoResolvedSyncConflict = `-- name: CreateAutoResolvedSyncConflict :one +INSERT INTO sync_conflicts (media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_at) +VALUES ($1, $2, $3, $4, 'auto_resolved', $5, NOW()) +RETURNING id, media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_by, resolved_at, created_at +` + +type CreateAutoResolvedSyncConflictParams struct { + 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"` + ResolutionData []byte `db:"resolution_data" json:"resolution_data"` +} + +func (q *Queries) CreateAutoResolvedSyncConflict(ctx context.Context, arg CreateAutoResolvedSyncConflictParams) (SyncConflicts, error) { + row := q.db.QueryRow(ctx, CreateAutoResolvedSyncConflict, + arg.MediaItemID, + arg.UserID, + arg.ConflictType, + arg.ConflictData, + arg.ResolutionData, + ) + var i SyncConflicts + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.ConflictType, + &i.ConflictData, + &i.ResolutionStatus, + &i.ResolutionData, + &i.ResolvedBy, + &i.ResolvedAt, + &i.CreatedAt, + ) + return i, err +} + const CreateCollection = `-- name: CreateCollection :one INSERT INTO collections (user_id, name, description, color, icon, auto_assign_rules, view_settings) @@ -566,7 +604,7 @@ func (q *Queries) CreateLibrary(ctx context.Context, arg CreateLibraryParams) (L const CreateMediaBookmark = `-- name: CreateMediaBookmark :one INSERT INTO media_bookmarks (media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) -RETURNING id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at +RETURNING id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at ` type CreateMediaBookmarkParams struct { @@ -603,6 +641,88 @@ func (q *Queries) CreateMediaBookmark(ctx context.Context, arg CreateMediaBookma &i.Position, &i.Notes, &i.CreatedAt, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.DeviceSyncData, + &i.PercentageLocation, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.Deleted, + &i.DeletedAt, + ) + return i, err +} + +const CreateMediaBookmarkFull = `-- name: CreateMediaBookmarkFull :one +INSERT INTO media_bookmarks ( + media_item_id, user_id, page_number, chapter_number, + cfi_position, title, position, notes, + percentage_location, epubcfi_location, chapter_reference, + dedup_key, last_modified_at, last_modified_source, + device_sync_data +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 +) RETURNING id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at +` + +type CreateMediaBookmarkFullParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + PageNumber pgtype.Int4 `db:"page_number" json:"page_number"` + ChapterNumber pgtype.Int4 `db:"chapter_number" json:"chapter_number"` + CfiPosition pgtype.Text `db:"cfi_position" json:"cfi_position"` + Title string `db:"title" json:"title"` + Position pgtype.Text `db:"position" json:"position"` + Notes pgtype.Text `db:"notes" json:"notes"` + PercentageLocation pgtype.Float8 `db:"percentage_location" json:"percentage_location"` + EpubcfiLocation pgtype.Text `db:"epubcfi_location" json:"epubcfi_location"` + ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` + LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"` + LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"` + DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"` +} + +func (q *Queries) CreateMediaBookmarkFull(ctx context.Context, arg CreateMediaBookmarkFullParams) (MediaBookmarks, error) { + row := q.db.QueryRow(ctx, CreateMediaBookmarkFull, + arg.MediaItemID, + arg.UserID, + arg.PageNumber, + arg.ChapterNumber, + arg.CfiPosition, + arg.Title, + arg.Position, + arg.Notes, + arg.PercentageLocation, + arg.EpubcfiLocation, + arg.ChapterReference, + arg.DedupKey, + arg.LastModifiedAt, + arg.LastModifiedSource, + arg.DeviceSyncData, + ) + var i MediaBookmarks + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.PageNumber, + &i.ChapterNumber, + &i.CfiPosition, + &i.Title, + &i.Position, + &i.Notes, + &i.CreatedAt, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.DeviceSyncData, + &i.PercentageLocation, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.Deleted, + &i.DeletedAt, ) return i, err } @@ -610,7 +730,7 @@ func (q *Queries) CreateMediaBookmark(ctx context.Context, arg CreateMediaBookma 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, percentage_start, percentage_end, character_start, character_end, epubcfi_start, epubcfi_end, chapter_reference, paragraph_start, paragraph_end, panel_number, device_sync_data +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, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at ` type CreateMediaHighlightParams struct { @@ -657,6 +777,97 @@ func (q *Queries) CreateMediaHighlight(ctx context.Context, arg CreateMediaHighl &i.ParagraphEnd, &i.PanelNumber, &i.DeviceSyncData, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.NoteText, + &i.Deleted, + &i.DeletedAt, + ) + return i, err +} + +const CreateMediaHighlightFull = `-- name: CreateMediaHighlightFull :one +INSERT INTO media_highlights ( + media_item_id, user_id, selection_text, + start_position, end_position, color, note_text, + percentage_start, percentage_end, + epubcfi_start, epubcfi_end, + chapter_reference, + dedup_key, last_modified_at, last_modified_source, + device_sync_data +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16 +) 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, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at +` + +type CreateMediaHighlightFullParams struct { + 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"` + NoteText pgtype.Text `db:"note_text" json:"note_text"` + PercentageStart pgtype.Float8 `db:"percentage_start" json:"percentage_start"` + PercentageEnd pgtype.Float8 `db:"percentage_end" json:"percentage_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"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` + LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"` + LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"` + DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"` +} + +func (q *Queries) CreateMediaHighlightFull(ctx context.Context, arg CreateMediaHighlightFullParams) (MediaHighlights, error) { + row := q.db.QueryRow(ctx, CreateMediaHighlightFull, + arg.MediaItemID, + arg.UserID, + arg.SelectionText, + arg.StartPosition, + arg.EndPosition, + arg.Color, + arg.NoteText, + arg.PercentageStart, + arg.PercentageEnd, + arg.EpubcfiStart, + arg.EpubcfiEnd, + arg.ChapterReference, + arg.DedupKey, + arg.LastModifiedAt, + arg.LastModifiedSource, + arg.DeviceSyncData, + ) + var i MediaHighlights + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.SelectionText, + &i.StartPosition, + &i.EndPosition, + &i.Color, + &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, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.NoteText, + &i.Deleted, + &i.DeletedAt, ) return i, err } @@ -876,7 +1087,7 @@ func (q *Queries) CreateMediaItemFormat(ctx context.Context, arg CreateMediaItem 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, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data +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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at ` type CreateMediaNoteParams struct { @@ -910,6 +1121,82 @@ func (q *Queries) CreateMediaNote(ctx context.Context, arg CreateMediaNoteParams &i.ChapterReference, &i.ParagraphReference, &i.DeviceSyncData, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.Deleted, + &i.DeletedAt, + ) + return i, err +} + +const CreateMediaNoteFull = `-- name: CreateMediaNoteFull :one +INSERT INTO media_notes ( + media_item_id, user_id, content, position, + percentage_location, character_start, character_end, + epubcfi_location, chapter_reference, paragraph_reference, + dedup_key, last_modified_at, last_modified_source, + device_sync_data +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14 +) 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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at +` + +type CreateMediaNoteFullParams struct { + 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"` + 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"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` + LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"` + LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"` + DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"` +} + +func (q *Queries) CreateMediaNoteFull(ctx context.Context, arg CreateMediaNoteFullParams) (MediaNotes, error) { + row := q.db.QueryRow(ctx, CreateMediaNoteFull, + arg.MediaItemID, + arg.UserID, + arg.Content, + arg.Position, + arg.PercentageLocation, + arg.CharacterStart, + arg.CharacterEnd, + arg.EpubcfiLocation, + arg.ChapterReference, + arg.ParagraphReference, + arg.DedupKey, + arg.LastModifiedAt, + arg.LastModifiedSource, + arg.DeviceSyncData, + ) + var i MediaNotes + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.Content, + &i.Position, + &i.CreatedAt, + &i.UpdatedAt, + &i.PercentageLocation, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.ParagraphReference, + &i.DeviceSyncData, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.Deleted, + &i.DeletedAt, ) return i, err } @@ -1803,6 +2090,114 @@ func (q *Queries) GenerateKoboEntitlementId(ctx context.Context) (interface{}, e return entitlement_id, err } +const GetActiveAnnotationsForBook = `-- name: GetActiveAnnotationsForBook :many + +SELECT + mh.id, + mh.selection_text, + mh.start_position, + mh.end_position, + mh.color, + mh.created_at, + mh.updated_at, + 'highlight' as annotation_type, + mh.percentage_start, + mh.percentage_end, + mh.epubcfi_start, + mh.epubcfi_end, + mh.note_text, + mh.dedup_key, + mh.last_modified_at, + mh.last_modified_source +FROM media_highlights mh +WHERE mh.media_item_id = $1 AND mh.user_id = $2 AND mh.deleted = FALSE +UNION ALL +SELECT + mn.id, + mn.content, + mn.position, + NULL as end_position, + NULL as color, + mn.created_at, + mn.updated_at, + 'note' as annotation_type, + mn.percentage_location as percentage_start, + NULL as percentage_end, + mn.epubcfi_location as epubcfi_start, + NULL as epubcfi_end, + NULL as note_text, + mn.dedup_key, + mn.last_modified_at, + mn.last_modified_source +FROM media_notes mn +WHERE mn.media_item_id = $1 AND mn.user_id = $2 AND mn.deleted = FALSE +ORDER BY created_at DESC +` + +type GetActiveAnnotationsForBookParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +type GetActiveAnnotationsForBookRow struct { + ID pgtype.UUID `db:"id" json:"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"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + AnnotationType string `db:"annotation_type" json:"annotation_type"` + PercentageStart pgtype.Float8 `db:"percentage_start" json:"percentage_start"` + PercentageEnd pgtype.Float8 `db:"percentage_end" json:"percentage_end"` + EpubcfiStart pgtype.Text `db:"epubcfi_start" json:"epubcfi_start"` + EpubcfiEnd pgtype.Text `db:"epubcfi_end" json:"epubcfi_end"` + NoteText pgtype.Text `db:"note_text" json:"note_text"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` + LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"` + LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"` +} + +// ============================================ +// ANNOTATION SERVE QUERIES +// ============================================ +func (q *Queries) GetActiveAnnotationsForBook(ctx context.Context, arg GetActiveAnnotationsForBookParams) ([]GetActiveAnnotationsForBookRow, error) { + rows, err := q.db.Query(ctx, GetActiveAnnotationsForBook, arg.MediaItemID, arg.UserID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetActiveAnnotationsForBookRow{} + for rows.Next() { + var i GetActiveAnnotationsForBookRow + if err := rows.Scan( + &i.ID, + &i.SelectionText, + &i.StartPosition, + &i.EndPosition, + &i.Color, + &i.CreatedAt, + &i.UpdatedAt, + &i.AnnotationType, + &i.PercentageStart, + &i.PercentageEnd, + &i.EpubcfiStart, + &i.EpubcfiEnd, + &i.NoteText, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetAllSystemConfig = `-- name: GetAllSystemConfig :many SELECT key, value, updated_at, updated_by FROM system_config ORDER BY key ` @@ -1878,7 +2273,7 @@ SELECT mh.epubcfi_start, mh.epubcfi_end FROM media_highlights mh -WHERE mh.media_item_id = $1 AND mh.user_id = $2 +WHERE mh.media_item_id = $1 AND mh.user_id = $2 AND COALESCE(mh.deleted, FALSE) = FALSE UNION ALL SELECT mn.id, @@ -1894,7 +2289,7 @@ SELECT mn.epubcfi_location as epubcfi_start, NULL as epubcfi_end FROM media_notes mn -WHERE mn.media_item_id = $1 AND mn.user_id = $2 +WHERE mn.media_item_id = $1 AND mn.user_id = $2 AND COALESCE(mn.deleted, FALSE) = FALSE ORDER BY created_at DESC ` @@ -4012,9 +4407,84 @@ func (q *Queries) GetLibraryWithType(ctx context.Context, id pgtype.UUID) (GetLi return i, err } +const GetMediaBookmark = `-- name: GetMediaBookmark :one +SELECT id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at FROM media_bookmarks WHERE id = $1 +` + +func (q *Queries) GetMediaBookmark(ctx context.Context, id pgtype.UUID) (MediaBookmarks, error) { + row := q.db.QueryRow(ctx, GetMediaBookmark, id) + var i MediaBookmarks + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.PageNumber, + &i.ChapterNumber, + &i.CfiPosition, + &i.Title, + &i.Position, + &i.Notes, + &i.CreatedAt, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.DeviceSyncData, + &i.PercentageLocation, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.Deleted, + &i.DeletedAt, + ) + return i, err +} + +const GetMediaBookmarkByDedupKey = `-- name: GetMediaBookmarkByDedupKey :one + +SELECT id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at FROM media_bookmarks +WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 +ORDER BY deleted ASC, deleted_at DESC NULLS LAST +LIMIT 1 +` + +type GetMediaBookmarkByDedupKeyParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` +} + +// ============================================ +// ANNOTATION SYNC QUERIES (bookmarks) +// ============================================ +func (q *Queries) GetMediaBookmarkByDedupKey(ctx context.Context, arg GetMediaBookmarkByDedupKeyParams) (MediaBookmarks, error) { + row := q.db.QueryRow(ctx, GetMediaBookmarkByDedupKey, arg.UserID, arg.MediaItemID, arg.DedupKey) + var i MediaBookmarks + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.PageNumber, + &i.ChapterNumber, + &i.CfiPosition, + &i.Title, + &i.Position, + &i.Notes, + &i.CreatedAt, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.DeviceSyncData, + &i.PercentageLocation, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.Deleted, + &i.DeletedAt, + ) + return i, err +} + const GetMediaBookmarks = `-- name: GetMediaBookmarks :many -SELECT id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at FROM media_bookmarks -WHERE media_item_id = $1 AND user_id = $2 +SELECT id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at FROM media_bookmarks +WHERE media_item_id = $1 AND user_id = $2 AND COALESCE(deleted, FALSE) = FALSE ORDER BY created_at DESC ` @@ -4043,6 +4513,15 @@ func (q *Queries) GetMediaBookmarks(ctx context.Context, arg GetMediaBookmarksPa &i.Position, &i.Notes, &i.CreatedAt, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.DeviceSyncData, + &i.PercentageLocation, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.Deleted, + &i.DeletedAt, ); err != nil { return nil, err } @@ -4055,7 +4534,7 @@ func (q *Queries) GetMediaBookmarks(ctx context.Context, arg GetMediaBookmarksPa } const GetMediaHighlight = `-- name: GetMediaHighlight :one -SELECT id, media_item_id, user_id, selection_text, start_position, end_position, color, note_id, created_at, updated_at, percentage_start, percentage_end, character_start, character_end, epubcfi_start, epubcfi_end, chapter_reference, paragraph_start, paragraph_end, panel_number, device_sync_data FROM media_highlights WHERE id = $1 +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, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at FROM media_highlights WHERE id = $1 ` func (q *Queries) GetMediaHighlight(ctx context.Context, id pgtype.UUID) (MediaHighlights, error) { @@ -4083,12 +4562,70 @@ func (q *Queries) GetMediaHighlight(ctx context.Context, id pgtype.UUID) (MediaH &i.ParagraphEnd, &i.PanelNumber, &i.DeviceSyncData, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.NoteText, + &i.Deleted, + &i.DeletedAt, + ) + return i, err +} + +const GetMediaHighlightByDedupKey = `-- name: GetMediaHighlightByDedupKey :one + +SELECT id, media_item_id, user_id, selection_text, start_position, end_position, color, note_id, created_at, updated_at, percentage_start, percentage_end, character_start, character_end, epubcfi_start, epubcfi_end, chapter_reference, paragraph_start, paragraph_end, panel_number, device_sync_data, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at FROM media_highlights +WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 +ORDER BY deleted ASC, deleted_at DESC NULLS LAST +LIMIT 1 +` + +type GetMediaHighlightByDedupKeyParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` +} + +// ============================================ +// ANNOTATION SYNC QUERIES (highlights) +// ============================================ +func (q *Queries) GetMediaHighlightByDedupKey(ctx context.Context, arg GetMediaHighlightByDedupKeyParams) (MediaHighlights, error) { + row := q.db.QueryRow(ctx, GetMediaHighlightByDedupKey, arg.UserID, arg.MediaItemID, arg.DedupKey) + var i MediaHighlights + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.SelectionText, + &i.StartPosition, + &i.EndPosition, + &i.Color, + &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, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.NoteText, + &i.Deleted, + &i.DeletedAt, ) 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, 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 +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, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at FROM media_highlights WHERE media_item_id = $1 AND user_id = $2 AND COALESCE(deleted, FALSE) = FALSE ORDER BY created_at DESC ` type GetMediaHighlightsParams struct { @@ -4127,6 +4664,12 @@ func (q *Queries) GetMediaHighlights(ctx context.Context, arg GetMediaHighlights &i.ParagraphEnd, &i.PanelNumber, &i.DeviceSyncData, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.NoteText, + &i.Deleted, + &i.DeletedAt, ); err != nil { return nil, err } @@ -4819,7 +5362,7 @@ func (q *Queries) GetMediaItemFormats(ctx context.Context, mediaItemID pgtype.UU } const GetMediaNote = `-- name: GetMediaNote :one -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 +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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at FROM media_notes WHERE id = $1 ` func (q *Queries) GetMediaNote(ctx context.Context, id pgtype.UUID) (MediaNotes, error) { @@ -4840,12 +5383,61 @@ func (q *Queries) GetMediaNote(ctx context.Context, id pgtype.UUID) (MediaNotes, &i.ChapterReference, &i.ParagraphReference, &i.DeviceSyncData, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.Deleted, + &i.DeletedAt, + ) + return i, err +} + +const GetMediaNoteByDedupKey = `-- name: GetMediaNoteByDedupKey :one + +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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at FROM media_notes +WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 +ORDER BY deleted ASC, deleted_at DESC NULLS LAST +LIMIT 1 +` + +type GetMediaNoteByDedupKeyParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` +} + +// ============================================ +// ANNOTATION SYNC QUERIES (notes) +// ============================================ +func (q *Queries) GetMediaNoteByDedupKey(ctx context.Context, arg GetMediaNoteByDedupKeyParams) (MediaNotes, error) { + row := q.db.QueryRow(ctx, GetMediaNoteByDedupKey, arg.UserID, arg.MediaItemID, arg.DedupKey) + var i MediaNotes + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.Content, + &i.Position, + &i.CreatedAt, + &i.UpdatedAt, + &i.PercentageLocation, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.ParagraphReference, + &i.DeviceSyncData, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.Deleted, + &i.DeletedAt, ) return i, err } const GetMediaNotes = `-- name: GetMediaNotes :many -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 +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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at FROM media_notes WHERE media_item_id = $1 AND user_id = $2 AND COALESCE(deleted, FALSE) = FALSE ORDER BY created_at DESC ` type GetMediaNotesParams struct { @@ -4877,6 +5469,11 @@ func (q *Queries) GetMediaNotes(ctx context.Context, arg GetMediaNotesParams) ([ &i.ChapterReference, &i.ParagraphReference, &i.DeviceSyncData, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.Deleted, + &i.DeletedAt, ); err != nil { return nil, err } @@ -6001,6 +6598,76 @@ func (q *Queries) GetSystemTimezone(ctx context.Context) (string, error) { return setting_value, err } +const GetTombstonedAnnotationsForBook = `-- name: GetTombstonedAnnotationsForBook :many +SELECT + mh.id, + mh.dedup_key, + 'highlight' as annotation_type, + mh.device_sync_data, + mh.deleted_at +FROM media_highlights mh +WHERE mh.media_item_id = $1 AND mh.user_id = $2 AND mh.deleted = TRUE AND mh.deleted_at > $3 +UNION ALL +SELECT + mn.id, + mn.dedup_key, + 'note' as annotation_type, + mn.device_sync_data, + mn.deleted_at +FROM media_notes mn +WHERE mn.media_item_id = $1 AND mn.user_id = $2 AND mn.deleted = TRUE AND mn.deleted_at > $3 +UNION ALL +SELECT + mb.id, + mb.dedup_key, + 'bookmark' as annotation_type, + mb.device_sync_data, + mb.deleted_at +FROM media_bookmarks mb +WHERE mb.media_item_id = $1 AND mb.user_id = $2 AND mb.deleted = TRUE AND mb.deleted_at > $3 +ORDER BY deleted_at DESC +` + +type GetTombstonedAnnotationsForBookParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + DeletedAt pgtype.Timestamptz `db:"deleted_at" json:"deleted_at"` +} + +type GetTombstonedAnnotationsForBookRow struct { + ID pgtype.UUID `db:"id" json:"id"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` + AnnotationType string `db:"annotation_type" json:"annotation_type"` + DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"` + DeletedAt pgtype.Timestamptz `db:"deleted_at" json:"deleted_at"` +} + +func (q *Queries) GetTombstonedAnnotationsForBook(ctx context.Context, arg GetTombstonedAnnotationsForBookParams) ([]GetTombstonedAnnotationsForBookRow, error) { + rows, err := q.db.Query(ctx, GetTombstonedAnnotationsForBook, arg.MediaItemID, arg.UserID, arg.DeletedAt) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetTombstonedAnnotationsForBookRow{} + for rows.Next() { + var i GetTombstonedAnnotationsForBookRow + if err := rows.Scan( + &i.ID, + &i.DedupKey, + &i.AnnotationType, + &i.DeviceSyncData, + &i.DeletedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetUniversalProgress = `-- name: GetUniversalProgress :one SELECT rp.id, @@ -8196,6 +8863,33 @@ func (q *Queries) ListUsers(ctx context.Context) ([]ListUsersRow, error) { return items, nil } +const PurgeExpiredBookmarkTombstones = `-- name: PurgeExpiredBookmarkTombstones :exec +DELETE FROM media_bookmarks WHERE deleted = TRUE AND deleted_at < $1 +` + +func (q *Queries) PurgeExpiredBookmarkTombstones(ctx context.Context, deletedAt pgtype.Timestamptz) error { + _, err := q.db.Exec(ctx, PurgeExpiredBookmarkTombstones, deletedAt) + return err +} + +const PurgeExpiredHighlightTombstones = `-- name: PurgeExpiredHighlightTombstones :exec +DELETE FROM media_highlights WHERE deleted = TRUE AND deleted_at < $1 +` + +func (q *Queries) PurgeExpiredHighlightTombstones(ctx context.Context, deletedAt pgtype.Timestamptz) error { + _, err := q.db.Exec(ctx, PurgeExpiredHighlightTombstones, deletedAt) + return err +} + +const PurgeExpiredNoteTombstones = `-- name: PurgeExpiredNoteTombstones :exec +DELETE FROM media_notes WHERE deleted = TRUE AND deleted_at < $1 +` + +func (q *Queries) PurgeExpiredNoteTombstones(ctx context.Context, deletedAt pgtype.Timestamptz) error { + _, err := q.db.Exec(ctx, PurgeExpiredNoteTombstones, deletedAt) + return err +} + const QueryMediaItemsByIdentifiers = `-- name: QueryMediaItemsByIdentifiers :many SELECT mi.id, @@ -9421,6 +10115,102 @@ func (q *Queries) SyncLibraryTypeExtensions(ctx context.Context, arg SyncLibrary return err } +const TombstoneMediaBookmarkByDedupKey = `-- name: TombstoneMediaBookmarkByDedupKey :exec +UPDATE media_bookmarks SET + deleted = TRUE, + deleted_at = NOW(), + last_modified_at = NOW() +WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 AND deleted = FALSE +` + +type TombstoneMediaBookmarkByDedupKeyParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` +} + +func (q *Queries) TombstoneMediaBookmarkByDedupKey(ctx context.Context, arg TombstoneMediaBookmarkByDedupKeyParams) error { + _, err := q.db.Exec(ctx, TombstoneMediaBookmarkByDedupKey, arg.UserID, arg.MediaItemID, arg.DedupKey) + return err +} + +const TombstoneMediaBookmarkByID = `-- name: TombstoneMediaBookmarkByID :exec +UPDATE media_bookmarks SET + deleted = TRUE, + deleted_at = NOW(), + last_modified_at = NOW() +WHERE id = $1 +` + +func (q *Queries) TombstoneMediaBookmarkByID(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, TombstoneMediaBookmarkByID, id) + return err +} + +const TombstoneMediaHighlightByDedupKey = `-- name: TombstoneMediaHighlightByDedupKey :exec +UPDATE media_highlights SET + deleted = TRUE, + deleted_at = NOW(), + last_modified_at = NOW() +WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 AND deleted = FALSE +` + +type TombstoneMediaHighlightByDedupKeyParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` +} + +func (q *Queries) TombstoneMediaHighlightByDedupKey(ctx context.Context, arg TombstoneMediaHighlightByDedupKeyParams) error { + _, err := q.db.Exec(ctx, TombstoneMediaHighlightByDedupKey, arg.UserID, arg.MediaItemID, arg.DedupKey) + return err +} + +const TombstoneMediaHighlightByID = `-- name: TombstoneMediaHighlightByID :exec +UPDATE media_highlights SET + deleted = TRUE, + deleted_at = NOW(), + last_modified_at = NOW() +WHERE id = $1 +` + +func (q *Queries) TombstoneMediaHighlightByID(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, TombstoneMediaHighlightByID, id) + return err +} + +const TombstoneMediaNoteByDedupKey = `-- name: TombstoneMediaNoteByDedupKey :exec +UPDATE media_notes SET + deleted = TRUE, + deleted_at = NOW(), + last_modified_at = NOW() +WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 AND deleted = FALSE +` + +type TombstoneMediaNoteByDedupKeyParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + DedupKey pgtype.Text `db:"dedup_key" json:"dedup_key"` +} + +func (q *Queries) TombstoneMediaNoteByDedupKey(ctx context.Context, arg TombstoneMediaNoteByDedupKeyParams) error { + _, err := q.db.Exec(ctx, TombstoneMediaNoteByDedupKey, arg.UserID, arg.MediaItemID, arg.DedupKey) + return err +} + +const TombstoneMediaNoteByID = `-- name: TombstoneMediaNoteByID :exec +UPDATE media_notes SET + deleted = TRUE, + deleted_at = NOW(), + last_modified_at = NOW() +WHERE id = $1 +` + +func (q *Queries) TombstoneMediaNoteByID(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, TombstoneMediaNoteByID, id) + return err +} + const UpdateCollection = `-- name: UpdateCollection :one UPDATE collections SET @@ -9942,7 +10732,7 @@ SET position = $4, updated_at = NOW() WHERE id = $1 AND user_id = $5 -RETURNING id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at +RETURNING id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at ` type UpdateMediaBookmarkParams struct { @@ -9973,6 +10763,91 @@ func (q *Queries) UpdateMediaBookmark(ctx context.Context, arg UpdateMediaBookma &i.Position, &i.Notes, &i.CreatedAt, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.DeviceSyncData, + &i.PercentageLocation, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.Deleted, + &i.DeletedAt, + ) + return i, err +} + +const UpdateMediaBookmarkForSync = `-- name: UpdateMediaBookmarkForSync :one +UPDATE media_bookmarks SET + page_number = $2, + chapter_number = $3, + cfi_position = $4, + title = $5, + position = $6, + notes = $7, + percentage_location = $8, + epubcfi_location = $9, + chapter_reference = $10, + last_modified_at = $11, + last_modified_source = $12, + device_sync_data = $13, + created_at = created_at +WHERE id = $1 +RETURNING id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at, dedup_key, last_modified_at, last_modified_source, device_sync_data, percentage_location, epubcfi_location, chapter_reference, deleted, deleted_at +` + +type UpdateMediaBookmarkForSyncParams struct { + ID pgtype.UUID `db:"id" json:"id"` + PageNumber pgtype.Int4 `db:"page_number" json:"page_number"` + ChapterNumber pgtype.Int4 `db:"chapter_number" json:"chapter_number"` + CfiPosition pgtype.Text `db:"cfi_position" json:"cfi_position"` + Title string `db:"title" json:"title"` + Position pgtype.Text `db:"position" json:"position"` + Notes pgtype.Text `db:"notes" json:"notes"` + PercentageLocation pgtype.Float8 `db:"percentage_location" json:"percentage_location"` + EpubcfiLocation pgtype.Text `db:"epubcfi_location" json:"epubcfi_location"` + ChapterReference pgtype.Int4 `db:"chapter_reference" json:"chapter_reference"` + LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"` + LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"` + DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"` +} + +func (q *Queries) UpdateMediaBookmarkForSync(ctx context.Context, arg UpdateMediaBookmarkForSyncParams) (MediaBookmarks, error) { + row := q.db.QueryRow(ctx, UpdateMediaBookmarkForSync, + arg.ID, + arg.PageNumber, + arg.ChapterNumber, + arg.CfiPosition, + arg.Title, + arg.Position, + arg.Notes, + arg.PercentageLocation, + arg.EpubcfiLocation, + arg.ChapterReference, + arg.LastModifiedAt, + arg.LastModifiedSource, + arg.DeviceSyncData, + ) + var i MediaBookmarks + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.PageNumber, + &i.ChapterNumber, + &i.CfiPosition, + &i.Title, + &i.Position, + &i.Notes, + &i.CreatedAt, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.DeviceSyncData, + &i.PercentageLocation, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.Deleted, + &i.DeletedAt, ) return i, err } @@ -9986,7 +10861,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, percentage_start, percentage_end, character_start, character_end, epubcfi_start, epubcfi_end, chapter_reference, paragraph_start, paragraph_end, panel_number, device_sync_data +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, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at ` type UpdateMediaHighlightParams struct { @@ -10030,6 +10905,99 @@ func (q *Queries) UpdateMediaHighlight(ctx context.Context, arg UpdateMediaHighl &i.ParagraphEnd, &i.PanelNumber, &i.DeviceSyncData, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.NoteText, + &i.Deleted, + &i.DeletedAt, + ) + return i, err +} + +const UpdateMediaHighlightForSync = `-- name: UpdateMediaHighlightForSync :one +UPDATE media_highlights SET + selection_text = $2, + start_position = $3, + end_position = $4, + color = $5, + note_text = $6, + percentage_start = $7, + percentage_end = $8, + epubcfi_start = $9, + epubcfi_end = $10, + chapter_reference = $11, + last_modified_at = $12, + last_modified_source = $13, + device_sync_data = $14, + 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, percentage_start, percentage_end, character_start, character_end, epubcfi_start, epubcfi_end, chapter_reference, paragraph_start, paragraph_end, panel_number, device_sync_data, dedup_key, last_modified_at, last_modified_source, note_text, deleted, deleted_at +` + +type UpdateMediaHighlightForSyncParams struct { + ID pgtype.UUID `db:"id" json:"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"` + NoteText pgtype.Text `db:"note_text" json:"note_text"` + PercentageStart pgtype.Float8 `db:"percentage_start" json:"percentage_start"` + PercentageEnd pgtype.Float8 `db:"percentage_end" json:"percentage_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"` + LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"` + LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"` + DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"` +} + +func (q *Queries) UpdateMediaHighlightForSync(ctx context.Context, arg UpdateMediaHighlightForSyncParams) (MediaHighlights, error) { + row := q.db.QueryRow(ctx, UpdateMediaHighlightForSync, + arg.ID, + arg.SelectionText, + arg.StartPosition, + arg.EndPosition, + arg.Color, + arg.NoteText, + arg.PercentageStart, + arg.PercentageEnd, + arg.EpubcfiStart, + arg.EpubcfiEnd, + arg.ChapterReference, + arg.LastModifiedAt, + arg.LastModifiedSource, + arg.DeviceSyncData, + ) + var i MediaHighlights + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.SelectionText, + &i.StartPosition, + &i.EndPosition, + &i.Color, + &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, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.NoteText, + &i.Deleted, + &i.DeletedAt, ) return i, err } @@ -10591,7 +11559,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, percentage_location, character_start, character_end, epubcfi_location, chapter_reference, paragraph_reference, device_sync_data +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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at ` type UpdateMediaNoteParams struct { @@ -10618,6 +11586,84 @@ func (q *Queries) UpdateMediaNote(ctx context.Context, arg UpdateMediaNoteParams &i.ChapterReference, &i.ParagraphReference, &i.DeviceSyncData, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.Deleted, + &i.DeletedAt, + ) + return i, err +} + +const UpdateMediaNoteForSync = `-- name: UpdateMediaNoteForSync :one +UPDATE media_notes SET + content = $2, + position = $3, + percentage_location = $4, + character_start = $5, + character_end = $6, + epubcfi_location = $7, + chapter_reference = $8, + paragraph_reference = $9, + last_modified_at = $10, + last_modified_source = $11, + device_sync_data = $12, + updated_at = NOW() +WHERE id = $1 +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, dedup_key, last_modified_at, last_modified_source, deleted, deleted_at +` + +type UpdateMediaNoteForSyncParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Content string `db:"content" json:"content"` + Position pgtype.Text `db:"position" json:"position"` + 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"` + LastModifiedAt pgtype.Timestamptz `db:"last_modified_at" json:"last_modified_at"` + LastModifiedSource pgtype.Text `db:"last_modified_source" json:"last_modified_source"` + DeviceSyncData []byte `db:"device_sync_data" json:"device_sync_data"` +} + +func (q *Queries) UpdateMediaNoteForSync(ctx context.Context, arg UpdateMediaNoteForSyncParams) (MediaNotes, error) { + row := q.db.QueryRow(ctx, UpdateMediaNoteForSync, + arg.ID, + arg.Content, + arg.Position, + arg.PercentageLocation, + arg.CharacterStart, + arg.CharacterEnd, + arg.EpubcfiLocation, + arg.ChapterReference, + arg.ParagraphReference, + arg.LastModifiedAt, + arg.LastModifiedSource, + arg.DeviceSyncData, + ) + var i MediaNotes + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.Content, + &i.Position, + &i.CreatedAt, + &i.UpdatedAt, + &i.PercentageLocation, + &i.CharacterStart, + &i.CharacterEnd, + &i.EpubcfiLocation, + &i.ChapterReference, + &i.ParagraphReference, + &i.DeviceSyncData, + &i.DedupKey, + &i.LastModifiedAt, + &i.LastModifiedSource, + &i.Deleted, + &i.DeletedAt, ) return i, err } diff --git a/internal/database/queries/queries.sql b/internal/database/queries/queries.sql index 55823de..b26d576 100644 --- a/internal/database/queries/queries.sql +++ b/internal/database/queries/queries.sql @@ -696,7 +696,7 @@ RETURNING *; SELECT * FROM media_notes WHERE id = $1; -- name: GetMediaNotes :many -SELECT * FROM media_notes WHERE media_item_id = $1 AND user_id = $2 ORDER BY created_at DESC; +SELECT * FROM media_notes WHERE media_item_id = $1 AND user_id = $2 AND COALESCE(deleted, FALSE) = FALSE ORDER BY created_at DESC; -- name: UpdateMediaNote :one UPDATE media_notes SET @@ -719,7 +719,7 @@ RETURNING *; SELECT * FROM media_highlights WHERE id = $1; -- name: GetMediaHighlights :many -SELECT * FROM media_highlights WHERE media_item_id = $1 AND user_id = $2 ORDER BY created_at DESC; +SELECT * FROM media_highlights WHERE media_item_id = $1 AND user_id = $2 AND COALESCE(deleted, FALSE) = FALSE ORDER BY created_at DESC; -- name: UpdateMediaHighlight :one UPDATE media_highlights SET @@ -735,6 +735,251 @@ RETURNING *; -- name: DeleteMediaHighlight :exec DELETE FROM media_highlights WHERE id = $1; +-- ============================================ +-- ANNOTATION SYNC QUERIES (highlights) +-- ============================================ + +-- name: GetMediaHighlightByDedupKey :one +SELECT * FROM media_highlights +WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 +ORDER BY deleted ASC, deleted_at DESC NULLS LAST +LIMIT 1; + +-- name: CreateMediaHighlightFull :one +INSERT INTO media_highlights ( + media_item_id, user_id, selection_text, + start_position, end_position, color, note_text, + percentage_start, percentage_end, + epubcfi_start, epubcfi_end, + chapter_reference, + dedup_key, last_modified_at, last_modified_source, + device_sync_data +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16 +) RETURNING *; + +-- name: UpdateMediaHighlightForSync :one +UPDATE media_highlights SET + selection_text = $2, + start_position = $3, + end_position = $4, + color = $5, + note_text = $6, + percentage_start = $7, + percentage_end = $8, + epubcfi_start = $9, + epubcfi_end = $10, + chapter_reference = $11, + last_modified_at = $12, + last_modified_source = $13, + device_sync_data = $14, + updated_at = NOW() +WHERE id = $1 +RETURNING *; + +-- name: TombstoneMediaHighlightByDedupKey :exec +UPDATE media_highlights SET + deleted = TRUE, + deleted_at = NOW(), + last_modified_at = NOW() +WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 AND deleted = FALSE; + +-- name: TombstoneMediaHighlightByID :exec +UPDATE media_highlights SET + deleted = TRUE, + deleted_at = NOW(), + last_modified_at = NOW() +WHERE id = $1; + +-- name: PurgeExpiredHighlightTombstones :exec +DELETE FROM media_highlights WHERE deleted = TRUE AND deleted_at < $1; + +-- ============================================ +-- ANNOTATION SYNC QUERIES (notes) +-- ============================================ + +-- name: GetMediaNoteByDedupKey :one +SELECT * FROM media_notes +WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 +ORDER BY deleted ASC, deleted_at DESC NULLS LAST +LIMIT 1; + +-- name: CreateMediaNoteFull :one +INSERT INTO media_notes ( + media_item_id, user_id, content, position, + percentage_location, character_start, character_end, + epubcfi_location, chapter_reference, paragraph_reference, + dedup_key, last_modified_at, last_modified_source, + device_sync_data +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14 +) RETURNING *; + +-- name: UpdateMediaNoteForSync :one +UPDATE media_notes SET + content = $2, + position = $3, + percentage_location = $4, + character_start = $5, + character_end = $6, + epubcfi_location = $7, + chapter_reference = $8, + paragraph_reference = $9, + last_modified_at = $10, + last_modified_source = $11, + device_sync_data = $12, + updated_at = NOW() +WHERE id = $1 +RETURNING *; + +-- name: TombstoneMediaNoteByDedupKey :exec +UPDATE media_notes SET + deleted = TRUE, + deleted_at = NOW(), + last_modified_at = NOW() +WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 AND deleted = FALSE; + +-- name: TombstoneMediaNoteByID :exec +UPDATE media_notes SET + deleted = TRUE, + deleted_at = NOW(), + last_modified_at = NOW() +WHERE id = $1; + +-- name: PurgeExpiredNoteTombstones :exec +DELETE FROM media_notes WHERE deleted = TRUE AND deleted_at < $1; + +-- ============================================ +-- ANNOTATION SYNC QUERIES (bookmarks) +-- ============================================ + +-- name: GetMediaBookmarkByDedupKey :one +SELECT * FROM media_bookmarks +WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 +ORDER BY deleted ASC, deleted_at DESC NULLS LAST +LIMIT 1; + +-- name: CreateMediaBookmarkFull :one +INSERT INTO media_bookmarks ( + media_item_id, user_id, page_number, chapter_number, + cfi_position, title, position, notes, + percentage_location, epubcfi_location, chapter_reference, + dedup_key, last_modified_at, last_modified_source, + device_sync_data +) VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 +) RETURNING *; + +-- name: UpdateMediaBookmarkForSync :one +UPDATE media_bookmarks SET + page_number = $2, + chapter_number = $3, + cfi_position = $4, + title = $5, + position = $6, + notes = $7, + percentage_location = $8, + epubcfi_location = $9, + chapter_reference = $10, + last_modified_at = $11, + last_modified_source = $12, + device_sync_data = $13, + created_at = created_at +WHERE id = $1 +RETURNING *; + +-- name: TombstoneMediaBookmarkByDedupKey :exec +UPDATE media_bookmarks SET + deleted = TRUE, + deleted_at = NOW(), + last_modified_at = NOW() +WHERE user_id = $1 AND media_item_id = $2 AND dedup_key = $3 AND deleted = FALSE; + +-- name: TombstoneMediaBookmarkByID :exec +UPDATE media_bookmarks SET + deleted = TRUE, + deleted_at = NOW(), + last_modified_at = NOW() +WHERE id = $1; + +-- name: PurgeExpiredBookmarkTombstones :exec +DELETE FROM media_bookmarks WHERE deleted = TRUE AND deleted_at < $1; + +-- ============================================ +-- ANNOTATION SERVE QUERIES +-- ============================================ + +-- name: GetActiveAnnotationsForBook :many +SELECT + mh.id, + mh.selection_text, + mh.start_position, + mh.end_position, + mh.color, + mh.created_at, + mh.updated_at, + 'highlight' as annotation_type, + mh.percentage_start, + mh.percentage_end, + mh.epubcfi_start, + mh.epubcfi_end, + mh.note_text, + mh.dedup_key, + mh.last_modified_at, + mh.last_modified_source +FROM media_highlights mh +WHERE mh.media_item_id = $1 AND mh.user_id = $2 AND mh.deleted = FALSE +UNION ALL +SELECT + mn.id, + mn.content, + mn.position, + NULL as end_position, + NULL as color, + mn.created_at, + mn.updated_at, + 'note' as annotation_type, + mn.percentage_location as percentage_start, + NULL as percentage_end, + mn.epubcfi_location as epubcfi_start, + NULL as epubcfi_end, + NULL as note_text, + mn.dedup_key, + mn.last_modified_at, + mn.last_modified_source +FROM media_notes mn +WHERE mn.media_item_id = $1 AND mn.user_id = $2 AND mn.deleted = FALSE +ORDER BY created_at DESC; + +-- name: GetTombstonedAnnotationsForBook :many +SELECT + mh.id, + mh.dedup_key, + 'highlight' as annotation_type, + mh.device_sync_data, + mh.deleted_at +FROM media_highlights mh +WHERE mh.media_item_id = $1 AND mh.user_id = $2 AND mh.deleted = TRUE AND mh.deleted_at > $3 +UNION ALL +SELECT + mn.id, + mn.dedup_key, + 'note' as annotation_type, + mn.device_sync_data, + mn.deleted_at +FROM media_notes mn +WHERE mn.media_item_id = $1 AND mn.user_id = $2 AND mn.deleted = TRUE AND mn.deleted_at > $3 +UNION ALL +SELECT + mb.id, + mb.dedup_key, + 'bookmark' as annotation_type, + mb.device_sync_data, + mb.deleted_at +FROM media_bookmarks mb +WHERE mb.media_item_id = $1 AND mb.user_id = $2 AND mb.deleted = TRUE AND mb.deleted_at > $3 +ORDER BY deleted_at DESC; + -- Refresh Tokens queries -- name: CreateRefreshToken :one INSERT INTO refresh_tokens (user_id, token, expires_at) @@ -1126,6 +1371,11 @@ INSERT INTO sync_conflicts (media_item_id, user_id, conflict_type, conflict_data VALUES ($1, $2, $3, $4) RETURNING *; +-- name: CreateAutoResolvedSyncConflict :one +INSERT INTO sync_conflicts (media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_at) +VALUES ($1, $2, $3, $4, 'auto_resolved', $5, NOW()) +RETURNING *; + -- name: GetSyncConflict :one SELECT * FROM sync_conflicts WHERE id = $1; @@ -1225,7 +1475,7 @@ SELECT mh.epubcfi_start, mh.epubcfi_end FROM media_highlights mh -WHERE mh.media_item_id = $1 AND mh.user_id = $2 +WHERE mh.media_item_id = $1 AND mh.user_id = $2 AND COALESCE(mh.deleted, FALSE) = FALSE UNION ALL SELECT mn.id, @@ -1241,7 +1491,7 @@ SELECT mn.epubcfi_location as epubcfi_start, NULL as epubcfi_end FROM media_notes mn -WHERE mn.media_item_id = $1 AND mn.user_id = $2 +WHERE mn.media_item_id = $1 AND mn.user_id = $2 AND COALESCE(mn.deleted, FALSE) = FALSE ORDER BY created_at DESC; -- name: UpdateDeviceSyncTimestamp :one @@ -2112,9 +2362,12 @@ RETURNING *; -- name: GetMediaBookmarks :many SELECT * FROM media_bookmarks -WHERE media_item_id = $1 AND user_id = $2 +WHERE media_item_id = $1 AND user_id = $2 AND COALESCE(deleted, FALSE) = FALSE ORDER BY created_at DESC; +-- name: GetMediaBookmark :one +SELECT * FROM media_bookmarks WHERE id = $1; + -- name: CreateMediaBookmark :one INSERT INTO media_bookmarks (media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)