diff --git a/internal/database/querier.go b/internal/database/querier.go index 3d2f438..3bd1a96 100644 --- a/internal/database/querier.go +++ b/internal/database/querier.go @@ -11,14 +11,19 @@ import ( ) type Querier interface { + // ============================================ + // KOBO SHELF MANAGEMENT QUERIES (Phase 4) + // ============================================ + AddBookToKoboShelf(ctx context.Context, arg AddBookToKoboShelfParams) (KoboShelves, error) // Library Folders queries AddLibraryFolder(ctx context.Context, arg AddLibraryFolderParams) (LibraryFolders, error) // Bulk update format group for all media items BulkUpdateFormatGroups(ctx context.Context) error BulkUpdateProgressFromSync(ctx context.Context, arg BulkUpdateProgressFromSyncParams) ([]interface{}, error) - CheckForProgressConflicts(ctx context.Context, arg CheckForProgressConflictsParams) (int64, error) CleanupExpiredRefreshTokens(ctx context.Context) error ClearDeviceSyncQueue(ctx context.Context, deviceID pgtype.UUID) error + ClearKoboShelf(ctx context.Context, deviceID pgtype.UUID) error + ClearKoboShelfByName(ctx context.Context, arg ClearKoboShelfByNameParams) error // ============================================ // PHASE 2: DEVICE MANAGEMENT & AUTH (Weeks 5-6) // ============================================ @@ -35,6 +40,10 @@ type Querier interface { // Media Notes queries CreateMediaNote(ctx context.Context, arg CreateMediaNoteParams) (MediaNotes, error) CreateMediaRating(ctx context.Context, arg CreateMediaRatingParams) (MediaRatings, error) + // ============================================ + // KOBO ENTITLEMENT QUERIES (Phase 4) + // ============================================ + CreateOrUpdateKoboEntitlement(ctx context.Context, arg CreateOrUpdateKoboEntitlementParams) (KoboEntitlements, error) // Create reading history entry CreateReadingHistory(ctx context.Context, arg CreateReadingHistoryParams) (ReadingHistory, error) // Refresh Tokens queries @@ -48,6 +57,7 @@ type Querier interface { DeleteDevice(ctx context.Context, id pgtype.UUID) error DeleteDeviceByToken(ctx context.Context, authToken string) error DeleteEbookNote(ctx context.Context, id pgtype.UUID) error + DeleteKoboEntitlement(ctx context.Context, arg DeleteKoboEntitlementParams) error DeleteLibrary(ctx context.Context, id pgtype.UUID) error DeleteLibraryFolder(ctx context.Context, arg DeleteLibraryFolderParams) (LibraryFolders, error) DeleteMediaHighlight(ctx context.Context, id pgtype.UUID) error @@ -58,10 +68,17 @@ type Querier interface { DeleteSyncConflict(ctx context.Context, id pgtype.UUID) error DeleteSyncQueueItem(ctx context.Context, id pgtype.UUID) error DeleteUser(ctx context.Context, id pgtype.UUID) error + GenerateKoboEntitlementId(ctx context.Context) (interface{}, error) GetAnnotationsForBook(ctx context.Context, arg GetAnnotationsForBookParams) ([]GetAnnotationsForBookRow, error) GetDevice(ctx context.Context, id pgtype.UUID) (Devices, error) GetDeviceByAuthToken(ctx context.Context, authToken string) (Devices, error) GetDeviceByIdentifier(ctx context.Context, deviceIdentifier string) (Devices, error) + GetKoboEntitlementByContentId(ctx context.Context, arg GetKoboEntitlementByContentIdParams) (GetKoboEntitlementByContentIdRow, error) + GetKoboEntitlementByEntitlementId(ctx context.Context, arg GetKoboEntitlementByEntitlementIdParams) (GetKoboEntitlementByEntitlementIdRow, error) + GetKoboEntitlementsForDevice(ctx context.Context, deviceID pgtype.UUID) ([]GetKoboEntitlementsForDeviceRow, error) + GetKoboShelfBookCount(ctx context.Context, deviceID pgtype.UUID) (int64, error) + GetKoboShelfBooks(ctx context.Context, deviceID pgtype.UUID) ([]GetKoboShelfBooksRow, error) + GetKoboShelfBooksByShelfName(ctx context.Context, arg GetKoboShelfBooksByShelfNameParams) ([]GetKoboShelfBooksByShelfNameRow, error) GetLibrary(ctx context.Context, id pgtype.UUID) (GetLibraryRow, error) GetLibraryByFolder(ctx context.Context, folderPath string) (GetLibraryByFolderRow, error) GetLibraryFolders(ctx context.Context, libraryID pgtype.UUID) ([]LibraryFolders, error) @@ -78,6 +95,7 @@ type Querier interface { // PHASE 3: KOREADER SYNC PROTOCOL (Weeks 7-9) // ============================================ GetMediaItemByFilePathForSync(ctx context.Context, filePath string) (MediaItems, error) + GetMediaItemByKoboContentId(ctx context.Context, koboContentID pgtype.Text) (MediaItems, error) GetMediaNote(ctx context.Context, id pgtype.UUID) (MediaNotes, error) GetMediaNotes(ctx context.Context, arg GetMediaNotesParams) ([]MediaNotes, error) GetMediaRating(ctx context.Context, arg GetMediaRatingParams) (MediaRatings, error) @@ -100,6 +118,7 @@ type Querier interface { GetUserPasswordHash(ctx context.Context, id pgtype.UUID) (string, error) GetUserProgressForBooks(ctx context.Context, arg GetUserProgressForBooksParams) ([]GetUserProgressForBooksRow, error) GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUID) ([]GetUserVisibleLibrariesRow, error) + IsBookOnKoboShelf(ctx context.Context, arg IsBookOnKoboShelfParams) (bool, error) ListDevicesByType(ctx context.Context, deviceType string) ([]Devices, error) ListDevicesByUser(ctx context.Context, userID pgtype.UUID) ([]Devices, error) ListLibraries(ctx context.Context) ([]ListLibrariesRow, error) @@ -111,6 +130,7 @@ type Querier interface { ListSyncConflictsByMediaItem(ctx context.Context, arg ListSyncConflictsByMediaItemParams) ([]SyncConflicts, error) ListSyncConflictsByUser(ctx context.Context, userID pgtype.UUID) ([]ListSyncConflictsByUserRow, error) ListUsers(ctx context.Context) ([]ListUsersRow, error) + RemoveBookFromKoboShelf(ctx context.Context, arg RemoveBookFromKoboShelfParams) error ResolveSyncConflict(ctx context.Context, arg ResolveSyncConflictParams) (SyncConflicts, error) RevokeAllUserRefreshTokens(ctx context.Context, userID pgtype.UUID) error RevokeDevice(ctx context.Context, id pgtype.UUID) error @@ -128,6 +148,9 @@ type Querier interface { UpdateDeviceSyncTimestamp(ctx context.Context, id pgtype.UUID) (Devices, error) UpdateEbookNote(ctx context.Context, arg UpdateEbookNoteParams) (MediaNotes, error) UpdateEmail(ctx context.Context, arg UpdateEmailParams) error + UpdateKoboEntitlementRevision(ctx context.Context, arg UpdateKoboEntitlementRevisionParams) error + UpdateKoboEntitlementStatus(ctx context.Context, arg UpdateKoboEntitlementStatusParams) error + UpdateKoboShelfBookPosition(ctx context.Context, arg UpdateKoboShelfBookPositionParams) error UpdateLibrary(ctx context.Context, arg UpdateLibraryParams) (Libraries, error) UpdateMediaHighlight(ctx context.Context, arg UpdateMediaHighlightParams) (MediaHighlights, error) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams) (MediaItems, error) @@ -136,6 +159,7 @@ type Querier interface { // ============================================ // Update media item format group information UpdateMediaItemFormatGroup(ctx context.Context, arg UpdateMediaItemFormatGroupParams) error + UpdateMediaItemKoboMetadata(ctx context.Context, arg UpdateMediaItemKoboMetadataParams) (MediaItems, error) UpdateMediaNote(ctx context.Context, arg UpdateMediaNoteParams) (MediaNotes, error) UpdateMediaRating(ctx context.Context, arg UpdateMediaRatingParams) (MediaRatings, error) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index 06ff65f..de66c69 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -11,6 +11,48 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +const AddBookToKoboShelf = `-- name: AddBookToKoboShelf :one + +INSERT INTO kobo_shelves (device_id, media_item_id, shelf_name, shelf_position) +VALUES ($1, $2, $3, $4) +ON CONFLICT (device_id, media_item_id) +DO UPDATE SET + shelf_name = EXCLUDED.shelf_name, + shelf_position = EXCLUDED.shelf_position, + last_synced_at = NOW() +RETURNING id, device_id, media_item_id, shelf_name, shelf_position, added_at, last_synced_at +` + +type AddBookToKoboShelfParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"` + ShelfPosition pgtype.Int4 `db:"shelf_position" json:"shelf_position"` +} + +// ============================================ +// KOBO SHELF MANAGEMENT QUERIES (Phase 4) +// ============================================ +func (q *Queries) AddBookToKoboShelf(ctx context.Context, arg AddBookToKoboShelfParams) (KoboShelves, error) { + row := q.db.QueryRow(ctx, AddBookToKoboShelf, + arg.DeviceID, + arg.MediaItemID, + arg.ShelfName, + arg.ShelfPosition, + ) + var i KoboShelves + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.ShelfName, + &i.ShelfPosition, + &i.AddedAt, + &i.LastSyncedAt, + ) + return i, err +} + const AddLibraryFolder = `-- name: AddLibraryFolder :one INSERT INTO library_folders (library_id, folder_path) VALUES ($1, $2) RETURNING id, library_id, folder_path, created_at ` @@ -79,28 +121,6 @@ func (q *Queries) BulkUpdateProgressFromSync(ctx context.Context, arg BulkUpdate return items, nil } -const CheckForProgressConflicts = `-- name: CheckForProgressConflicts :one -SELECT COUNT(*) as conflict_count -FROM reading_progress -WHERE media_item_id = $1 - AND user_id = $2 - AND last_sync_timestamp > NOW() - INTERVAL '5 minutes' - AND last_sync_source != $3 -` - -type CheckForProgressConflictsParams struct { - MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` - UserID pgtype.UUID `db:"user_id" json:"user_id"` - LastSyncSource pgtype.Text `db:"last_sync_source" json:"last_sync_source"` -} - -func (q *Queries) CheckForProgressConflicts(ctx context.Context, arg CheckForProgressConflictsParams) (int64, error) { - row := q.db.QueryRow(ctx, CheckForProgressConflicts, arg.MediaItemID, arg.UserID, arg.LastSyncSource) - var conflict_count int64 - err := row.Scan(&conflict_count) - return conflict_count, err -} - const CleanupExpiredRefreshTokens = `-- name: CleanupExpiredRefreshTokens :exec DELETE FROM refresh_tokens WHERE expires_at < NOW() OR (revoked_at IS NOT NULL AND revoked_at < NOW() - INTERVAL '7 days') ` @@ -119,6 +139,29 @@ func (q *Queries) ClearDeviceSyncQueue(ctx context.Context, deviceID pgtype.UUID return err } +const ClearKoboShelf = `-- name: ClearKoboShelf :exec +DELETE FROM kobo_shelves WHERE device_id = $1 +` + +func (q *Queries) ClearKoboShelf(ctx context.Context, deviceID pgtype.UUID) error { + _, err := q.db.Exec(ctx, ClearKoboShelf, deviceID) + return err +} + +const ClearKoboShelfByName = `-- name: ClearKoboShelfByName :exec +DELETE FROM kobo_shelves WHERE device_id = $1 AND shelf_name = $2 +` + +type ClearKoboShelfByNameParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"` +} + +func (q *Queries) ClearKoboShelfByName(ctx context.Context, arg ClearKoboShelfByNameParams) error { + _, err := q.db.Exec(ctx, ClearKoboShelfByName, arg.DeviceID, arg.ShelfName) + return err +} + const CreateDevice = `-- name: CreateDevice :one INSERT INTO devices (user_id, device_name, device_type, device_identifier, auth_token, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata) @@ -306,7 +349,7 @@ func (q *Queries) CreateMediaHighlight(ctx context.Context, arg CreateMediaHighl const CreateMediaItem = `-- name: CreateMediaItem :one INSERT INTO media_items (library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25) -RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count +RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata ` type CreateMediaItemParams struct { @@ -402,6 +445,10 @@ func (q *Queries) CreateMediaItem(ctx context.Context, arg CreateMediaItemParams &i.HasFixedLayout, &i.TotalCharacters, &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, ) return i, err } @@ -477,6 +524,64 @@ func (q *Queries) CreateMediaRating(ctx context.Context, arg CreateMediaRatingPa return i, err } +const CreateOrUpdateKoboEntitlement = `-- name: CreateOrUpdateKoboEntitlement :one + +INSERT INTO kobo_entitlements (device_id, media_item_id, entitlement_id, content_id, revision_number, purchase_date, kobo_metadata) +VALUES ($1, $2, $3, $4, $5, $6, $7) +ON CONFLICT (device_id, entitlement_id) +DO UPDATE SET + content_id = EXCLUDED.content_id, + revision_number = EXCLUDED.revision_number, + purchase_date = COALESCE(EXCLUDED.purchase_date, kobo_entitlements.purchase_date), + book_status = 'installed', + sync_status = 'synced', + kobo_metadata = EXCLUDED.kobo_metadata, + updated_at = NOW() +RETURNING id, device_id, media_item_id, entitlement_id, content_id, revision_number, purchase_date, accession_date, book_status, sync_status, kobo_metadata, created_at, updated_at +` + +type CreateOrUpdateKoboEntitlementParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + EntitlementID string `db:"entitlement_id" json:"entitlement_id"` + ContentID string `db:"content_id" json:"content_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` + PurchaseDate pgtype.Timestamptz `db:"purchase_date" json:"purchase_date"` + KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"` +} + +// ============================================ +// KOBO ENTITLEMENT QUERIES (Phase 4) +// ============================================ +func (q *Queries) CreateOrUpdateKoboEntitlement(ctx context.Context, arg CreateOrUpdateKoboEntitlementParams) (KoboEntitlements, error) { + row := q.db.QueryRow(ctx, CreateOrUpdateKoboEntitlement, + arg.DeviceID, + arg.MediaItemID, + arg.EntitlementID, + arg.ContentID, + arg.RevisionNumber, + arg.PurchaseDate, + arg.KoboMetadata, + ) + var i KoboEntitlements + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.EntitlementID, + &i.ContentID, + &i.RevisionNumber, + &i.PurchaseDate, + &i.AccessionDate, + &i.BookStatus, + &i.SyncStatus, + &i.KoboMetadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + const CreateReadingHistory = `-- name: CreateReadingHistory :one INSERT INTO reading_history ( user_id, @@ -756,6 +861,20 @@ func (q *Queries) DeleteEbookNote(ctx context.Context, id pgtype.UUID) error { return err } +const DeleteKoboEntitlement = `-- name: DeleteKoboEntitlement :exec +DELETE FROM kobo_entitlements WHERE device_id = $1 AND entitlement_id = $2 +` + +type DeleteKoboEntitlementParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + EntitlementID string `db:"entitlement_id" json:"entitlement_id"` +} + +func (q *Queries) DeleteKoboEntitlement(ctx context.Context, arg DeleteKoboEntitlementParams) error { + _, err := q.db.Exec(ctx, DeleteKoboEntitlement, arg.DeviceID, arg.EntitlementID) + return err +} + const DeleteLibrary = `-- name: DeleteLibrary :exec DELETE FROM libraries WHERE id = $1 ` @@ -868,6 +987,17 @@ func (q *Queries) DeleteUser(ctx context.Context, id pgtype.UUID) error { return err } +const GenerateKoboEntitlementId = `-- name: GenerateKoboEntitlementId :one +SELECT 'kobo_' || uuid_generate_v4()::TEXT as entitlement_id +` + +func (q *Queries) GenerateKoboEntitlementId(ctx context.Context) (interface{}, error) { + row := q.db.QueryRow(ctx, GenerateKoboEntitlementId) + var entitlement_id interface{} + err := row.Scan(&entitlement_id) + return entitlement_id, err +} + const GetAnnotationsForBook = `-- name: GetAnnotationsForBook :many SELECT mh.id, @@ -1034,6 +1164,318 @@ func (q *Queries) GetDeviceByIdentifier(ctx context.Context, deviceIdentifier st return i, err } +const GetKoboEntitlementByContentId = `-- name: GetKoboEntitlementByContentId :one +SELECT ke.id, ke.device_id, ke.media_item_id, ke.entitlement_id, ke.content_id, ke.revision_number, ke.purchase_date, ke.accession_date, ke.book_status, ke.sync_status, ke.kobo_metadata, ke.created_at, ke.updated_at, mi.title, mi.author, mi.file_path +FROM kobo_entitlements ke +JOIN media_items mi ON ke.media_item_id = mi.id +WHERE ke.device_id = $1 AND ke.content_id = $2 +` + +type GetKoboEntitlementByContentIdParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + ContentID string `db:"content_id" json:"content_id"` +} + +type GetKoboEntitlementByContentIdRow struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + EntitlementID string `db:"entitlement_id" json:"entitlement_id"` + ContentID string `db:"content_id" json:"content_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` + PurchaseDate pgtype.Timestamptz `db:"purchase_date" json:"purchase_date"` + AccessionDate pgtype.Timestamptz `db:"accession_date" json:"accession_date"` + BookStatus pgtype.Text `db:"book_status" json:"book_status"` + SyncStatus pgtype.Text `db:"sync_status" json:"sync_status"` + KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + FilePath string `db:"file_path" json:"file_path"` +} + +func (q *Queries) GetKoboEntitlementByContentId(ctx context.Context, arg GetKoboEntitlementByContentIdParams) (GetKoboEntitlementByContentIdRow, error) { + row := q.db.QueryRow(ctx, GetKoboEntitlementByContentId, arg.DeviceID, arg.ContentID) + var i GetKoboEntitlementByContentIdRow + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.EntitlementID, + &i.ContentID, + &i.RevisionNumber, + &i.PurchaseDate, + &i.AccessionDate, + &i.BookStatus, + &i.SyncStatus, + &i.KoboMetadata, + &i.CreatedAt, + &i.UpdatedAt, + &i.Title, + &i.Author, + &i.FilePath, + ) + return i, err +} + +const GetKoboEntitlementByEntitlementId = `-- name: GetKoboEntitlementByEntitlementId :one +SELECT ke.id, ke.device_id, ke.media_item_id, ke.entitlement_id, ke.content_id, ke.revision_number, ke.purchase_date, ke.accession_date, ke.book_status, ke.sync_status, ke.kobo_metadata, ke.created_at, ke.updated_at, mi.title, mi.author, mi.file_path +FROM kobo_entitlements ke +JOIN media_items mi ON ke.media_item_id = mi.id +WHERE ke.device_id = $1 AND ke.entitlement_id = $2 +` + +type GetKoboEntitlementByEntitlementIdParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + EntitlementID string `db:"entitlement_id" json:"entitlement_id"` +} + +type GetKoboEntitlementByEntitlementIdRow struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + EntitlementID string `db:"entitlement_id" json:"entitlement_id"` + ContentID string `db:"content_id" json:"content_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` + PurchaseDate pgtype.Timestamptz `db:"purchase_date" json:"purchase_date"` + AccessionDate pgtype.Timestamptz `db:"accession_date" json:"accession_date"` + BookStatus pgtype.Text `db:"book_status" json:"book_status"` + SyncStatus pgtype.Text `db:"sync_status" json:"sync_status"` + KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + FilePath string `db:"file_path" json:"file_path"` +} + +func (q *Queries) GetKoboEntitlementByEntitlementId(ctx context.Context, arg GetKoboEntitlementByEntitlementIdParams) (GetKoboEntitlementByEntitlementIdRow, error) { + row := q.db.QueryRow(ctx, GetKoboEntitlementByEntitlementId, arg.DeviceID, arg.EntitlementID) + var i GetKoboEntitlementByEntitlementIdRow + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.EntitlementID, + &i.ContentID, + &i.RevisionNumber, + &i.PurchaseDate, + &i.AccessionDate, + &i.BookStatus, + &i.SyncStatus, + &i.KoboMetadata, + &i.CreatedAt, + &i.UpdatedAt, + &i.Title, + &i.Author, + &i.FilePath, + ) + return i, err +} + +const GetKoboEntitlementsForDevice = `-- name: GetKoboEntitlementsForDevice :many +SELECT ke.id, ke.device_id, ke.media_item_id, ke.entitlement_id, ke.content_id, ke.revision_number, ke.purchase_date, ke.accession_date, ke.book_status, ke.sync_status, ke.kobo_metadata, ke.created_at, ke.updated_at, mi.title, mi.author, mi.file_path +FROM kobo_entitlements ke +JOIN media_items mi ON ke.media_item_id = mi.id +WHERE ke.device_id = $1 +ORDER BY ke.accession_date DESC +` + +type GetKoboEntitlementsForDeviceRow struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + EntitlementID string `db:"entitlement_id" json:"entitlement_id"` + ContentID string `db:"content_id" json:"content_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` + PurchaseDate pgtype.Timestamptz `db:"purchase_date" json:"purchase_date"` + AccessionDate pgtype.Timestamptz `db:"accession_date" json:"accession_date"` + BookStatus pgtype.Text `db:"book_status" json:"book_status"` + SyncStatus pgtype.Text `db:"sync_status" json:"sync_status"` + KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + FilePath string `db:"file_path" json:"file_path"` +} + +func (q *Queries) GetKoboEntitlementsForDevice(ctx context.Context, deviceID pgtype.UUID) ([]GetKoboEntitlementsForDeviceRow, error) { + rows, err := q.db.Query(ctx, GetKoboEntitlementsForDevice, deviceID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetKoboEntitlementsForDeviceRow{} + for rows.Next() { + var i GetKoboEntitlementsForDeviceRow + if err := rows.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.EntitlementID, + &i.ContentID, + &i.RevisionNumber, + &i.PurchaseDate, + &i.AccessionDate, + &i.BookStatus, + &i.SyncStatus, + &i.KoboMetadata, + &i.CreatedAt, + &i.UpdatedAt, + &i.Title, + &i.Author, + &i.FilePath, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetKoboShelfBookCount = `-- name: GetKoboShelfBookCount :one +SELECT COUNT(*) as book_count +FROM kobo_shelves +WHERE device_id = $1 +` + +func (q *Queries) GetKoboShelfBookCount(ctx context.Context, deviceID pgtype.UUID) (int64, error) { + row := q.db.QueryRow(ctx, GetKoboShelfBookCount, deviceID) + var book_count int64 + err := row.Scan(&book_count) + return book_count, err +} + +const GetKoboShelfBooks = `-- name: GetKoboShelfBooks :many +SELECT ks.id, ks.device_id, ks.media_item_id, ks.shelf_name, ks.shelf_position, ks.added_at, ks.last_synced_at, mi.title, mi.author, mi.file_path, mi.mime_type, mi.entitlement_id, mi.kobo_content_id, mi.revision_number +FROM kobo_shelves ks +JOIN media_items mi ON ks.media_item_id = mi.id +WHERE ks.device_id = $1 +ORDER BY ks.shelf_position ASC, ks.added_at ASC +` + +type GetKoboShelfBooksRow struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"` + ShelfPosition pgtype.Int4 `db:"shelf_position" json:"shelf_position"` + AddedAt pgtype.Timestamptz `db:"added_at" json:"added_at"` + LastSyncedAt pgtype.Timestamptz `db:"last_synced_at" json:"last_synced_at"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + FilePath string `db:"file_path" json:"file_path"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"` + KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` +} + +func (q *Queries) GetKoboShelfBooks(ctx context.Context, deviceID pgtype.UUID) ([]GetKoboShelfBooksRow, error) { + rows, err := q.db.Query(ctx, GetKoboShelfBooks, deviceID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetKoboShelfBooksRow{} + for rows.Next() { + var i GetKoboShelfBooksRow + if err := rows.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.ShelfName, + &i.ShelfPosition, + &i.AddedAt, + &i.LastSyncedAt, + &i.Title, + &i.Author, + &i.FilePath, + &i.MimeType, + &i.EntitlementID, + &i.KoboContentID, + &i.RevisionNumber, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetKoboShelfBooksByShelfName = `-- name: GetKoboShelfBooksByShelfName :many +SELECT ks.id, ks.device_id, ks.media_item_id, ks.shelf_name, ks.shelf_position, ks.added_at, ks.last_synced_at, mi.title, mi.author, mi.file_path, mi.mime_type, mi.entitlement_id, mi.kobo_content_id, mi.revision_number +FROM kobo_shelves ks +JOIN media_items mi ON ks.media_item_id = mi.id +WHERE ks.device_id = $1 AND ks.shelf_name = $2 +ORDER BY ks.shelf_position ASC, ks.added_at ASC +` + +type GetKoboShelfBooksByShelfNameParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"` +} + +type GetKoboShelfBooksByShelfNameRow struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + ShelfName pgtype.Text `db:"shelf_name" json:"shelf_name"` + ShelfPosition pgtype.Int4 `db:"shelf_position" json:"shelf_position"` + AddedAt pgtype.Timestamptz `db:"added_at" json:"added_at"` + LastSyncedAt pgtype.Timestamptz `db:"last_synced_at" json:"last_synced_at"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + FilePath string `db:"file_path" json:"file_path"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"` + KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` +} + +func (q *Queries) GetKoboShelfBooksByShelfName(ctx context.Context, arg GetKoboShelfBooksByShelfNameParams) ([]GetKoboShelfBooksByShelfNameRow, error) { + rows, err := q.db.Query(ctx, GetKoboShelfBooksByShelfName, arg.DeviceID, arg.ShelfName) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetKoboShelfBooksByShelfNameRow{} + for rows.Next() { + var i GetKoboShelfBooksByShelfNameRow + if err := rows.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.ShelfName, + &i.ShelfPosition, + &i.AddedAt, + &i.LastSyncedAt, + &i.Title, + &i.Author, + &i.FilePath, + &i.MimeType, + &i.EntitlementID, + &i.KoboContentID, + &i.RevisionNumber, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const GetLibrary = `-- name: GetLibrary :one SELECT l.id, l.name, l.description, l.library_type_id, l.created_by_admin_id, l.created_at, l.updated_at, lt.name as type_name, lt.description as type_description FROM libraries l @@ -1305,7 +1747,7 @@ func (q *Queries) GetMediaHighlights(ctx context.Context, arg GetMediaHighlights } const GetMediaItem = `-- name: GetMediaItem :one -SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count FROM media_items WHERE id = $1 +SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata FROM media_items WHERE id = $1 ` func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, error) { @@ -1346,12 +1788,16 @@ func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, &i.HasFixedLayout, &i.TotalCharacters, &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, ) return i, err } const GetMediaItemByFilePath = `-- name: GetMediaItemByFilePath :one -SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count FROM media_items WHERE file_path = $1 +SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata FROM media_items WHERE file_path = $1 ` func (q *Queries) GetMediaItemByFilePath(ctx context.Context, filePath string) (MediaItems, error) { @@ -1392,13 +1838,17 @@ func (q *Queries) GetMediaItemByFilePath(ctx context.Context, filePath string) ( &i.HasFixedLayout, &i.TotalCharacters, &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, ) return i, err } const GetMediaItemByFilePathForSync = `-- name: GetMediaItemByFilePathForSync :one -SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count FROM media_items WHERE file_path = $1 +SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata FROM media_items WHERE file_path = $1 ` // ============================================ @@ -1442,6 +1892,60 @@ func (q *Queries) GetMediaItemByFilePathForSync(ctx context.Context, filePath st &i.HasFixedLayout, &i.TotalCharacters, &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, + ) + return i, err +} + +const GetMediaItemByKoboContentId = `-- name: GetMediaItemByKoboContentId :one +SELECT id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata FROM media_items WHERE kobo_content_id = $1 +` + +func (q *Queries) GetMediaItemByKoboContentId(ctx context.Context, koboContentID pgtype.Text) (MediaItems, error) { + row := q.db.QueryRow(ctx, GetMediaItemByKoboContentId, koboContentID) + var i MediaItems + err := row.Scan( + &i.ID, + &i.LibraryID, + &i.Title, + &i.Author, + &i.Isbn, + &i.Description, + &i.FilePath, + &i.FileSize, + &i.MimeType, + &i.CoverImagePath, + &i.Series, + &i.SeriesNumber, + &i.Tags, + &i.Asin, + &i.DatePublished, + &i.Publisher, + &i.Contributors, + &i.Language, + &i.Edition, + &i.PageCount, + &i.Genre, + &i.CopyrightYear, + &i.GoodreadsID, + &i.OpenlibraryID, + &i.GoogleBooksID, + &i.AddedByAdminID, + &i.CreatedAt, + &i.UpdatedAt, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, ) return i, err } @@ -2058,13 +2562,16 @@ SELECT mi.page_count, mi.format_group, mi.total_characters, - mi.chapter_count + mi.chapter_count, + mi.entitlement_id, + mi.revision_number, + mi.file_size FROM media_items mi JOIN library_visibility lv ON mi.library_id = lv.library_id -WHERE lv.user_id = $1 + WHERE lv.user_id = $1 AND lv.is_visible = true -ORDER BY mi.title ASC -LIMIT 1000 + ORDER BY mi.title ASC + LIMIT 1000 ` type GetUserMediaItemsForSyncRow struct { @@ -2077,6 +2584,9 @@ type GetUserMediaItemsForSyncRow struct { FormatGroup string `db:"format_group" json:"format_group"` TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` + FileSize pgtype.Int8 `db:"file_size" json:"file_size"` } func (q *Queries) GetUserMediaItemsForSync(ctx context.Context, userID pgtype.UUID) ([]GetUserMediaItemsForSyncRow, error) { @@ -2098,6 +2608,9 @@ func (q *Queries) GetUserMediaItemsForSync(ctx context.Context, userID pgtype.UU &i.FormatGroup, &i.TotalCharacters, &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.FileSize, ); err != nil { return nil, err } @@ -2257,6 +2770,25 @@ func (q *Queries) GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUI return items, nil } +const IsBookOnKoboShelf = `-- name: IsBookOnKoboShelf :one +SELECT EXISTS( + SELECT 1 FROM kobo_shelves + WHERE device_id = $1 AND media_item_id = $2 +) as on_shelf +` + +type IsBookOnKoboShelfParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` +} + +func (q *Queries) IsBookOnKoboShelf(ctx context.Context, arg IsBookOnKoboShelfParams) (bool, error) { + row := q.db.QueryRow(ctx, IsBookOnKoboShelf, arg.DeviceID, arg.MediaItemID) + var on_shelf bool + err := row.Scan(&on_shelf) + return on_shelf, err +} + const ListDevicesByType = `-- name: ListDevicesByType :many SELECT id, user_id, device_name, device_type, device_identifier, auth_token, last_sync, last_seen, sync_enabled, auto_sync, sync_frequency_minutes, device_metadata, created_at, updated_at FROM devices WHERE device_type = $1 ORDER BY created_at DESC ` @@ -2385,7 +2917,7 @@ func (q *Queries) ListLibraries(ctx context.Context) ([]ListLibrariesRow, error) } const ListMediaItems = `-- name: ListMediaItems :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, l.name as library_name, lt.name as library_type_name +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, l.name as library_name, lt.name as library_type_name FROM media_items mi JOIN libraries l ON mi.library_id = l.id JOIN library_types lt ON l.library_type_id = lt.id @@ -2432,6 +2964,10 @@ type ListMediaItemsRow struct { HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` + KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"` + KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"` LibraryName string `db:"library_name" json:"library_name"` LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } @@ -2480,6 +3016,10 @@ func (q *Queries) ListMediaItems(ctx context.Context, arg ListMediaItemsParams) &i.HasFixedLayout, &i.TotalCharacters, &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, &i.LibraryName, &i.LibraryTypeName, ); err != nil { @@ -2494,7 +3034,7 @@ func (q *Queries) ListMediaItems(ctx context.Context, arg ListMediaItemsParams) } const ListMediaItemsByLibrary = `-- name: ListMediaItemsByLibrary :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, l.name as library_name, lt.name as library_type_name +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, l.name as library_name, lt.name as library_type_name FROM media_items mi JOIN libraries l ON mi.library_id = l.id JOIN library_types lt ON l.library_type_id = lt.id @@ -2537,6 +3077,10 @@ type ListMediaItemsByLibraryRow struct { HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` + KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"` + KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"` LibraryName string `db:"library_name" json:"library_name"` LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } @@ -2585,6 +3129,10 @@ func (q *Queries) ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype. &i.HasFixedLayout, &i.TotalCharacters, &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, &i.LibraryName, &i.LibraryTypeName, ); err != nil { @@ -2599,7 +3147,7 @@ func (q *Queries) ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype. } const ListMediaItemsFiltered = `-- name: ListMediaItemsFiltered :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, l.name as library_name, lt.name as library_type_name +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, l.name as library_name, lt.name as library_type_name FROM media_items mi JOIN libraries l ON mi.library_id = l.id JOIN library_types lt ON l.library_type_id = lt.id @@ -2692,6 +3240,10 @@ type ListMediaItemsFilteredRow struct { HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` + KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"` + KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"` LibraryName string `db:"library_name" json:"library_name"` LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } @@ -2753,6 +3305,10 @@ func (q *Queries) ListMediaItemsFiltered(ctx context.Context, arg ListMediaItems &i.HasFixedLayout, &i.TotalCharacters, &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, &i.LibraryName, &i.LibraryTypeName, ); err != nil { @@ -2767,7 +3323,7 @@ func (q *Queries) ListMediaItemsFiltered(ctx context.Context, arg ListMediaItems } const ListMediaItemsSorted = `-- name: ListMediaItemsSorted :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, l.name as library_name, lt.name as library_type_name +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, l.name as library_name, lt.name as library_type_name FROM media_items mi JOIN libraries l ON mi.library_id = l.id JOIN library_types lt ON l.library_type_id = lt.id @@ -2883,6 +3439,10 @@ type ListMediaItemsSortedRow struct { HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` + KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"` + KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"` LibraryName string `db:"library_name" json:"library_name"` LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } @@ -2936,6 +3496,10 @@ func (q *Queries) ListMediaItemsSorted(ctx context.Context, arg ListMediaItemsSo &i.HasFixedLayout, &i.TotalCharacters, &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, &i.LibraryName, &i.LibraryTypeName, ); err != nil { @@ -3138,6 +3702,20 @@ func (q *Queries) ListUsers(ctx context.Context) ([]ListUsersRow, error) { return items, nil } +const RemoveBookFromKoboShelf = `-- name: RemoveBookFromKoboShelf :exec +DELETE FROM kobo_shelves WHERE device_id = $1 AND media_item_id = $2 +` + +type RemoveBookFromKoboShelfParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` +} + +func (q *Queries) RemoveBookFromKoboShelf(ctx context.Context, arg RemoveBookFromKoboShelfParams) error { + _, err := q.db.Exec(ctx, RemoveBookFromKoboShelf, arg.DeviceID, arg.MediaItemID) + return err +} + const ResolveSyncConflict = `-- name: ResolveSyncConflict :one UPDATE sync_conflicts SET @@ -3213,7 +3791,7 @@ func (q *Queries) RevokeRefreshToken(ctx context.Context, token string) error { const SearchMediaItems = `-- name: SearchMediaItems :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, l.name as library_name, lt.name as library_type_name +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, l.name as library_name, lt.name as library_type_name FROM media_items mi JOIN libraries l ON mi.library_id = l.id JOIN library_types lt ON l.library_type_id = lt.id @@ -3280,6 +3858,10 @@ type SearchMediaItemsRow struct { HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` + KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"` + KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"` LibraryName string `db:"library_name" json:"library_name"` LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } @@ -3336,6 +3918,10 @@ func (q *Queries) SearchMediaItems(ctx context.Context, arg SearchMediaItemsPara &i.HasFixedLayout, &i.TotalCharacters, &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, &i.LibraryName, &i.LibraryTypeName, ); err != nil { @@ -3350,7 +3936,7 @@ func (q *Queries) SearchMediaItems(ctx context.Context, arg SearchMediaItemsPara } const SearchMediaItemsFuzzy = `-- name: SearchMediaItemsFuzzy :many -SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, l.name as library_name, lt.name as library_type_name +SELECT mi.id, mi.library_id, mi.title, mi.author, mi.isbn, mi.description, mi.file_path, mi.file_size, mi.mime_type, mi.cover_image_path, mi.series, mi.series_number, mi.tags, mi.asin, mi.date_published, mi.publisher, mi.contributors, mi.language, mi.edition, mi.page_count, mi.genre, mi.copyright_year, mi.goodreads_id, mi.openlibrary_id, mi.google_books_id, mi.added_by_admin_id, mi.created_at, mi.updated_at, mi.format_group, mi.format_mimetype, mi.is_reflowable, mi.has_fixed_layout, mi.total_characters, mi.chapter_count, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, l.name as library_name, lt.name as library_type_name FROM media_items mi JOIN libraries l ON mi.library_id = l.id JOIN library_types lt ON l.library_type_id = lt.id @@ -3417,6 +4003,10 @@ type SearchMediaItemsFuzzyRow struct { HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` + KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"` + KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"` LibraryName string `db:"library_name" json:"library_name"` LibraryTypeName string `db:"library_type_name" json:"library_type_name"` } @@ -3470,6 +4060,10 @@ func (q *Queries) SearchMediaItemsFuzzy(ctx context.Context, arg SearchMediaItem &i.HasFixedLayout, &i.TotalCharacters, &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, &i.LibraryName, &i.LibraryTypeName, ); err != nil { @@ -3711,6 +4305,57 @@ func (q *Queries) UpdateEmail(ctx context.Context, arg UpdateEmailParams) error return err } +const UpdateKoboEntitlementRevision = `-- name: UpdateKoboEntitlementRevision :exec +UPDATE kobo_entitlements +SET revision_number = $3, updated_at = NOW() +WHERE device_id = $1 AND entitlement_id = $2 +` + +type UpdateKoboEntitlementRevisionParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + EntitlementID string `db:"entitlement_id" json:"entitlement_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` +} + +func (q *Queries) UpdateKoboEntitlementRevision(ctx context.Context, arg UpdateKoboEntitlementRevisionParams) error { + _, err := q.db.Exec(ctx, UpdateKoboEntitlementRevision, arg.DeviceID, arg.EntitlementID, arg.RevisionNumber) + return err +} + +const UpdateKoboEntitlementStatus = `-- name: UpdateKoboEntitlementStatus :exec +UPDATE kobo_entitlements +SET book_status = $3, sync_status = 'synced', updated_at = NOW() +WHERE device_id = $1 AND entitlement_id = $2 +` + +type UpdateKoboEntitlementStatusParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + EntitlementID string `db:"entitlement_id" json:"entitlement_id"` + BookStatus pgtype.Text `db:"book_status" json:"book_status"` +} + +func (q *Queries) UpdateKoboEntitlementStatus(ctx context.Context, arg UpdateKoboEntitlementStatusParams) error { + _, err := q.db.Exec(ctx, UpdateKoboEntitlementStatus, arg.DeviceID, arg.EntitlementID, arg.BookStatus) + return err +} + +const UpdateKoboShelfBookPosition = `-- name: UpdateKoboShelfBookPosition :exec +UPDATE kobo_shelves +SET shelf_position = $3, last_synced_at = NOW() +WHERE device_id = $1 AND media_item_id = $2 +` + +type UpdateKoboShelfBookPositionParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + ShelfPosition pgtype.Int4 `db:"shelf_position" json:"shelf_position"` +} + +func (q *Queries) UpdateKoboShelfBookPosition(ctx context.Context, arg UpdateKoboShelfBookPositionParams) error { + _, err := q.db.Exec(ctx, UpdateKoboShelfBookPosition, arg.DeviceID, arg.MediaItemID, arg.ShelfPosition) + return err +} + const UpdateLibrary = `-- name: UpdateLibrary :one UPDATE libraries SET name = $2, @@ -3822,7 +4467,7 @@ UPDATE media_items SET google_books_id = $21, updated_at = NOW() WHERE id = $1 -RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count +RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata ` type UpdateMediaItemParams struct { @@ -3909,6 +4554,10 @@ func (q *Queries) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams &i.HasFixedLayout, &i.TotalCharacters, &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, ) return i, err } @@ -3954,6 +4603,77 @@ func (q *Queries) UpdateMediaItemFormatGroup(ctx context.Context, arg UpdateMedi return err } +const UpdateMediaItemKoboMetadata = `-- name: UpdateMediaItemKoboMetadata :one +UPDATE media_items +SET entitlement_id = $2, + kobo_content_id = $3, + revision_number = COALESCE($4, revision_number) + 1, + kobo_metadata = $5, + updated_at = NOW() +WHERE id = $1 +RETURNING id, library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, asin, date_published, publisher, contributors, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at, updated_at, format_group, format_mimetype, is_reflowable, has_fixed_layout, total_characters, chapter_count, entitlement_id, revision_number, kobo_content_id, kobo_metadata +` + +type UpdateMediaItemKoboMetadataParams struct { + ID pgtype.UUID `db:"id" json:"id"` + EntitlementID pgtype.Text `db:"entitlement_id" json:"entitlement_id"` + KoboContentID pgtype.Text `db:"kobo_content_id" json:"kobo_content_id"` + RevisionNumber pgtype.Int4 `db:"revision_number" json:"revision_number"` + KoboMetadata []byte `db:"kobo_metadata" json:"kobo_metadata"` +} + +func (q *Queries) UpdateMediaItemKoboMetadata(ctx context.Context, arg UpdateMediaItemKoboMetadataParams) (MediaItems, error) { + row := q.db.QueryRow(ctx, UpdateMediaItemKoboMetadata, + arg.ID, + arg.EntitlementID, + arg.KoboContentID, + arg.RevisionNumber, + arg.KoboMetadata, + ) + var i MediaItems + err := row.Scan( + &i.ID, + &i.LibraryID, + &i.Title, + &i.Author, + &i.Isbn, + &i.Description, + &i.FilePath, + &i.FileSize, + &i.MimeType, + &i.CoverImagePath, + &i.Series, + &i.SeriesNumber, + &i.Tags, + &i.Asin, + &i.DatePublished, + &i.Publisher, + &i.Contributors, + &i.Language, + &i.Edition, + &i.PageCount, + &i.Genre, + &i.CopyrightYear, + &i.GoodreadsID, + &i.OpenlibraryID, + &i.GoogleBooksID, + &i.AddedByAdminID, + &i.CreatedAt, + &i.UpdatedAt, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.KoboContentID, + &i.KoboMetadata, + ) + return i, err +} + const UpdateMediaNote = `-- name: UpdateMediaNote :one UPDATE media_notes SET content = $2, diff --git a/internal/database/queries/queries.sql b/internal/database/queries/queries.sql index ea28195..da8dd31 100644 --- a/internal/database/queries/queries.sql +++ b/internal/database/queries/queries.sql @@ -845,15 +845,18 @@ SELECT mi.page_count, mi.format_group, mi.total_characters, - mi.chapter_count + mi.chapter_count, + mi.entitlement_id, + mi.revision_number, + mi.file_size FROM media_items mi JOIN library_visibility lv ON mi.library_id = lv.library_id -WHERE lv.user_id = $1 + WHERE lv.user_id = $1 AND lv.is_visible = true -ORDER BY mi.title ASC -LIMIT 1000; + ORDER BY mi.title ASC + LIMIT 1000; --- name: CheckForProgressConflicts :one + -- name: CheckForProgressConflicts :one SELECT COUNT(*) as conflict_count FROM reading_progress WHERE media_item_id = $1 @@ -861,4 +864,123 @@ WHERE media_item_id = $1 AND last_sync_timestamp > NOW() - INTERVAL '5 minutes' AND last_sync_source != $3; - -- Media Items Admin Operations \ No newline at end of file +-- ============================================ +-- KOBO SHELF MANAGEMENT QUERIES (Phase 4) +-- ============================================ + +-- name: AddBookToKoboShelf :one +INSERT INTO kobo_shelves (device_id, media_item_id, shelf_name, shelf_position) +VALUES ($1, $2, $3, $4) +ON CONFLICT (device_id, media_item_id) +DO UPDATE SET + shelf_name = EXCLUDED.shelf_name, + shelf_position = EXCLUDED.shelf_position, + last_synced_at = NOW() +RETURNING *; + +-- name: RemoveBookFromKoboShelf :exec +DELETE FROM kobo_shelves WHERE device_id = $1 AND media_item_id = $2; + +-- name: GetKoboShelfBooks :many +SELECT ks.*, mi.title, mi.author, mi.file_path, mi.mime_type, mi.entitlement_id, mi.kobo_content_id, mi.revision_number +FROM kobo_shelves ks +JOIN media_items mi ON ks.media_item_id = mi.id +WHERE ks.device_id = $1 +ORDER BY ks.shelf_position ASC, ks.added_at ASC; + +-- name: GetKoboShelfBooksByShelfName :many +SELECT ks.*, mi.title, mi.author, mi.file_path, mi.mime_type, mi.entitlement_id, mi.kobo_content_id, mi.revision_number +FROM kobo_shelves ks +JOIN media_items mi ON ks.media_item_id = mi.id +WHERE ks.device_id = $1 AND ks.shelf_name = $2 +ORDER BY ks.shelf_position ASC, ks.added_at ASC; + +-- name: UpdateKoboShelfBookPosition :exec +UPDATE kobo_shelves +SET shelf_position = $3, last_synced_at = NOW() +WHERE device_id = $1 AND media_item_id = $2; + +-- name: ClearKoboShelf :exec +DELETE FROM kobo_shelves WHERE device_id = $1; + +-- name: ClearKoboShelfByName :exec +DELETE FROM kobo_shelves WHERE device_id = $1 AND shelf_name = $2; + +-- name: IsBookOnKoboShelf :one +SELECT EXISTS( + SELECT 1 FROM kobo_shelves + WHERE device_id = $1 AND media_item_id = $2 +) as on_shelf; + +-- name: GetKoboShelfBookCount :one +SELECT COUNT(*) as book_count +FROM kobo_shelves +WHERE device_id = $1; + +-- ============================================ +-- KOBO ENTITLEMENT QUERIES (Phase 4) +-- ============================================ + +-- name: CreateOrUpdateKoboEntitlement :one +INSERT INTO kobo_entitlements (device_id, media_item_id, entitlement_id, content_id, revision_number, purchase_date, kobo_metadata) +VALUES ($1, $2, $3, $4, $5, $6, $7) +ON CONFLICT (device_id, entitlement_id) +DO UPDATE SET + content_id = EXCLUDED.content_id, + revision_number = EXCLUDED.revision_number, + purchase_date = COALESCE(EXCLUDED.purchase_date, kobo_entitlements.purchase_date), + book_status = 'installed', + sync_status = 'synced', + kobo_metadata = EXCLUDED.kobo_metadata, + updated_at = NOW() +RETURNING *; + +-- name: GetKoboEntitlementsForDevice :many +SELECT ke.*, mi.title, mi.author, mi.file_path +FROM kobo_entitlements ke +JOIN media_items mi ON ke.media_item_id = mi.id +WHERE ke.device_id = $1 +ORDER BY ke.accession_date DESC; + +-- name: GetKoboEntitlementByContentId :one +SELECT ke.*, mi.title, mi.author, mi.file_path +FROM kobo_entitlements ke +JOIN media_items mi ON ke.media_item_id = mi.id +WHERE ke.device_id = $1 AND ke.content_id = $2; + +-- name: GetKoboEntitlementByEntitlementId :one +SELECT ke.*, mi.title, mi.author, mi.file_path +FROM kobo_entitlements ke +JOIN media_items mi ON ke.media_item_id = mi.id +WHERE ke.device_id = $1 AND ke.entitlement_id = $2; + +-- name: UpdateKoboEntitlementStatus :exec +UPDATE kobo_entitlements +SET book_status = $3, sync_status = 'synced', updated_at = NOW() +WHERE device_id = $1 AND entitlement_id = $2; + +-- name: UpdateKoboEntitlementRevision :exec +UPDATE kobo_entitlements +SET revision_number = $3, updated_at = NOW() +WHERE device_id = $1 AND entitlement_id = $2; + +-- name: DeleteKoboEntitlement :exec +DELETE FROM kobo_entitlements WHERE device_id = $1 AND entitlement_id = $2; + +-- name: GenerateKoboEntitlementId :one +SELECT 'kobo_' || uuid_generate_v4()::TEXT as entitlement_id; + +-- name: UpdateMediaItemKoboMetadata :one +UPDATE media_items +SET entitlement_id = $2, + kobo_content_id = $3, + revision_number = COALESCE($4, revision_number) + 1, + kobo_metadata = $5, + updated_at = NOW() +WHERE id = $1 +RETURNING *; + +-- name: GetMediaItemByKoboContentId :one +SELECT * FROM media_items WHERE kobo_content_id = $1; + + -- Media Items Admin Operations \ No newline at end of file