diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index fc01487..f0c5414 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -6931,7 +6931,7 @@ JOIN libraries l ON mi.library_id = l.id JOIN library_types lt ON l.library_type_id = lt.id LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $1 WHERE COALESCE(lv.is_visible, true) = true - AND ($2 IS NULL OR mi.library_id = $2) + AND ($2::uuid IS NULL OR mi.library_id = $2::uuid) AND ( mi.title ILIKE $3 OR mi.author ILIKE $3 OR @@ -6953,7 +6953,7 @@ LIMIT $5 OFFSET $4 type SearchMediaItemsParams struct { UserID pgtype.UUID `db:"user_id" json:"user_id"` - LibraryID interface{} `db:"library_id" json:"library_id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` SearchPattern pgtype.Text `db:"search_pattern" json:"search_pattern"` Offset pgtype.Int4 `db:"offset" json:"offset"` Limit pgtype.Int4 `db:"limit" json:"limit"` diff --git a/internal/database/queries.sql.go.backup b/internal/database/queries.sql.go.backup new file mode 100644 index 0000000..fc01487 --- /dev/null +++ b/internal/database/queries.sql.go.backup @@ -0,0 +1,8689 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: queries.sql + +package database + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const AddBookToCollection = `-- name: AddBookToCollection :one + +INSERT INTO collection_items (collection_id, media_item_id, added_by_user_id) +VALUES ($1, $2, $3) +ON CONFLICT (collection_id, media_item_id) DO NOTHING +RETURNING id, collection_id, media_item_id, added_at, added_by_user_id, excluded +` + +type AddBookToCollectionParams struct { + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + AddedByUserID pgtype.UUID `db:"added_by_user_id" json:"added_by_user_id"` +} + +// COLLECTION ITEMS QUERIES +// Add book to collection +func (q *Queries) AddBookToCollection(ctx context.Context, arg AddBookToCollectionParams) (CollectionItems, error) { + row := q.db.QueryRow(ctx, AddBookToCollection, arg.CollectionID, arg.MediaItemID, arg.AddedByUserID) + var i CollectionItems + err := row.Scan( + &i.ID, + &i.CollectionID, + &i.MediaItemID, + &i.AddedAt, + &i.AddedByUserID, + &i.Excluded, + ) + return i, err +} + +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, collection_id, position_in_collection +` + +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 +// ============================================ +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, + &i.CollectionID, + &i.PositionInCollection, + ) + 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 +` + +type AddLibraryFolderParams struct { + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + FolderPath string `db:"folder_path" json:"folder_path"` +} + +// Library Folders queries +func (q *Queries) AddLibraryFolder(ctx context.Context, arg AddLibraryFolderParams) (LibraryFolders, error) { + row := q.db.QueryRow(ctx, AddLibraryFolder, arg.LibraryID, arg.FolderPath) + var i LibraryFolders + err := row.Scan( + &i.ID, + &i.LibraryID, + &i.FolderPath, + &i.CreatedAt, + ) + return i, err +} + +const BulkUpdateFormatGroups = `-- name: BulkUpdateFormatGroups :exec +UPDATE media_items m +SET + format_group = detect_format_group(m.mime_type, m.file_path), + format_mimetype = m.mime_type, + is_reflowable = (detect_format_group(m.mime_type, m.file_path) = 'reflowable'), + has_fixed_layout = (detect_format_group(m.mime_type, m.file_path) IN ('fixed_layout', 'comic_archive')), + updated_at = NOW() +WHERE m.format_group IS NULL OR m.format_group = 'unknown' +` + +// Bulk update format group for all media items +func (q *Queries) BulkUpdateFormatGroups(ctx context.Context) error { + _, err := q.db.Exec(ctx, BulkUpdateFormatGroups) + return err +} + +const BulkUpdateProgressFromSync = `-- name: BulkUpdateProgressFromSync :many +SELECT bulk_update_progress_from_koreader FROM bulk_update_progress_from_koreader($1::uuid, $2::jsonb) +` + +type BulkUpdateProgressFromSyncParams struct { + Column1 pgtype.UUID `db:"column_1" json:"column_1"` + Column2 []byte `db:"column_2" json:"column_2"` +} + +func (q *Queries) BulkUpdateProgressFromSync(ctx context.Context, arg BulkUpdateProgressFromSyncParams) ([]interface{}, error) { + rows, err := q.db.Query(ctx, BulkUpdateProgressFromSync, arg.Column1, arg.Column2) + if err != nil { + return nil, err + } + defer rows.Close() + items := []interface{}{} + for rows.Next() { + var bulk_update_progress_from_koreader interface{} + if err := rows.Scan(&bulk_update_progress_from_koreader); err != nil { + return nil, err + } + items = append(items, bulk_update_progress_from_koreader) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const CleanupExpiredOpdsTokens = `-- name: CleanupExpiredOpdsTokens :exec +DELETE FROM opds_tokens WHERE expires_at < NOW() +` + +// Cleanup expired OPDS tokens +func (q *Queries) CleanupExpiredOpdsTokens(ctx context.Context) error { + _, err := q.db.Exec(ctx, CleanupExpiredOpdsTokens) + return 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') +` + +func (q *Queries) CleanupExpiredRefreshTokens(ctx context.Context) error { + _, err := q.db.Exec(ctx, CleanupExpiredRefreshTokens) + return err +} + +const ClearDeviceSyncQueue = `-- name: ClearDeviceSyncQueue :exec +DELETE FROM sync_queue WHERE device_id = $1 +` + +func (q *Queries) ClearDeviceSyncQueue(ctx context.Context, deviceID pgtype.UUID) error { + _, err := q.db.Exec(ctx, ClearDeviceSyncQueue, deviceID) + 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 CountUnlinkedBooks = `-- name: CountUnlinkedBooks :one +SELECT COUNT(*) as count +FROM unlinked_books +WHERE device_id = $1 AND resolved = false +` + +// Count unlinked books for a device +func (q *Queries) CountUnlinkedBooks(ctx context.Context, deviceID pgtype.UUID) (int64, error) { + row := q.db.QueryRow(ctx, CountUnlinkedBooks, deviceID) + var count int64 + err := row.Scan(&count) + return count, err +} + +const CountUserDevices = `-- name: CountUserDevices :one +SELECT COUNT(*) FROM devices WHERE user_id = $1 +` + +func (q *Queries) CountUserDevices(ctx context.Context, userID pgtype.UUID) (int64, error) { + row := q.db.QueryRow(ctx, CountUserDevices, userID) + var count int64 + err := row.Scan(&count) + return count, err +} + +const CreateCollection = `-- name: CreateCollection :one + +INSERT INTO collections (user_id, name, description, color, icon, auto_assign_rules, view_settings) +VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at +` + +type CreateCollectionParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Name string `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` + Color pgtype.Text `db:"color" json:"color"` + Icon pgtype.Text `db:"icon" json:"icon"` + AutoAssignRules []byte `db:"auto_assign_rules" json:"auto_assign_rules"` + ViewSettings []byte `db:"view_settings" json:"view_settings"` +} + +// COLLECTIONS QUERIES +// Create collection +func (q *Queries) CreateCollection(ctx context.Context, arg CreateCollectionParams) (Collections, error) { + row := q.db.QueryRow(ctx, CreateCollection, + arg.UserID, + arg.Name, + arg.Description, + arg.Color, + arg.Icon, + arg.AutoAssignRules, + arg.ViewSettings, + ) + var i Collections + err := row.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.Description, + &i.Color, + &i.Icon, + &i.AutoAssignRules, + &i.ViewSettings, + &i.ShowOnDashboard, + &i.QueryType, + &i.Priority, + &i.IsSystemCollection, + &i.CreatedAt, + ) + return i, 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) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +RETURNING 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 +` + +type CreateDeviceParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + DeviceName string `db:"device_name" json:"device_name"` + DeviceType string `db:"device_type" json:"device_type"` + DeviceIdentifier string `db:"device_identifier" json:"device_identifier"` + AuthToken string `db:"auth_token" json:"auth_token"` + SyncEnabled pgtype.Bool `db:"sync_enabled" json:"sync_enabled"` + AutoSync pgtype.Bool `db:"auto_sync" json:"auto_sync"` + SyncFrequencyMinutes pgtype.Int4 `db:"sync_frequency_minutes" json:"sync_frequency_minutes"` + DeviceMetadata []byte `db:"device_metadata" json:"device_metadata"` +} + +// ============================================ +// DEVICE MANAGEMENT & AUTH +// ============================================ +// Device Registration & Management +func (q *Queries) CreateDevice(ctx context.Context, arg CreateDeviceParams) (Devices, error) { + row := q.db.QueryRow(ctx, CreateDevice, + arg.UserID, + arg.DeviceName, + arg.DeviceType, + arg.DeviceIdentifier, + arg.AuthToken, + arg.SyncEnabled, + arg.AutoSync, + arg.SyncFrequencyMinutes, + arg.DeviceMetadata, + ) + var i Devices + err := row.Scan( + &i.ID, + &i.UserID, + &i.DeviceName, + &i.DeviceType, + &i.DeviceIdentifier, + &i.AuthToken, + &i.LastSync, + &i.LastSeen, + &i.SyncEnabled, + &i.AutoSync, + &i.SyncFrequencyMinutes, + &i.DeviceMetadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const CreateDeviceCatalog = `-- name: CreateDeviceCatalog :one + +INSERT INTO device_catalogs (device_id, media_item_id, bookhoard_uuid, kobo_content_id, content_id_type, available, delivery_date, delivery_method) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +ON CONFLICT (device_id, kobo_content_id) +DO UPDATE SET + available = EXCLUDED.available, + delivery_date = COALESCE(EXCLUDED.delivery_date, device_catalogs.delivery_date), + delivery_method = EXCLUDED.delivery_method +RETURNING id, device_id, media_item_id, bookhoard_uuid, kobo_content_id, content_id_type, available, delivery_date, delivery_method +` + +type CreateDeviceCatalogParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + BookhoardUuid pgtype.UUID `db:"bookhoard_uuid" json:"bookhoard_uuid"` + KoboContentID string `db:"kobo_content_id" json:"kobo_content_id"` + ContentIDType pgtype.Text `db:"content_id_type" json:"content_id_type"` + Available pgtype.Bool `db:"available" json:"available"` + DeliveryDate pgtype.Timestamptz `db:"delivery_date" json:"delivery_date"` + DeliveryMethod pgtype.Text `db:"delivery_method" json:"delivery_method"` +} + +// DEVICE CATALOGS QUERIES +// Create device catalog entry +func (q *Queries) CreateDeviceCatalog(ctx context.Context, arg CreateDeviceCatalogParams) (DeviceCatalogs, error) { + row := q.db.QueryRow(ctx, CreateDeviceCatalog, + arg.DeviceID, + arg.MediaItemID, + arg.BookhoardUuid, + arg.KoboContentID, + arg.ContentIDType, + arg.Available, + arg.DeliveryDate, + arg.DeliveryMethod, + ) + var i DeviceCatalogs + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.BookhoardUuid, + &i.KoboContentID, + &i.ContentIDType, + &i.Available, + &i.DeliveryDate, + &i.DeliveryMethod, + ) + return i, err +} + +const CreateDeviceFileAlias = `-- name: CreateDeviceFileAlias :one + +INSERT INTO device_file_aliases (media_item_id, device_id, file_path, file_sha256, confidence_score) +VALUES ($1, $2, $3, $4, $5) +RETURNING id, media_item_id, device_id, file_path, file_sha256, confidence_score, last_seen_at +` + +type CreateDeviceFileAliasParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + FilePath string `db:"file_path" json:"file_path"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"` +} + +// DEVICE FILE ALIASES QUERIES +// Create device file alias +func (q *Queries) CreateDeviceFileAlias(ctx context.Context, arg CreateDeviceFileAliasParams) (DeviceFileAliases, error) { + row := q.db.QueryRow(ctx, CreateDeviceFileAlias, + arg.MediaItemID, + arg.DeviceID, + arg.FilePath, + arg.FileSha256, + arg.ConfidenceScore, + ) + var i DeviceFileAliases + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.DeviceID, + &i.FilePath, + &i.FileSha256, + &i.ConfidenceScore, + &i.LastSeenAt, + ) + return i, err +} + +const CreateDeviceShelfMapping = `-- name: CreateDeviceShelfMapping :one + +INSERT INTO device_shelf_mappings (collection_id, device_id, device_shelf_name, sync_direction) +VALUES ($1, $2, $3, $4) +ON CONFLICT (collection_id, device_id) +DO UPDATE SET + device_shelf_name = EXCLUDED.device_shelf_name, + sync_direction = EXCLUDED.sync_direction +RETURNING id, collection_id, device_id, device_shelf_name, sync_direction, created_at +` + +type CreateDeviceShelfMappingParams struct { + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + DeviceShelfName pgtype.Text `db:"device_shelf_name" json:"device_shelf_name"` + SyncDirection pgtype.Text `db:"sync_direction" json:"sync_direction"` +} + +// DEVICE SHELF MAPPINGS QUERIES +// Create device shelf mapping +func (q *Queries) CreateDeviceShelfMapping(ctx context.Context, arg CreateDeviceShelfMappingParams) (DeviceShelfMappings, error) { + row := q.db.QueryRow(ctx, CreateDeviceShelfMapping, + arg.CollectionID, + arg.DeviceID, + arg.DeviceShelfName, + arg.SyncDirection, + ) + var i DeviceShelfMappings + err := row.Scan( + &i.ID, + &i.CollectionID, + &i.DeviceID, + &i.DeviceShelfName, + &i.SyncDirection, + &i.CreatedAt, + ) + return i, err +} + +const CreateLibrary = `-- name: CreateLibrary :one +INSERT INTO libraries (name, description, library_type_id, created_by_admin_id) +VALUES ($1, $2, $3, $4) +RETURNING id, name, description, library_type_id, created_by_admin_id, created_at, updated_at +` + +type CreateLibraryParams struct { + Name string `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` + LibraryTypeID pgtype.UUID `db:"library_type_id" json:"library_type_id"` + CreatedByAdminID pgtype.UUID `db:"created_by_admin_id" json:"created_by_admin_id"` +} + +// Libraries queries +func (q *Queries) CreateLibrary(ctx context.Context, arg CreateLibraryParams) (Libraries, error) { + row := q.db.QueryRow(ctx, CreateLibrary, + arg.Name, + arg.Description, + arg.LibraryTypeID, + arg.CreatedByAdminID, + ) + var i Libraries + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.LibraryTypeID, + &i.CreatedByAdminID, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +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 +` + +type CreateMediaHighlightParams 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"` + NoteID pgtype.UUID `db:"note_id" json:"note_id"` +} + +// Media Highlights queries +func (q *Queries) CreateMediaHighlight(ctx context.Context, arg CreateMediaHighlightParams) (MediaHighlights, error) { + row := q.db.QueryRow(ctx, CreateMediaHighlight, + arg.MediaItemID, + arg.UserID, + arg.SelectionText, + arg.StartPosition, + arg.EndPosition, + arg.Color, + arg.NoteID, + ) + 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, + ) + return i, err +} + +const CreateMediaItem = `-- name: CreateMediaItem :one +INSERT INTO media_items (library_id, title, author, isbn, description, file_path, file_size, mime_type, cover_image_path, series, series_number, tags, tags_search, asin, date_published, publisher, contributors, contributors_search, language, edition, page_count, genre, copyright_year, goodreads_id, openlibrary_id, google_books_id, added_by_admin_id, created_at) +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, $26, $27, $28) +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, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence +` + +type CreateMediaItemParams struct { + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + Isbn pgtype.Text `db:"isbn" json:"isbn"` + Description pgtype.Text `db:"description" json:"description"` + FilePath string `db:"file_path" json:"file_path"` + FileSize pgtype.Int8 `db:"file_size" json:"file_size"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` + Series pgtype.Text `db:"series" json:"series"` + SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` + Tags []string `db:"tags" json:"tags"` + TagsSearch []string `db:"tags_search" json:"tags_search"` + Asin pgtype.Text `db:"asin" json:"asin"` + DatePublished pgtype.Date `db:"date_published" json:"date_published"` + Publisher pgtype.Text `db:"publisher" json:"publisher"` + Contributors []string `db:"contributors" json:"contributors"` + ContributorsSearch []string `db:"contributors_search" json:"contributors_search"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` + AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` +} + +// Media Items queries +func (q *Queries) CreateMediaItem(ctx context.Context, arg CreateMediaItemParams) (MediaItems, error) { + row := q.db.QueryRow(ctx, CreateMediaItem, + arg.LibraryID, + arg.Title, + arg.Author, + arg.Isbn, + arg.Description, + arg.FilePath, + arg.FileSize, + arg.MimeType, + arg.CoverImagePath, + arg.Series, + arg.SeriesNumber, + arg.Tags, + arg.TagsSearch, + arg.Asin, + arg.DatePublished, + arg.Publisher, + arg.Contributors, + arg.ContributorsSearch, + arg.Language, + arg.Edition, + arg.PageCount, + arg.Genre, + arg.CopyrightYear, + arg.GoodreadsID, + arg.OpenlibraryID, + arg.GoogleBooksID, + arg.AddedByAdminID, + arg.CreatedAt, + ) + 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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ) + return i, err +} + +const CreateMediaItemFormat = `-- name: CreateMediaItemFormat :one + +INSERT INTO media_item_formats (media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, converted_from_format_id) +VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING id, media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, created_at, converted_from_format_id +` + +type CreateMediaItemFormatParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + FormatType string `db:"format_type" json:"format_type"` + FilePath pgtype.Text `db:"file_path" json:"file_path"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + FileSizeBytes pgtype.Int8 `db:"file_size_bytes" json:"file_size_bytes"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + ConvertedFromFormatID pgtype.UUID `db:"converted_from_format_id" json:"converted_from_format_id"` +} + +// MEDIA ITEM FORMATS QUERIES +// Create media item format +func (q *Queries) CreateMediaItemFormat(ctx context.Context, arg CreateMediaItemFormatParams) (MediaItemFormats, error) { + row := q.db.QueryRow(ctx, CreateMediaItemFormat, + arg.MediaItemID, + arg.FormatType, + arg.FilePath, + arg.FileSha256, + arg.FileSizeBytes, + arg.MimeType, + arg.ConvertedFromFormatID, + ) + var i MediaItemFormats + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.FormatType, + &i.FilePath, + &i.FileSha256, + &i.FileSizeBytes, + &i.MimeType, + &i.CreatedAt, + &i.ConvertedFromFormatID, + ) + return i, err +} + +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 +` + +type CreateMediaNoteParams 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"` +} + +// Media Notes queries +func (q *Queries) CreateMediaNote(ctx context.Context, arg CreateMediaNoteParams) (MediaNotes, error) { + row := q.db.QueryRow(ctx, CreateMediaNote, + arg.MediaItemID, + arg.UserID, + arg.Content, + arg.Position, + ) + 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, + ) + return i, err +} + +const CreateMediaRating = `-- name: CreateMediaRating :one +INSERT INTO media_ratings (media_item_id, user_id, rating) +VALUES ($1, $2, $3) +ON CONFLICT (media_item_id, user_id) +DO UPDATE SET + rating = EXCLUDED.rating, + updated_at = NOW() +RETURNING id, media_item_id, user_id, rating, created_at, updated_at +` + +type CreateMediaRatingParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Rating int32 `db:"rating" json:"rating"` +} + +func (q *Queries) CreateMediaRating(ctx context.Context, arg CreateMediaRatingParams) (MediaRatings, error) { + row := q.db.QueryRow(ctx, CreateMediaRating, arg.MediaItemID, arg.UserID, arg.Rating) + var i MediaRatings + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.Rating, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const CreateOpdsToken = `-- name: CreateOpdsToken :one + +INSERT INTO opds_tokens (device_id, token, token_type, expires_at) +VALUES ($1, $2, $3, $4) +RETURNING id, device_id, token, token_type, expires_at, created_at +` + +type CreateOpdsTokenParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + Token string `db:"token" json:"token"` + TokenType pgtype.Text `db:"token_type" json:"token_type"` + ExpiresAt pgtype.Timestamptz `db:"expires_at" json:"expires_at"` +} + +// OPDS TOKENS QUERIES +// Create OPDS token +func (q *Queries) CreateOpdsToken(ctx context.Context, arg CreateOpdsTokenParams) (OpdsTokens, error) { + row := q.db.QueryRow(ctx, CreateOpdsToken, + arg.DeviceID, + arg.Token, + arg.TokenType, + arg.ExpiresAt, + ) + var i OpdsTokens + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.Token, + &i.TokenType, + &i.ExpiresAt, + &i.CreatedAt, + ) + 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 +// ============================================ +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, + media_item_id, + device_id, + progress_percentage, + reading_session_start, + reading_session_end, + pages_read, + time_spent_seconds, + device_metadata +) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) +RETURNING id, user_id, media_item_id, device_id, progress_percentage, reading_session_start, reading_session_end, pages_read, time_spent_seconds, device_metadata, created_at +` + +type CreateReadingHistoryParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + ProgressPercentage pgtype.Float8 `db:"progress_percentage" json:"progress_percentage"` + ReadingSessionStart pgtype.Timestamptz `db:"reading_session_start" json:"reading_session_start"` + ReadingSessionEnd pgtype.Timestamptz `db:"reading_session_end" json:"reading_session_end"` + PagesRead pgtype.Int4 `db:"pages_read" json:"pages_read"` + TimeSpentSeconds pgtype.Int4 `db:"time_spent_seconds" json:"time_spent_seconds"` + DeviceMetadata []byte `db:"device_metadata" json:"device_metadata"` +} + +// Create reading history entry +func (q *Queries) CreateReadingHistory(ctx context.Context, arg CreateReadingHistoryParams) (ReadingHistory, error) { + row := q.db.QueryRow(ctx, CreateReadingHistory, + arg.UserID, + arg.MediaItemID, + arg.DeviceID, + arg.ProgressPercentage, + arg.ReadingSessionStart, + arg.ReadingSessionEnd, + arg.PagesRead, + arg.TimeSpentSeconds, + arg.DeviceMetadata, + ) + var i ReadingHistory + err := row.Scan( + &i.ID, + &i.UserID, + &i.MediaItemID, + &i.DeviceID, + &i.ProgressPercentage, + &i.ReadingSessionStart, + &i.ReadingSessionEnd, + &i.PagesRead, + &i.TimeSpentSeconds, + &i.DeviceMetadata, + &i.CreatedAt, + ) + return i, err +} + +const CreateRefreshToken = `-- name: CreateRefreshToken :one +INSERT INTO refresh_tokens (user_id, token, expires_at) +VALUES ($1, $2, $3) +RETURNING id, user_id, token, expires_at, created_at, revoked_at +` + +type CreateRefreshTokenParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Token pgtype.UUID `db:"token" json:"token"` + ExpiresAt pgtype.Timestamptz `db:"expires_at" json:"expires_at"` +} + +// Refresh Tokens queries +func (q *Queries) CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) (RefreshTokens, error) { + row := q.db.QueryRow(ctx, CreateRefreshToken, arg.UserID, arg.Token, arg.ExpiresAt) + var i RefreshTokens + err := row.Scan( + &i.ID, + &i.UserID, + &i.Token, + &i.ExpiresAt, + &i.CreatedAt, + &i.RevokedAt, + ) + return i, err +} + +const CreateSyncConflict = `-- name: CreateSyncConflict :one +INSERT INTO sync_conflicts (media_item_id, user_id, conflict_type, conflict_data) +VALUES ($1, $2, $3, $4) +RETURNING id, media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_by, resolved_at, created_at +` + +type CreateSyncConflictParams 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"` +} + +// Conflict Resolution +func (q *Queries) CreateSyncConflict(ctx context.Context, arg CreateSyncConflictParams) (SyncConflicts, error) { + row := q.db.QueryRow(ctx, CreateSyncConflict, + arg.MediaItemID, + arg.UserID, + arg.ConflictType, + arg.ConflictData, + ) + 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 CreateSyncHistoryEntry = `-- name: CreateSyncHistoryEntry :one +INSERT INTO sync_queue (device_id, media_item_id, sync_type, sync_data, priority, status) +VALUES ($1, $2, 'koreader_progress', $3, 5, 'completed') +RETURNING id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at +` + +type CreateSyncHistoryEntryParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + SyncData []byte `db:"sync_data" json:"sync_data"` +} + +func (q *Queries) CreateSyncHistoryEntry(ctx context.Context, arg CreateSyncHistoryEntryParams) (SyncQueue, error) { + row := q.db.QueryRow(ctx, CreateSyncHistoryEntry, arg.DeviceID, arg.MediaItemID, arg.SyncData) + var i SyncQueue + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.SyncType, + &i.SyncData, + &i.Priority, + &i.Attempts, + &i.MaxAttempts, + &i.Status, + &i.ErrorMessage, + &i.CreatedAt, + &i.ProcessedAt, + ) + return i, err +} + +const CreateSyncQueueItem = `-- name: CreateSyncQueueItem :one +INSERT INTO sync_queue (device_id, media_item_id, sync_type, sync_data, priority, max_attempts, status) +VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at +` + +type CreateSyncQueueItemParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + SyncType string `db:"sync_type" json:"sync_type"` + SyncData []byte `db:"sync_data" json:"sync_data"` + Priority pgtype.Int4 `db:"priority" json:"priority"` + MaxAttempts pgtype.Int4 `db:"max_attempts" json:"max_attempts"` + Status pgtype.Text `db:"status" json:"status"` +} + +// Sync Queue Management +func (q *Queries) CreateSyncQueueItem(ctx context.Context, arg CreateSyncQueueItemParams) (SyncQueue, error) { + row := q.db.QueryRow(ctx, CreateSyncQueueItem, + arg.DeviceID, + arg.MediaItemID, + arg.SyncType, + arg.SyncData, + arg.Priority, + arg.MaxAttempts, + arg.Status, + ) + var i SyncQueue + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.SyncType, + &i.SyncData, + &i.Priority, + &i.Attempts, + &i.MaxAttempts, + &i.Status, + &i.ErrorMessage, + &i.CreatedAt, + &i.ProcessedAt, + ) + return i, err +} + +const CreateSystemCollection = `-- name: CreateSystemCollection :one +INSERT INTO collections (user_id, name, description, icon, color, show_on_dashboard, query_type, priority, is_system_collection, auto_assign_rules) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8, true, 'null'::jsonb) +RETURNING id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at +` + +type CreateSystemCollectionParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Name string `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` + Icon pgtype.Text `db:"icon" json:"icon"` + Color pgtype.Text `db:"color" json:"color"` + ShowOnDashboard pgtype.Bool `db:"show_on_dashboard" json:"show_on_dashboard"` + QueryType pgtype.Text `db:"query_type" json:"query_type"` + Priority pgtype.Int4 `db:"priority" json:"priority"` +} + +func (q *Queries) CreateSystemCollection(ctx context.Context, arg CreateSystemCollectionParams) (Collections, error) { + row := q.db.QueryRow(ctx, CreateSystemCollection, + arg.UserID, + arg.Name, + arg.Description, + arg.Icon, + arg.Color, + arg.ShowOnDashboard, + arg.QueryType, + arg.Priority, + ) + var i Collections + err := row.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.Description, + &i.Color, + &i.Icon, + &i.AutoAssignRules, + &i.ViewSettings, + &i.ShowOnDashboard, + &i.QueryType, + &i.Priority, + &i.IsSystemCollection, + &i.CreatedAt, + ) + return i, err +} + +const CreateUnlinkedBook = `-- name: CreateUnlinkedBook :one + +INSERT INTO unlinked_books (device_id, content_id, file_path, title, author, confidence_score) +VALUES ($1, $2, $3, $4, $5, $6) +RETURNING id, device_id, content_id, file_path, title, author, confidence_score, resolved, media_item_id, resolved_at, resolution_method, last_seen_at, created_at +` + +type CreateUnlinkedBookParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + ContentID string `db:"content_id" json:"content_id"` + FilePath pgtype.Text `db:"file_path" json:"file_path"` + Title pgtype.Text `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"` +} + +// ============================================ +// ENHANCED KOBO SYNC +// ============================================ +// Create unlinked book entry +func (q *Queries) CreateUnlinkedBook(ctx context.Context, arg CreateUnlinkedBookParams) (UnlinkedBooks, error) { + row := q.db.QueryRow(ctx, CreateUnlinkedBook, + arg.DeviceID, + arg.ContentID, + arg.FilePath, + arg.Title, + arg.Author, + arg.ConfidenceScore, + ) + var i UnlinkedBooks + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.ContentID, + &i.FilePath, + &i.Title, + &i.Author, + &i.ConfidenceScore, + &i.Resolved, + &i.MediaItemID, + &i.ResolvedAt, + &i.ResolutionMethod, + &i.LastSeenAt, + &i.CreatedAt, + ) + return i, err +} + +const CreateUser = `-- name: CreateUser :one +INSERT INTO users (email, username, password_hash, first_name, last_name, theme, role) +VALUES ($1, $2, $3, $4, $5, $6, $7) +RETURNING id, email, username, theme, first_name, last_name, role, created_at, updated_at +` + +type CreateUserParams struct { + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + PasswordHash string `db:"password_hash" json:"password_hash"` + FirstName pgtype.Text `db:"first_name" json:"first_name"` + LastName pgtype.Text `db:"last_name" json:"last_name"` + Theme pgtype.Text `db:"theme" json:"theme"` + Role string `db:"role" json:"role"` +} + +type CreateUserRow struct { + ID pgtype.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + Theme pgtype.Text `db:"theme" json:"theme"` + FirstName pgtype.Text `db:"first_name" json:"first_name"` + LastName pgtype.Text `db:"last_name" json:"last_name"` + Role string `db:"role" json:"role"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` +} + +func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error) { + row := q.db.QueryRow(ctx, CreateUser, + arg.Email, + arg.Username, + arg.PasswordHash, + arg.FirstName, + arg.LastName, + arg.Theme, + arg.Role, + ) + var i CreateUserRow + err := row.Scan( + &i.ID, + &i.Email, + &i.Username, + &i.Theme, + &i.FirstName, + &i.LastName, + &i.Role, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const DeleteCollection = `-- name: DeleteCollection :exec +DELETE FROM collections WHERE id = $1 +` + +// Delete collection +func (q *Queries) DeleteCollection(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteCollection, id) + return err +} + +const DeleteDevice = `-- name: DeleteDevice :exec +DELETE FROM devices WHERE id = $1 +` + +func (q *Queries) DeleteDevice(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteDevice, id) + return err +} + +const DeleteDeviceByToken = `-- name: DeleteDeviceByToken :exec +DELETE FROM devices WHERE auth_token = $1 +` + +func (q *Queries) DeleteDeviceByToken(ctx context.Context, authToken string) error { + _, err := q.db.Exec(ctx, DeleteDeviceByToken, authToken) + return err +} + +const DeleteDeviceCatalog = `-- name: DeleteDeviceCatalog :exec +DELETE FROM device_catalogs WHERE id = $1 +` + +// Delete device catalog entry +func (q *Queries) DeleteDeviceCatalog(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteDeviceCatalog, id) + return err +} + +const DeleteDeviceFileAlias = `-- name: DeleteDeviceFileAlias :exec +DELETE FROM device_file_aliases WHERE id = $1 +` + +// Delete device file alias +func (q *Queries) DeleteDeviceFileAlias(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteDeviceFileAlias, id) + return err +} + +const DeleteDeviceShelfMapping = `-- name: DeleteDeviceShelfMapping :exec +DELETE FROM device_shelf_mappings WHERE id = $1 +` + +// Delete device shelf mapping +func (q *Queries) DeleteDeviceShelfMapping(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteDeviceShelfMapping, id) + 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 +` + +func (q *Queries) DeleteLibrary(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteLibrary, id) + return err +} + +const DeleteLibraryFolder = `-- name: DeleteLibraryFolder :one +DELETE FROM library_folders WHERE library_id = $1 AND folder_path = $2 RETURNING id, library_id, folder_path, created_at +` + +type DeleteLibraryFolderParams struct { + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + FolderPath string `db:"folder_path" json:"folder_path"` +} + +func (q *Queries) DeleteLibraryFolder(ctx context.Context, arg DeleteLibraryFolderParams) (LibraryFolders, error) { + row := q.db.QueryRow(ctx, DeleteLibraryFolder, arg.LibraryID, arg.FolderPath) + var i LibraryFolders + err := row.Scan( + &i.ID, + &i.LibraryID, + &i.FolderPath, + &i.CreatedAt, + ) + return i, err +} + +const DeleteMediaHighlight = `-- name: DeleteMediaHighlight :exec +DELETE FROM media_highlights WHERE id = $1 +` + +func (q *Queries) DeleteMediaHighlight(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteMediaHighlight, id) + return err +} + +const DeleteMediaItem = `-- name: DeleteMediaItem :exec +DELETE FROM media_items WHERE id = $1 +` + +func (q *Queries) DeleteMediaItem(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteMediaItem, id) + return err +} + +const DeleteMediaItemFormat = `-- name: DeleteMediaItemFormat :exec +DELETE FROM media_item_formats WHERE id = $1 +` + +// Delete media item format +func (q *Queries) DeleteMediaItemFormat(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteMediaItemFormat, id) + return err +} + +const DeleteMediaNote = `-- name: DeleteMediaNote :exec +DELETE FROM media_notes WHERE id = $1 +` + +func (q *Queries) DeleteMediaNote(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteMediaNote, id) + return err +} + +const DeleteMediaRating = `-- name: DeleteMediaRating :exec +DELETE FROM media_ratings WHERE media_item_id = $1 AND user_id = $2 +` + +type DeleteMediaRatingParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +func (q *Queries) DeleteMediaRating(ctx context.Context, arg DeleteMediaRatingParams) error { + _, err := q.db.Exec(ctx, DeleteMediaRating, arg.MediaItemID, arg.UserID) + return err +} + +const DeleteReadingProgress = `-- name: DeleteReadingProgress :exec +DELETE FROM reading_progress WHERE media_item_id = $1 AND user_id = $2 +` + +type DeleteReadingProgressParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +func (q *Queries) DeleteReadingProgress(ctx context.Context, arg DeleteReadingProgressParams) error { + _, err := q.db.Exec(ctx, DeleteReadingProgress, arg.MediaItemID, arg.UserID) + return err +} + +const DeleteSyncConflict = `-- name: DeleteSyncConflict :exec +DELETE FROM sync_conflicts WHERE id = $1 +` + +func (q *Queries) DeleteSyncConflict(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteSyncConflict, id) + return err +} + +const DeleteSyncQueueItem = `-- name: DeleteSyncQueueItem :exec +DELETE FROM sync_queue WHERE id = $1 +` + +func (q *Queries) DeleteSyncQueueItem(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteSyncQueueItem, id) + return err +} + +const DeleteSystemConfig = `-- name: DeleteSystemConfig :exec +DELETE FROM system_config WHERE key = $1 +` + +// Delete system config +func (q *Queries) DeleteSystemConfig(ctx context.Context, key string) error { + _, err := q.db.Exec(ctx, DeleteSystemConfig, key) + return err +} + +const DeleteUnlinkedBook = `-- name: DeleteUnlinkedBook :exec +DELETE FROM unlinked_books WHERE id = $1 +` + +// Delete unlinked book +func (q *Queries) DeleteUnlinkedBook(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteUnlinkedBook, id) + return err +} + +const DeleteUser = `-- name: DeleteUser :exec +DELETE FROM users WHERE id = $1 +` + +func (q *Queries) DeleteUser(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, DeleteUser, id) + return err +} + +const DeleteUserSystemCollection = `-- name: DeleteUserSystemCollection :exec +DELETE FROM collections +WHERE user_id = $1 + AND name = $2 + AND is_system_collection = true +` + +type DeleteUserSystemCollectionParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Name string `db:"name" json:"name"` +} + +func (q *Queries) DeleteUserSystemCollection(ctx context.Context, arg DeleteUserSystemCollectionParams) error { + _, err := q.db.Exec(ctx, DeleteUserSystemCollection, arg.UserID, arg.Name) + 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 GetAllSystemConfig = `-- name: GetAllSystemConfig :many +SELECT key, value, updated_at, updated_by FROM system_config ORDER BY key +` + +// Get all system config +func (q *Queries) GetAllSystemConfig(ctx context.Context) ([]SystemConfig, error) { + rows, err := q.db.Query(ctx, GetAllSystemConfig) + if err != nil { + return nil, err + } + defer rows.Close() + items := []SystemConfig{} + for rows.Next() { + var i SystemConfig + if err := rows.Scan( + &i.Key, + &i.Value, + &i.UpdatedAt, + &i.UpdatedBy, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetAllSystemSettings = `-- name: GetAllSystemSettings :many +SELECT setting_key, setting_value, description FROM system_settings ORDER BY setting_key +` + +type GetAllSystemSettingsRow struct { + SettingKey string `db:"setting_key" json:"setting_key"` + SettingValue string `db:"setting_value" json:"setting_value"` + Description pgtype.Text `db:"description" json:"description"` +} + +func (q *Queries) GetAllSystemSettings(ctx context.Context) ([]GetAllSystemSettingsRow, error) { + rows, err := q.db.Query(ctx, GetAllSystemSettings) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetAllSystemSettingsRow{} + for rows.Next() { + var i GetAllSystemSettingsRow + if err := rows.Scan(&i.SettingKey, &i.SettingValue, &i.Description); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetAnnotationsForBook = `-- name: GetAnnotationsForBook :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 +FROM media_highlights mh +WHERE mh.media_item_id = $1 AND mh.user_id = $2 +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 +FROM media_notes mn +WHERE mn.media_item_id = $1 AND mn.user_id = $2 +ORDER BY created_at DESC +` + +type GetAnnotationsForBookParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +type GetAnnotationsForBookRow 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"` +} + +func (q *Queries) GetAnnotationsForBook(ctx context.Context, arg GetAnnotationsForBookParams) ([]GetAnnotationsForBookRow, error) { + rows, err := q.db.Query(ctx, GetAnnotationsForBook, arg.MediaItemID, arg.UserID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetAnnotationsForBookRow{} + for rows.Next() { + var i GetAnnotationsForBookRow + 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, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetCollection = `-- name: GetCollection :one +SELECT id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at FROM collections WHERE id = $1 +` + +// Get collection +func (q *Queries) GetCollection(ctx context.Context, id pgtype.UUID) (Collections, error) { + row := q.db.QueryRow(ctx, GetCollection, id) + var i Collections + err := row.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.Description, + &i.Color, + &i.Icon, + &i.AutoAssignRules, + &i.ViewSettings, + &i.ShowOnDashboard, + &i.QueryType, + &i.Priority, + &i.IsSystemCollection, + &i.CreatedAt, + ) + return i, err +} + +const GetCollectionItems = `-- name: GetCollectionItems :many +SELECT ci.id, ci.collection_id, ci.media_item_id, ci.added_at, ci.added_by_user_id, ci.excluded, mi.title, mi.author, mi.cover_image_path, mi.library_id +FROM collection_items ci +JOIN media_items mi ON ci.media_item_id = mi.id +WHERE ci.collection_id = $1 +ORDER BY ci.added_at DESC +` + +type GetCollectionItemsRow struct { + ID pgtype.UUID `db:"id" json:"id"` + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + AddedAt pgtype.Timestamptz `db:"added_at" json:"added_at"` + AddedByUserID pgtype.UUID `db:"added_by_user_id" json:"added_by_user_id"` + Excluded pgtype.Bool `db:"excluded" json:"excluded"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` +} + +// Get collection items +func (q *Queries) GetCollectionItems(ctx context.Context, collectionID pgtype.UUID) ([]GetCollectionItemsRow, error) { + rows, err := q.db.Query(ctx, GetCollectionItems, collectionID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetCollectionItemsRow{} + for rows.Next() { + var i GetCollectionItemsRow + if err := rows.Scan( + &i.ID, + &i.CollectionID, + &i.MediaItemID, + &i.AddedAt, + &i.AddedByUserID, + &i.Excluded, + &i.Title, + &i.Author, + &i.CoverImagePath, + &i.LibraryID, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetCollectionItemsForDashboard = `-- name: GetCollectionItemsForDashboard :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, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, ci.excluded FROM media_items mi +INNER JOIN collection_items ci ON ci.media_item_id = mi.id +WHERE ci.collection_id = $1 + AND mi.library_id = $2 +ORDER BY ci.added_at DESC +LIMIT $3 +` + +type GetCollectionItemsForDashboardParams struct { + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Limit int32 `db:"limit" json:"limit"` +} + +type GetCollectionItemsForDashboardRow struct { + ID pgtype.UUID `db:"id" json:"id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + Isbn pgtype.Text `db:"isbn" json:"isbn"` + Description pgtype.Text `db:"description" json:"description"` + FilePath string `db:"file_path" json:"file_path"` + FileSize pgtype.Int8 `db:"file_size" json:"file_size"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` + Series pgtype.Text `db:"series" json:"series"` + SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` + Tags []string `db:"tags" json:"tags"` + Asin pgtype.Text `db:"asin" json:"asin"` + DatePublished pgtype.Date `db:"date_published" json:"date_published"` + Publisher pgtype.Text `db:"publisher" json:"publisher"` + Contributors []string `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` + AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + 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"` + TagsSearch []string `db:"tags_search" json:"tags_search"` + ContributorsSearch []string `db:"contributors_search" json:"contributors_search"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"` + OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"` + HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"` + Excluded pgtype.Bool `db:"excluded" json:"excluded"` +} + +func (q *Queries) GetCollectionItemsForDashboard(ctx context.Context, arg GetCollectionItemsForDashboardParams) ([]GetCollectionItemsForDashboardRow, error) { + rows, err := q.db.Query(ctx, GetCollectionItemsForDashboard, arg.CollectionID, arg.LibraryID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetCollectionItemsForDashboardRow{} + for rows.Next() { + var i GetCollectionItemsForDashboardRow + if err := rows.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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + &i.Excluded, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetCollectionWithBookCount = `-- name: GetCollectionWithBookCount :one +SELECT + c.id, c.user_id, c.name, c.description, c.color, c.icon, c.auto_assign_rules, c.view_settings, c.show_on_dashboard, c.query_type, c.priority, c.is_system_collection, c.created_at, + COUNT(ci.id) as book_count +FROM collections c +LEFT JOIN collection_items ci ON c.id = ci.collection_id +WHERE c.id = $1 +GROUP BY c.id +` + +type GetCollectionWithBookCountRow struct { + ID pgtype.UUID `db:"id" json:"id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Name string `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` + Color pgtype.Text `db:"color" json:"color"` + Icon pgtype.Text `db:"icon" json:"icon"` + AutoAssignRules []byte `db:"auto_assign_rules" json:"auto_assign_rules"` + ViewSettings []byte `db:"view_settings" json:"view_settings"` + ShowOnDashboard pgtype.Bool `db:"show_on_dashboard" json:"show_on_dashboard"` + QueryType pgtype.Text `db:"query_type" json:"query_type"` + Priority pgtype.Int4 `db:"priority" json:"priority"` + IsSystemCollection pgtype.Bool `db:"is_system_collection" json:"is_system_collection"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + BookCount int64 `db:"book_count" json:"book_count"` +} + +// Get collection with book count +func (q *Queries) GetCollectionWithBookCount(ctx context.Context, id pgtype.UUID) (GetCollectionWithBookCountRow, error) { + row := q.db.QueryRow(ctx, GetCollectionWithBookCount, id) + var i GetCollectionWithBookCountRow + err := row.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.Description, + &i.Color, + &i.Icon, + &i.AutoAssignRules, + &i.ViewSettings, + &i.ShowOnDashboard, + &i.QueryType, + &i.Priority, + &i.IsSystemCollection, + &i.CreatedAt, + &i.BookCount, + ) + return i, err +} + +const GetCollectionsByUser = `-- name: GetCollectionsByUser :many +SELECT id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at FROM collections WHERE user_id = $1 ORDER BY created_at DESC +` + +// Get collections by user +func (q *Queries) GetCollectionsByUser(ctx context.Context, userID pgtype.UUID) ([]Collections, error) { + rows, err := q.db.Query(ctx, GetCollectionsByUser, userID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Collections{} + for rows.Next() { + var i Collections + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.Description, + &i.Color, + &i.Icon, + &i.AutoAssignRules, + &i.ViewSettings, + &i.ShowOnDashboard, + &i.QueryType, + &i.Priority, + &i.IsSystemCollection, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetCollectionsForBook = `-- name: GetCollectionsForBook :many +SELECT c.id, c.user_id, c.name, c.description, c.color, c.icon, c.auto_assign_rules, c.view_settings, c.show_on_dashboard, c.query_type, c.priority, c.is_system_collection, c.created_at +FROM collections c +JOIN collection_items ci ON c.id = ci.collection_id +WHERE ci.media_item_id = $1 +` + +// Get collections for book +func (q *Queries) GetCollectionsForBook(ctx context.Context, mediaItemID pgtype.UUID) ([]Collections, error) { + rows, err := q.db.Query(ctx, GetCollectionsForBook, mediaItemID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Collections{} + for rows.Next() { + var i Collections + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.Description, + &i.Color, + &i.Icon, + &i.AutoAssignRules, + &i.ViewSettings, + &i.ShowOnDashboard, + &i.QueryType, + &i.Priority, + &i.IsSystemCollection, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetContinueReadingItems = `-- name: GetContinueReadingItems :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, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi +INNER JOIN ( + SELECT DISTINCT ON (media_item_id) media_item_id, last_read_at + FROM reading_progress + WHERE user_id = $2 + AND percentage > 0 + AND percentage < 1 + ORDER BY media_item_id, last_read_at DESC +) rp ON rp.media_item_id = mi.id +WHERE mi.library_id = $1 +ORDER BY rp.last_read_at DESC +LIMIT $3 +` + +type GetContinueReadingItemsParams struct { + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Limit int32 `db:"limit" json:"limit"` +} + +// Smart section queries (for system collections) +func (q *Queries) GetContinueReadingItems(ctx context.Context, arg GetContinueReadingItemsParams) ([]MediaItems, error) { + rows, err := q.db.Query(ctx, GetContinueReadingItems, arg.LibraryID, arg.UserID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []MediaItems{} + for rows.Next() { + var i MediaItems + if err := rows.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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetDashboardPreferences = `-- name: GetDashboardPreferences :one + +SELECT id, user_id, library_id, hidden_collections, collection_order, items_per_section, created_at, updated_at FROM user_dashboard_preferences +WHERE user_id = $1 AND library_id = $2 +` + +type GetDashboardPreferencesParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` +} + +// ============================================ +// CAROUSEL-STYLE DASHBOARD +// ============================================ +// Dashboard preferences queries +func (q *Queries) GetDashboardPreferences(ctx context.Context, arg GetDashboardPreferencesParams) (UserDashboardPreferences, error) { + row := q.db.QueryRow(ctx, GetDashboardPreferences, arg.UserID, arg.LibraryID) + var i UserDashboardPreferences + err := row.Scan( + &i.ID, + &i.UserID, + &i.LibraryID, + &i.HiddenCollections, + &i.CollectionOrder, + &i.ItemsPerSection, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetDevice = `-- name: GetDevice :one +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 id = $1 +` + +func (q *Queries) GetDevice(ctx context.Context, id pgtype.UUID) (Devices, error) { + row := q.db.QueryRow(ctx, GetDevice, id) + var i Devices + err := row.Scan( + &i.ID, + &i.UserID, + &i.DeviceName, + &i.DeviceType, + &i.DeviceIdentifier, + &i.AuthToken, + &i.LastSync, + &i.LastSeen, + &i.SyncEnabled, + &i.AutoSync, + &i.SyncFrequencyMinutes, + &i.DeviceMetadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetDeviceByAuthToken = `-- name: GetDeviceByAuthToken :one +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 auth_token = $1 +` + +func (q *Queries) GetDeviceByAuthToken(ctx context.Context, authToken string) (Devices, error) { + row := q.db.QueryRow(ctx, GetDeviceByAuthToken, authToken) + var i Devices + err := row.Scan( + &i.ID, + &i.UserID, + &i.DeviceName, + &i.DeviceType, + &i.DeviceIdentifier, + &i.AuthToken, + &i.LastSync, + &i.LastSeen, + &i.SyncEnabled, + &i.AutoSync, + &i.SyncFrequencyMinutes, + &i.DeviceMetadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetDeviceByIdentifier = `-- name: GetDeviceByIdentifier :one +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_identifier = $1 +` + +func (q *Queries) GetDeviceByIdentifier(ctx context.Context, deviceIdentifier string) (Devices, error) { + row := q.db.QueryRow(ctx, GetDeviceByIdentifier, deviceIdentifier) + var i Devices + err := row.Scan( + &i.ID, + &i.UserID, + &i.DeviceName, + &i.DeviceType, + &i.DeviceIdentifier, + &i.AuthToken, + &i.LastSync, + &i.LastSeen, + &i.SyncEnabled, + &i.AutoSync, + &i.SyncFrequencyMinutes, + &i.DeviceMetadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetDeviceCatalogByBookhoardUUID = `-- name: GetDeviceCatalogByBookhoardUUID :one +SELECT id, device_id, media_item_id, bookhoard_uuid, kobo_content_id, content_id_type, available, delivery_date, delivery_method FROM device_catalogs WHERE device_id = $1 AND bookhoard_uuid = $2 +` + +type GetDeviceCatalogByBookhoardUUIDParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + BookhoardUuid pgtype.UUID `db:"bookhoard_uuid" json:"bookhoard_uuid"` +} + +// Get device catalog by Bookhoard UUID +func (q *Queries) GetDeviceCatalogByBookhoardUUID(ctx context.Context, arg GetDeviceCatalogByBookhoardUUIDParams) (DeviceCatalogs, error) { + row := q.db.QueryRow(ctx, GetDeviceCatalogByBookhoardUUID, arg.DeviceID, arg.BookhoardUuid) + var i DeviceCatalogs + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.BookhoardUuid, + &i.KoboContentID, + &i.ContentIDType, + &i.Available, + &i.DeliveryDate, + &i.DeliveryMethod, + ) + return i, err +} + +const GetDeviceCatalogByKoboContentId = `-- name: GetDeviceCatalogByKoboContentId :one +SELECT id, device_id, media_item_id, bookhoard_uuid, kobo_content_id, content_id_type, available, delivery_date, delivery_method FROM device_catalogs WHERE kobo_content_id = $1 +` + +// Get device catalog by Kobo ContentId +func (q *Queries) GetDeviceCatalogByKoboContentId(ctx context.Context, koboContentID string) (DeviceCatalogs, error) { + row := q.db.QueryRow(ctx, GetDeviceCatalogByKoboContentId, koboContentID) + var i DeviceCatalogs + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.BookhoardUuid, + &i.KoboContentID, + &i.ContentIDType, + &i.Available, + &i.DeliveryDate, + &i.DeliveryMethod, + ) + return i, err +} + +const GetDeviceCatalogEntries = `-- name: GetDeviceCatalogEntries :many +SELECT dc.id, dc.device_id, dc.media_item_id, dc.bookhoard_uuid, dc.kobo_content_id, dc.content_id_type, dc.available, dc.delivery_date, dc.delivery_method, mi.title, mi.author +FROM device_catalogs dc +JOIN media_items mi ON dc.media_item_id = mi.id +WHERE dc.device_id = $1 AND dc.available = true +ORDER BY dc.delivery_date DESC +` + +type GetDeviceCatalogEntriesRow 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"` + BookhoardUuid pgtype.UUID `db:"bookhoard_uuid" json:"bookhoard_uuid"` + KoboContentID string `db:"kobo_content_id" json:"kobo_content_id"` + ContentIDType pgtype.Text `db:"content_id_type" json:"content_id_type"` + Available pgtype.Bool `db:"available" json:"available"` + DeliveryDate pgtype.Timestamptz `db:"delivery_date" json:"delivery_date"` + DeliveryMethod pgtype.Text `db:"delivery_method" json:"delivery_method"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` +} + +// Get device catalog entries +func (q *Queries) GetDeviceCatalogEntries(ctx context.Context, deviceID pgtype.UUID) ([]GetDeviceCatalogEntriesRow, error) { + rows, err := q.db.Query(ctx, GetDeviceCatalogEntries, deviceID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetDeviceCatalogEntriesRow{} + for rows.Next() { + var i GetDeviceCatalogEntriesRow + if err := rows.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.BookhoardUuid, + &i.KoboContentID, + &i.ContentIDType, + &i.Available, + &i.DeliveryDate, + &i.DeliveryMethod, + &i.Title, + &i.Author, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetDeviceFileAlias = `-- name: GetDeviceFileAlias :one +SELECT id, media_item_id, device_id, file_path, file_sha256, confidence_score, last_seen_at FROM device_file_aliases WHERE device_id = $1 AND file_path = $2 +` + +type GetDeviceFileAliasParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + FilePath string `db:"file_path" json:"file_path"` +} + +// Get device file alias +func (q *Queries) GetDeviceFileAlias(ctx context.Context, arg GetDeviceFileAliasParams) (DeviceFileAliases, error) { + row := q.db.QueryRow(ctx, GetDeviceFileAlias, arg.DeviceID, arg.FilePath) + var i DeviceFileAliases + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.DeviceID, + &i.FilePath, + &i.FileSha256, + &i.ConfidenceScore, + &i.LastSeenAt, + ) + return i, err +} + +const GetDeviceFileAliasBySHA256 = `-- name: GetDeviceFileAliasBySHA256 :one +SELECT dfa.id, dfa.media_item_id, dfa.device_id, dfa.file_path, dfa.file_sha256, dfa.confidence_score, dfa.last_seen_at, mi.title, mi.author +FROM device_file_aliases dfa +JOIN media_items mi ON dfa.media_item_id = mi.id +WHERE dfa.file_sha256 = $1 +` + +type GetDeviceFileAliasBySHA256Row struct { + ID pgtype.UUID `db:"id" json:"id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + FilePath string `db:"file_path" json:"file_path"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"` + LastSeenAt pgtype.Timestamptz `db:"last_seen_at" json:"last_seen_at"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` +} + +// Get device file alias by SHA-256 +func (q *Queries) GetDeviceFileAliasBySHA256(ctx context.Context, fileSha256 pgtype.Text) (GetDeviceFileAliasBySHA256Row, error) { + row := q.db.QueryRow(ctx, GetDeviceFileAliasBySHA256, fileSha256) + var i GetDeviceFileAliasBySHA256Row + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.DeviceID, + &i.FilePath, + &i.FileSha256, + &i.ConfidenceScore, + &i.LastSeenAt, + &i.Title, + &i.Author, + ) + return i, err +} + +const GetDeviceFileAliasesByDevice = `-- name: GetDeviceFileAliasesByDevice :many +SELECT dfa.id, dfa.media_item_id, dfa.device_id, dfa.file_path, dfa.file_sha256, dfa.confidence_score, dfa.last_seen_at, mi.title, mi.author +FROM device_file_aliases dfa +JOIN media_items mi ON dfa.media_item_id = mi.id +WHERE dfa.device_id = $1 +ORDER BY dfa.last_seen_at DESC +` + +type GetDeviceFileAliasesByDeviceRow struct { + ID pgtype.UUID `db:"id" json:"id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + FilePath string `db:"file_path" json:"file_path"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"` + LastSeenAt pgtype.Timestamptz `db:"last_seen_at" json:"last_seen_at"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` +} + +// Get device file aliases by device +func (q *Queries) GetDeviceFileAliasesByDevice(ctx context.Context, deviceID pgtype.UUID) ([]GetDeviceFileAliasesByDeviceRow, error) { + rows, err := q.db.Query(ctx, GetDeviceFileAliasesByDevice, deviceID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetDeviceFileAliasesByDeviceRow{} + for rows.Next() { + var i GetDeviceFileAliasesByDeviceRow + if err := rows.Scan( + &i.ID, + &i.MediaItemID, + &i.DeviceID, + &i.FilePath, + &i.FileSha256, + &i.ConfidenceScore, + &i.LastSeenAt, + &i.Title, + &i.Author, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetDeviceShelfMapping = `-- name: GetDeviceShelfMapping :one +SELECT id, collection_id, device_id, device_shelf_name, sync_direction, created_at FROM device_shelf_mappings WHERE device_id = $1 AND collection_id = $2 +` + +type GetDeviceShelfMappingParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` +} + +// Get device shelf mapping +func (q *Queries) GetDeviceShelfMapping(ctx context.Context, arg GetDeviceShelfMappingParams) (DeviceShelfMappings, error) { + row := q.db.QueryRow(ctx, GetDeviceShelfMapping, arg.DeviceID, arg.CollectionID) + var i DeviceShelfMappings + err := row.Scan( + &i.ID, + &i.CollectionID, + &i.DeviceID, + &i.DeviceShelfName, + &i.SyncDirection, + &i.CreatedAt, + ) + return i, err +} + +const GetDeviceShelfMappings = `-- name: GetDeviceShelfMappings :many +SELECT dsm.id, dsm.collection_id, dsm.device_id, dsm.device_shelf_name, dsm.sync_direction, dsm.created_at, c.name as collection_name, c.icon as collection_icon +FROM device_shelf_mappings dsm +JOIN collections c ON dsm.collection_id = c.id +WHERE dsm.device_id = $1 +` + +type GetDeviceShelfMappingsRow struct { + ID pgtype.UUID `db:"id" json:"id"` + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + DeviceShelfName pgtype.Text `db:"device_shelf_name" json:"device_shelf_name"` + SyncDirection pgtype.Text `db:"sync_direction" json:"sync_direction"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + CollectionName string `db:"collection_name" json:"collection_name"` + CollectionIcon pgtype.Text `db:"collection_icon" json:"collection_icon"` +} + +// Get device shelf mappings +func (q *Queries) GetDeviceShelfMappings(ctx context.Context, deviceID pgtype.UUID) ([]GetDeviceShelfMappingsRow, error) { + rows, err := q.db.Query(ctx, GetDeviceShelfMappings, deviceID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetDeviceShelfMappingsRow{} + for rows.Next() { + var i GetDeviceShelfMappingsRow + if err := rows.Scan( + &i.ID, + &i.CollectionID, + &i.DeviceID, + &i.DeviceShelfName, + &i.SyncDirection, + &i.CreatedAt, + &i.CollectionName, + &i.CollectionIcon, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetFailedSyncQueueItems = `-- name: GetFailedSyncQueueItems :many +SELECT id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at FROM sync_queue +WHERE status = 'failed' AND attempts < max_attempts +ORDER BY priority ASC, created_at ASC +LIMIT $1 +` + +func (q *Queries) GetFailedSyncQueueItems(ctx context.Context, limit int32) ([]SyncQueue, error) { + rows, err := q.db.Query(ctx, GetFailedSyncQueueItems, limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []SyncQueue{} + for rows.Next() { + var i SyncQueue + if err := rows.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.SyncType, + &i.SyncData, + &i.Priority, + &i.Attempts, + &i.MaxAttempts, + &i.Status, + &i.ErrorMessage, + &i.CreatedAt, + &i.ProcessedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +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, ks.collection_id, ks.position_in_collection, 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"` + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` + PositionInCollection pgtype.Int4 `db:"position_in_collection" json:"position_in_collection"` + 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.CollectionID, + &i.PositionInCollection, + &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, ks.collection_id, ks.position_in_collection, 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"` + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` + PositionInCollection pgtype.Int4 `db:"position_in_collection" json:"position_in_collection"` + 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.CollectionID, + &i.PositionInCollection, + &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 GetKoboShelvesByCollection = `-- name: GetKoboShelvesByCollection :many +SELECT ks.id, ks.device_id, ks.media_item_id, ks.shelf_name, ks.shelf_position, ks.added_at, ks.last_synced_at, ks.collection_id, ks.position_in_collection, mi.title, mi.author +FROM kobo_shelves ks +JOIN media_items mi ON ks.media_item_id = mi.id +WHERE ks.device_id = $1 AND ks.collection_id = $2 +ORDER BY ks.position_in_collection ASC, ks.shelf_position ASC +` + +type GetKoboShelvesByCollectionParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` +} + +type GetKoboShelvesByCollectionRow 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"` + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` + PositionInCollection pgtype.Int4 `db:"position_in_collection" json:"position_in_collection"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` +} + +// Get Kobo shelves by collection +func (q *Queries) GetKoboShelvesByCollection(ctx context.Context, arg GetKoboShelvesByCollectionParams) ([]GetKoboShelvesByCollectionRow, error) { + rows, err := q.db.Query(ctx, GetKoboShelvesByCollection, arg.DeviceID, arg.CollectionID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetKoboShelvesByCollectionRow{} + for rows.Next() { + var i GetKoboShelvesByCollectionRow + if err := rows.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.ShelfName, + &i.ShelfPosition, + &i.AddedAt, + &i.LastSyncedAt, + &i.CollectionID, + &i.PositionInCollection, + &i.Title, + &i.Author, + ); 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 +JOIN library_types lt ON l.library_type_id = lt.id +WHERE l.id = $1 +` + +type GetLibraryRow struct { + ID pgtype.UUID `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` + LibraryTypeID pgtype.UUID `db:"library_type_id" json:"library_type_id"` + CreatedByAdminID pgtype.UUID `db:"created_by_admin_id" json:"created_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + TypeName string `db:"type_name" json:"type_name"` + TypeDescription pgtype.Text `db:"type_description" json:"type_description"` +} + +func (q *Queries) GetLibrary(ctx context.Context, id pgtype.UUID) (GetLibraryRow, error) { + row := q.db.QueryRow(ctx, GetLibrary, id) + var i GetLibraryRow + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.LibraryTypeID, + &i.CreatedByAdminID, + &i.CreatedAt, + &i.UpdatedAt, + &i.TypeName, + &i.TypeDescription, + ) + return i, err +} + +const GetLibraryByFolder = `-- name: GetLibraryByFolder :one +SELECT lf.library_id, l.id, l.name, l.description, l.library_type_id, l.created_by_admin_id, l.created_at, l.updated_at FROM library_folders lf +JOIN libraries l ON lf.library_id = l.id +WHERE lf.folder_path = $1 +` + +type GetLibraryByFolderRow struct { + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + ID pgtype.UUID `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` + LibraryTypeID pgtype.UUID `db:"library_type_id" json:"library_type_id"` + CreatedByAdminID pgtype.UUID `db:"created_by_admin_id" json:"created_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` +} + +func (q *Queries) GetLibraryByFolder(ctx context.Context, folderPath string) (GetLibraryByFolderRow, error) { + row := q.db.QueryRow(ctx, GetLibraryByFolder, folderPath) + var i GetLibraryByFolderRow + err := row.Scan( + &i.LibraryID, + &i.ID, + &i.Name, + &i.Description, + &i.LibraryTypeID, + &i.CreatedByAdminID, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetLibraryFolders = `-- name: GetLibraryFolders :many +SELECT id, library_id, folder_path, created_at FROM library_folders WHERE library_id = $1 ORDER BY created_at +` + +func (q *Queries) GetLibraryFolders(ctx context.Context, libraryID pgtype.UUID) ([]LibraryFolders, error) { + rows, err := q.db.Query(ctx, GetLibraryFolders, libraryID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []LibraryFolders{} + for rows.Next() { + var i LibraryFolders + if err := rows.Scan( + &i.ID, + &i.LibraryID, + &i.FolderPath, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetLibraryItems = `-- name: GetLibraryItems :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, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi +WHERE mi.library_id = $1 +ORDER BY mi.created_at DESC +` + +func (q *Queries) GetLibraryItems(ctx context.Context, libraryID pgtype.UUID) ([]MediaItems, error) { + rows, err := q.db.Query(ctx, GetLibraryItems, libraryID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []MediaItems{} + for rows.Next() { + var i MediaItems + if err := rows.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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetLibraryType = `-- name: GetLibraryType :one +SELECT id, name, description, allowed_extensions, created_at FROM library_types WHERE id = $1 +` + +func (q *Queries) GetLibraryType(ctx context.Context, id pgtype.UUID) (LibraryTypes, error) { + row := q.db.QueryRow(ctx, GetLibraryType, id) + var i LibraryTypes + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.AllowedExtensions, + &i.CreatedAt, + ) + return i, err +} + +const GetLibraryTypeByName = `-- name: GetLibraryTypeByName :one +SELECT id, name, description, allowed_extensions, created_at FROM library_types WHERE name = $1 +` + +func (q *Queries) GetLibraryTypeByName(ctx context.Context, name string) (LibraryTypes, error) { + row := q.db.QueryRow(ctx, GetLibraryTypeByName, name) + var i LibraryTypes + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.AllowedExtensions, + &i.CreatedAt, + ) + return i, err +} + +const GetLibraryTypes = `-- name: GetLibraryTypes :many +SELECT id, name, description, allowed_extensions, created_at FROM library_types ORDER BY name +` + +// Library Types queries +func (q *Queries) GetLibraryTypes(ctx context.Context) ([]LibraryTypes, error) { + rows, err := q.db.Query(ctx, GetLibraryTypes) + if err != nil { + return nil, err + } + defer rows.Close() + items := []LibraryTypes{} + for rows.Next() { + var i LibraryTypes + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.AllowedExtensions, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetLibraryVisibility = `-- name: GetLibraryVisibility :one +SELECT id, user_id, library_id, is_visible, created_at, updated_at FROM library_visibility WHERE user_id = $1 AND library_id = $2 +` + +type GetLibraryVisibilityParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` +} + +func (q *Queries) GetLibraryVisibility(ctx context.Context, arg GetLibraryVisibilityParams) (LibraryVisibility, error) { + row := q.db.QueryRow(ctx, GetLibraryVisibility, arg.UserID, arg.LibraryID) + var i LibraryVisibility + err := row.Scan( + &i.ID, + &i.UserID, + &i.LibraryID, + &i.IsVisible, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +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 +` + +func (q *Queries) GetMediaHighlight(ctx context.Context, id pgtype.UUID) (MediaHighlights, error) { + row := q.db.QueryRow(ctx, GetMediaHighlight, id) + 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, + ) + 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 +` + +type GetMediaHighlightsParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +func (q *Queries) GetMediaHighlights(ctx context.Context, arg GetMediaHighlightsParams) ([]MediaHighlights, error) { + rows, err := q.db.Query(ctx, GetMediaHighlights, arg.MediaItemID, arg.UserID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []MediaHighlights{} + for rows.Next() { + var i MediaHighlights + if err := rows.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, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +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, entitlement_id, revision_number, kobo_content_id, kobo_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE id = $1 +` + +func (q *Queries) GetMediaItem(ctx context.Context, id pgtype.UUID) (MediaItems, error) { + row := q.db.QueryRow(ctx, GetMediaItem, id) + 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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ) + 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, entitlement_id, revision_number, kobo_content_id, kobo_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 AND library_id = $2 +` + +type GetMediaItemByFilePathParams struct { + FilePath string `db:"file_path" json:"file_path"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` +} + +func (q *Queries) GetMediaItemByFilePath(ctx context.Context, arg GetMediaItemByFilePathParams) (MediaItems, error) { + row := q.db.QueryRow(ctx, GetMediaItemByFilePath, arg.FilePath, arg.LibraryID) + 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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ) + return i, err +} + +const GetMediaItemByFilePathAnyLibrary = `-- name: GetMediaItemByFilePathAnyLibrary :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, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 LIMIT 1 +` + +func (q *Queries) GetMediaItemByFilePathAnyLibrary(ctx context.Context, filePath string) (MediaItems, error) { + row := q.db.QueryRow(ctx, GetMediaItemByFilePathAnyLibrary, filePath) + 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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ) + 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, entitlement_id, revision_number, kobo_content_id, kobo_metadata, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_path = $1 +` + +// ============================================ +// KOREADER SYNC PROTOCOL +// ============================================ +func (q *Queries) GetMediaItemByFilePathForSync(ctx context.Context, filePath string) (MediaItems, error) { + row := q.db.QueryRow(ctx, GetMediaItemByFilePathForSync, filePath) + 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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ) + 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, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence 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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ) + return i, err +} + +const GetMediaItemByOPFIdentifier = `-- name: GetMediaItemByOPFIdentifier :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, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE opf_identifier = $1 +` + +// Get media item by OPF identifier +func (q *Queries) GetMediaItemByOPFIdentifier(ctx context.Context, opfIdentifier pgtype.Text) (MediaItems, error) { + row := q.db.QueryRow(ctx, GetMediaItemByOPFIdentifier, opfIdentifier) + 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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ) + return i, err +} + +const GetMediaItemByOPFUUID = `-- name: GetMediaItemByOPFUUID :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, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE opf_uuid = $1 +` + +// Get media item by OPF UUID +func (q *Queries) GetMediaItemByOPFUUID(ctx context.Context, opfUuid pgtype.Text) (MediaItems, error) { + row := q.db.QueryRow(ctx, GetMediaItemByOPFUUID, opfUuid) + 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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ) + return i, err +} + +const GetMediaItemBySHA256 = `-- name: GetMediaItemBySHA256 :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, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence FROM media_items WHERE file_sha256 = $1 +` + +// Get media item by SHA-256 hash +func (q *Queries) GetMediaItemBySHA256(ctx context.Context, fileSha256 pgtype.Text) (MediaItems, error) { + row := q.db.QueryRow(ctx, GetMediaItemBySHA256, fileSha256) + 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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ) + return i, err +} + +const GetMediaItemFormatBySHA256 = `-- name: GetMediaItemFormatBySHA256 :one +SELECT id, media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, created_at, converted_from_format_id FROM media_item_formats WHERE file_sha256 = $1 +` + +// Get media item format by SHA-256 +func (q *Queries) GetMediaItemFormatBySHA256(ctx context.Context, fileSha256 pgtype.Text) (MediaItemFormats, error) { + row := q.db.QueryRow(ctx, GetMediaItemFormatBySHA256, fileSha256) + var i MediaItemFormats + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.FormatType, + &i.FilePath, + &i.FileSha256, + &i.FileSizeBytes, + &i.MimeType, + &i.CreatedAt, + &i.ConvertedFromFormatID, + ) + return i, err +} + +const GetMediaItemFormatByType = `-- name: GetMediaItemFormatByType :one +SELECT id, media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, created_at, converted_from_format_id FROM media_item_formats WHERE media_item_id = $1 AND format_type = $2 +` + +type GetMediaItemFormatByTypeParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + FormatType string `db:"format_type" json:"format_type"` +} + +// Get media item format by type +func (q *Queries) GetMediaItemFormatByType(ctx context.Context, arg GetMediaItemFormatByTypeParams) (MediaItemFormats, error) { + row := q.db.QueryRow(ctx, GetMediaItemFormatByType, arg.MediaItemID, arg.FormatType) + var i MediaItemFormats + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.FormatType, + &i.FilePath, + &i.FileSha256, + &i.FileSizeBytes, + &i.MimeType, + &i.CreatedAt, + &i.ConvertedFromFormatID, + ) + return i, err +} + +const GetMediaItemFormats = `-- name: GetMediaItemFormats :many +SELECT id, media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, created_at, converted_from_format_id FROM media_item_formats WHERE media_item_id = $1 +` + +// Get media item formats +func (q *Queries) GetMediaItemFormats(ctx context.Context, mediaItemID pgtype.UUID) ([]MediaItemFormats, error) { + rows, err := q.db.Query(ctx, GetMediaItemFormats, mediaItemID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []MediaItemFormats{} + for rows.Next() { + var i MediaItemFormats + if err := rows.Scan( + &i.ID, + &i.MediaItemID, + &i.FormatType, + &i.FilePath, + &i.FileSha256, + &i.FileSizeBytes, + &i.MimeType, + &i.CreatedAt, + &i.ConvertedFromFormatID, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +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 +` + +func (q *Queries) GetMediaNote(ctx context.Context, id pgtype.UUID) (MediaNotes, error) { + row := q.db.QueryRow(ctx, GetMediaNote, id) + 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, + ) + 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 +` + +type GetMediaNotesParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +func (q *Queries) GetMediaNotes(ctx context.Context, arg GetMediaNotesParams) ([]MediaNotes, error) { + rows, err := q.db.Query(ctx, GetMediaNotes, arg.MediaItemID, arg.UserID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []MediaNotes{} + for rows.Next() { + var i MediaNotes + if err := rows.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, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetMediaRating = `-- name: GetMediaRating :one +SELECT id, media_item_id, user_id, rating, created_at, updated_at FROM media_ratings WHERE media_item_id = $1 AND user_id = $2 +` + +type GetMediaRatingParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +func (q *Queries) GetMediaRating(ctx context.Context, arg GetMediaRatingParams) (MediaRatings, error) { + row := q.db.QueryRow(ctx, GetMediaRating, arg.MediaItemID, arg.UserID) + var i MediaRatings + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.Rating, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetMediaRatings = `-- name: GetMediaRatings :many +SELECT mr.id, mr.media_item_id, mr.user_id, mr.rating, mr.created_at, mr.updated_at, u.username +FROM media_ratings mr +JOIN users u ON mr.user_id = u.id +WHERE mr.media_item_id = $1 +ORDER BY mr.created_at DESC +` + +type GetMediaRatingsRow 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"` + Rating int32 `db:"rating" json:"rating"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + Username string `db:"username" json:"username"` +} + +func (q *Queries) GetMediaRatings(ctx context.Context, mediaItemID pgtype.UUID) ([]GetMediaRatingsRow, error) { + rows, err := q.db.Query(ctx, GetMediaRatings, mediaItemID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetMediaRatingsRow{} + for rows.Next() { + var i GetMediaRatingsRow + if err := rows.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.Rating, + &i.CreatedAt, + &i.UpdatedAt, + &i.Username, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetNextRetryTime = `-- name: GetNextRetryTime :one +SELECT + CASE + WHEN attempts = 0 THEN NOW() + WHEN attempts = 1 THEN NOW() + INTERVAL '1 minute' + WHEN attempts = 2 THEN NOW() + INTERVAL '5 minutes' + WHEN attempts = 3 THEN NOW() + INTERVAL '15 minutes' + WHEN attempts = 4 THEN NOW() + INTERVAL '1 hour' + ELSE NOW() + INTERVAL '24 hours' + END as next_retry_time +` + +func (q *Queries) GetNextRetryTime(ctx context.Context) (interface{}, error) { + row := q.db.QueryRow(ctx, GetNextRetryTime) + var next_retry_time interface{} + err := row.Scan(&next_retry_time) + return next_retry_time, err +} + +const GetNotStartedItems = `-- name: GetNotStartedItems :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, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi +WHERE mi.library_id = $1 + AND NOT EXISTS ( + SELECT 1 FROM reading_progress rp + WHERE rp.media_item_id = mi.id + AND rp.user_id = $2 + AND rp.percentage > 0 + ) +ORDER BY mi.created_at DESC +LIMIT $3 +` + +type GetNotStartedItemsParams struct { + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Limit int32 `db:"limit" json:"limit"` +} + +func (q *Queries) GetNotStartedItems(ctx context.Context, arg GetNotStartedItemsParams) ([]MediaItems, error) { + rows, err := q.db.Query(ctx, GetNotStartedItems, arg.LibraryID, arg.UserID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []MediaItems{} + for rows.Next() { + var i MediaItems + if err := rows.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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetOpdsToken = `-- name: GetOpdsToken :one +SELECT ot.id, ot.device_id, ot.token, ot.token_type, ot.expires_at, ot.created_at, d.device_name, d.device_type +FROM opds_tokens ot +JOIN devices d ON ot.device_id = d.id +WHERE ot.token = $1 AND ot.expires_at > NOW() +` + +type GetOpdsTokenRow struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + Token string `db:"token" json:"token"` + TokenType pgtype.Text `db:"token_type" json:"token_type"` + ExpiresAt pgtype.Timestamptz `db:"expires_at" json:"expires_at"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + DeviceName string `db:"device_name" json:"device_name"` + DeviceType string `db:"device_type" json:"device_type"` +} + +// Get OPDS token +func (q *Queries) GetOpdsToken(ctx context.Context, token string) (GetOpdsTokenRow, error) { + row := q.db.QueryRow(ctx, GetOpdsToken, token) + var i GetOpdsTokenRow + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.Token, + &i.TokenType, + &i.ExpiresAt, + &i.CreatedAt, + &i.DeviceName, + &i.DeviceType, + ) + return i, err +} + +const GetOpdsTokensByDevice = `-- name: GetOpdsTokensByDevice :many +SELECT id, device_id, token, token_type, expires_at, created_at FROM opds_tokens WHERE device_id = $1 AND expires_at > NOW() +` + +// Get OPDS tokens by device +func (q *Queries) GetOpdsTokensByDevice(ctx context.Context, deviceID pgtype.UUID) ([]OpdsTokens, error) { + rows, err := q.db.Query(ctx, GetOpdsTokensByDevice, deviceID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []OpdsTokens{} + for rows.Next() { + var i OpdsTokens + if err := rows.Scan( + &i.ID, + &i.DeviceID, + &i.Token, + &i.TokenType, + &i.ExpiresAt, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetPopularBooks = `-- name: GetPopularBooks :many +SELECT + mi.id, + mi.title, + mi.author, + COUNT(*) as read_count, + AVG(rh.progress_percentage) as avg_completion, + MAX(rh.created_at) as last_read + FROM media_items mi + JOIN reading_history rh ON rh.media_item_id = mi.id + WHERE rh.user_id = $1 + GROUP BY mi.id, mi.title, mi.author + ORDER BY read_count DESC + LIMIT $2 +` + +type GetPopularBooksParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Limit int32 `db:"limit" json:"limit"` +} + +type GetPopularBooksRow struct { + ID pgtype.UUID `db:"id" json:"id"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + ReadCount int64 `db:"read_count" json:"read_count"` + AvgCompletion float64 `db:"avg_completion" json:"avg_completion"` + LastRead interface{} `db:"last_read" json:"last_read"` +} + +// Get most popular books for a user +func (q *Queries) GetPopularBooks(ctx context.Context, arg GetPopularBooksParams) ([]GetPopularBooksRow, error) { + rows, err := q.db.Query(ctx, GetPopularBooks, arg.UserID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetPopularBooksRow{} + for rows.Next() { + var i GetPopularBooksRow + if err := rows.Scan( + &i.ID, + &i.Title, + &i.Author, + &i.ReadCount, + &i.AvgCompletion, + &i.LastRead, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetReadingHistory = `-- name: GetReadingHistory :many +SELECT id, user_id, media_item_id, device_id, progress_percentage, reading_session_start, reading_session_end, pages_read, time_spent_seconds, device_metadata, created_at +FROM reading_history +WHERE user_id = $1 AND media_item_id = $2 +ORDER BY created_at DESC +LIMIT $3 +` + +type GetReadingHistoryParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + Limit int32 `db:"limit" json:"limit"` +} + +// Get reading history for a user and book +func (q *Queries) GetReadingHistory(ctx context.Context, arg GetReadingHistoryParams) ([]ReadingHistory, error) { + rows, err := q.db.Query(ctx, GetReadingHistory, arg.UserID, arg.MediaItemID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ReadingHistory{} + for rows.Next() { + var i ReadingHistory + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.MediaItemID, + &i.DeviceID, + &i.ProgressPercentage, + &i.ReadingSessionStart, + &i.ReadingSessionEnd, + &i.PagesRead, + &i.TimeSpentSeconds, + &i.DeviceMetadata, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetReadingProgress = `-- name: GetReadingProgress :one +SELECT id, media_item_id, user_id, current_page, total_pages, last_read_at, percentage, character_offset, epubcfi, chapter, chapter_progress, viewport_x, viewport_y, zoom_level, scroll_position_x, scroll_position_y, panel_number, reading_mode, last_sync_device, last_sync_source, last_sync_timestamp, conflict_detected, conflict_resolved FROM reading_progress WHERE media_item_id = $1 AND user_id = $2 +` + +type GetReadingProgressParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +func (q *Queries) GetReadingProgress(ctx context.Context, arg GetReadingProgressParams) (ReadingProgress, error) { + row := q.db.QueryRow(ctx, GetReadingProgress, arg.MediaItemID, arg.UserID) + var i ReadingProgress + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.CurrentPage, + &i.TotalPages, + &i.LastReadAt, + &i.Percentage, + &i.CharacterOffset, + &i.Epubcfi, + &i.Chapter, + &i.ChapterProgress, + &i.ViewportX, + &i.ViewportY, + &i.ZoomLevel, + &i.ScrollPositionX, + &i.ScrollPositionY, + &i.PanelNumber, + &i.ReadingMode, + &i.LastSyncDevice, + &i.LastSyncSource, + &i.LastSyncTimestamp, + &i.ConflictDetected, + &i.ConflictResolved, + ) + return i, err +} + +const GetRecentlyAddedItems = `-- name: GetRecentlyAddedItems :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, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi +WHERE mi.library_id = $1 +ORDER BY mi.created_at DESC +LIMIT $2 +` + +type GetRecentlyAddedItemsParams struct { + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Limit int32 `db:"limit" json:"limit"` +} + +func (q *Queries) GetRecentlyAddedItems(ctx context.Context, arg GetRecentlyAddedItemsParams) ([]MediaItems, error) { + rows, err := q.db.Query(ctx, GetRecentlyAddedItems, arg.LibraryID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []MediaItems{} + for rows.Next() { + var i MediaItems + if err := rows.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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetRecentlyReadItems = `-- name: GetRecentlyReadItems :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, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence FROM media_items mi +INNER JOIN ( + SELECT DISTINCT ON (media_item_id) media_item_id, last_read_at + FROM reading_progress + WHERE user_id = $2 + AND percentage >= 1 + ORDER BY media_item_id, last_read_at DESC +) rp ON rp.media_item_id = mi.id +WHERE mi.library_id = $1 +ORDER BY rp.last_read_at DESC +LIMIT $3 +` + +type GetRecentlyReadItemsParams struct { + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Limit int32 `db:"limit" json:"limit"` +} + +func (q *Queries) GetRecentlyReadItems(ctx context.Context, arg GetRecentlyReadItemsParams) ([]MediaItems, error) { + rows, err := q.db.Query(ctx, GetRecentlyReadItems, arg.LibraryID, arg.UserID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []MediaItems{} + for rows.Next() { + var i MediaItems + if err := rows.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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetRefreshToken = `-- name: GetRefreshToken :one +SELECT rt.id, rt.user_id, rt.token, rt.expires_at, rt.created_at, rt.revoked_at, u.email, u.username, u.role +FROM refresh_tokens rt +JOIN users u ON rt.user_id = u.id +WHERE rt.token = $1 AND rt.revoked_at IS NULL AND rt.expires_at > NOW() +` + +type GetRefreshTokenRow struct { + ID pgtype.UUID `db:"id" json:"id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Token pgtype.UUID `db:"token" json:"token"` + ExpiresAt pgtype.Timestamptz `db:"expires_at" json:"expires_at"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + RevokedAt pgtype.Timestamptz `db:"revoked_at" json:"revoked_at"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + Role string `db:"role" json:"role"` +} + +func (q *Queries) GetRefreshToken(ctx context.Context, token pgtype.UUID) (GetRefreshTokenRow, error) { + row := q.db.QueryRow(ctx, GetRefreshToken, token) + var i GetRefreshTokenRow + err := row.Scan( + &i.ID, + &i.UserID, + &i.Token, + &i.ExpiresAt, + &i.CreatedAt, + &i.RevokedAt, + &i.Email, + &i.Username, + &i.Role, + ) + return i, err +} + +const GetStuckSyncQueueItems = `-- name: GetStuckSyncQueueItems :many +SELECT id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at FROM sync_queue +WHERE status = 'processing' AND created_at < NOW() - INTERVAL '1 hour' +ORDER BY priority ASC, created_at ASC +` + +func (q *Queries) GetStuckSyncQueueItems(ctx context.Context) ([]SyncQueue, error) { + rows, err := q.db.Query(ctx, GetStuckSyncQueueItems) + if err != nil { + return nil, err + } + defer rows.Close() + items := []SyncQueue{} + for rows.Next() { + var i SyncQueue + if err := rows.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.SyncType, + &i.SyncData, + &i.Priority, + &i.Attempts, + &i.MaxAttempts, + &i.Status, + &i.ErrorMessage, + &i.CreatedAt, + &i.ProcessedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetSyncConflict = `-- name: GetSyncConflict :one +SELECT id, media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_by, resolved_at, created_at FROM sync_conflicts WHERE id = $1 +` + +func (q *Queries) GetSyncConflict(ctx context.Context, id pgtype.UUID) (SyncConflicts, error) { + row := q.db.QueryRow(ctx, GetSyncConflict, id) + 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 GetSyncQueueItem = `-- name: GetSyncQueueItem :one +SELECT id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at FROM sync_queue WHERE id = $1 +` + +func (q *Queries) GetSyncQueueItem(ctx context.Context, id pgtype.UUID) (SyncQueue, error) { + row := q.db.QueryRow(ctx, GetSyncQueueItem, id) + var i SyncQueue + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.SyncType, + &i.SyncData, + &i.Priority, + &i.Attempts, + &i.MaxAttempts, + &i.Status, + &i.ErrorMessage, + &i.CreatedAt, + &i.ProcessedAt, + ) + return i, err +} + +const GetSyncQueueStats = `-- name: GetSyncQueueStats :one +SELECT + COUNT(*) FILTER (WHERE status = 'pending') as pending_count, + COUNT(*) FILTER (WHERE status = 'processing') as processing_count, + COUNT(*) FILTER (WHERE status = 'failed') as failed_count, + COUNT(*) FILTER (WHERE status = 'completed') as completed_count, + COUNT(*) as total_count +FROM sync_queue +WHERE device_id = $1 +` + +type GetSyncQueueStatsRow struct { + PendingCount int64 `db:"pending_count" json:"pending_count"` + ProcessingCount int64 `db:"processing_count" json:"processing_count"` + FailedCount int64 `db:"failed_count" json:"failed_count"` + CompletedCount int64 `db:"completed_count" json:"completed_count"` + TotalCount int64 `db:"total_count" json:"total_count"` +} + +func (q *Queries) GetSyncQueueStats(ctx context.Context, deviceID pgtype.UUID) (GetSyncQueueStatsRow, error) { + row := q.db.QueryRow(ctx, GetSyncQueueStats, deviceID) + var i GetSyncQueueStatsRow + err := row.Scan( + &i.PendingCount, + &i.ProcessingCount, + &i.FailedCount, + &i.CompletedCount, + &i.TotalCount, + ) + return i, err +} + +const GetSystemCollectionsForDashboard = `-- name: GetSystemCollectionsForDashboard :many +SELECT id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at FROM collections +WHERE user_id = $1 + AND is_system_collection = true + AND show_on_dashboard = true +ORDER BY priority ASC +` + +// Dashboard collections queries +func (q *Queries) GetSystemCollectionsForDashboard(ctx context.Context, userID pgtype.UUID) ([]Collections, error) { + rows, err := q.db.Query(ctx, GetSystemCollectionsForDashboard, userID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Collections{} + for rows.Next() { + var i Collections + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.Description, + &i.Color, + &i.Icon, + &i.AutoAssignRules, + &i.ViewSettings, + &i.ShowOnDashboard, + &i.QueryType, + &i.Priority, + &i.IsSystemCollection, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetSystemConfig = `-- name: GetSystemConfig :one + +SELECT key, value, updated_at, updated_by FROM system_config WHERE key = $1 +` + +// SYSTEM CONFIG QUERIES +// Get system config +func (q *Queries) GetSystemConfig(ctx context.Context, key string) (SystemConfig, error) { + row := q.db.QueryRow(ctx, GetSystemConfig, key) + var i SystemConfig + err := row.Scan( + &i.Key, + &i.Value, + &i.UpdatedAt, + &i.UpdatedBy, + ) + return i, err +} + +const GetSystemSetting = `-- name: GetSystemSetting :one +SELECT setting_value FROM system_settings WHERE setting_key = $1 +` + +// System Settings queries +func (q *Queries) GetSystemSetting(ctx context.Context, settingKey string) (string, error) { + row := q.db.QueryRow(ctx, GetSystemSetting, settingKey) + var setting_value string + err := row.Scan(&setting_value) + return setting_value, err +} + +const GetUniversalProgress = `-- name: GetUniversalProgress :one +SELECT + rp.id, + rp.media_item_id, + rp.user_id, + rp.current_page, + rp.total_pages, + rp.last_read_at, + rp.percentage, + rp.character_offset, + rp.epubcfi, + rp.chapter, + rp.chapter_progress, + rp.viewport_x, + rp.viewport_y, + rp.zoom_level, + rp.scroll_position_x, + rp.scroll_position_y, + rp.panel_number, + rp.reading_mode, + rp.last_sync_device, + rp.last_sync_source, + rp.last_sync_timestamp, + rp.conflict_detected, + rp.conflict_resolved, + mi.format_group, + mi.format_mimetype, + mi.is_reflowable, + mi.has_fixed_layout, + mi.total_characters, + mi.chapter_count +FROM reading_progress rp +JOIN media_items mi ON rp.media_item_id = mi.id +WHERE rp.media_item_id = $1 AND rp.user_id = $2 +` + +type GetUniversalProgressParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +type GetUniversalProgressRow struct { + ID pgtype.UUID `db:"id" json:"id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"` + TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"` + LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"` + Percentage pgtype.Float8 `db:"percentage" json:"percentage"` + CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"` + Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"` + Chapter pgtype.Int4 `db:"chapter" json:"chapter"` + ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"` + ViewportX pgtype.Float8 `db:"viewport_x" json:"viewport_x"` + ViewportY pgtype.Float8 `db:"viewport_y" json:"viewport_y"` + ZoomLevel pgtype.Float8 `db:"zoom_level" json:"zoom_level"` + ScrollPositionX pgtype.Float8 `db:"scroll_position_x" json:"scroll_position_x"` + ScrollPositionY pgtype.Float8 `db:"scroll_position_y" json:"scroll_position_y"` + PanelNumber pgtype.Int4 `db:"panel_number" json:"panel_number"` + ReadingMode pgtype.Text `db:"reading_mode" json:"reading_mode"` + LastSyncDevice pgtype.Text `db:"last_sync_device" json:"last_sync_device"` + LastSyncSource pgtype.Text `db:"last_sync_source" json:"last_sync_source"` + LastSyncTimestamp pgtype.Timestamptz `db:"last_sync_timestamp" json:"last_sync_timestamp"` + ConflictDetected pgtype.Bool `db:"conflict_detected" json:"conflict_detected"` + ConflictResolved pgtype.Bool `db:"conflict_resolved" json:"conflict_resolved"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` +} + +// Get universal progress for a book +func (q *Queries) GetUniversalProgress(ctx context.Context, arg GetUniversalProgressParams) (GetUniversalProgressRow, error) { + row := q.db.QueryRow(ctx, GetUniversalProgress, arg.MediaItemID, arg.UserID) + var i GetUniversalProgressRow + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.CurrentPage, + &i.TotalPages, + &i.LastReadAt, + &i.Percentage, + &i.CharacterOffset, + &i.Epubcfi, + &i.Chapter, + &i.ChapterProgress, + &i.ViewportX, + &i.ViewportY, + &i.ZoomLevel, + &i.ScrollPositionX, + &i.ScrollPositionY, + &i.PanelNumber, + &i.ReadingMode, + &i.LastSyncDevice, + &i.LastSyncSource, + &i.LastSyncTimestamp, + &i.ConflictDetected, + &i.ConflictResolved, + &i.FormatGroup, + &i.FormatMimetype, + &i.IsReflowable, + &i.HasFixedLayout, + &i.TotalCharacters, + &i.ChapterCount, + ) + return i, err +} + +const GetUnlinkedBookByContentId = `-- name: GetUnlinkedBookByContentId :one +SELECT id, device_id, content_id, file_path, title, author, confidence_score, resolved, media_item_id, resolved_at, resolution_method, last_seen_at, created_at FROM unlinked_books WHERE device_id = $1 AND content_id = $2 +` + +type GetUnlinkedBookByContentIdParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + ContentID string `db:"content_id" json:"content_id"` +} + +// Get unlinked book by ContentId +func (q *Queries) GetUnlinkedBookByContentId(ctx context.Context, arg GetUnlinkedBookByContentIdParams) (UnlinkedBooks, error) { + row := q.db.QueryRow(ctx, GetUnlinkedBookByContentId, arg.DeviceID, arg.ContentID) + var i UnlinkedBooks + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.ContentID, + &i.FilePath, + &i.Title, + &i.Author, + &i.ConfidenceScore, + &i.Resolved, + &i.MediaItemID, + &i.ResolvedAt, + &i.ResolutionMethod, + &i.LastSeenAt, + &i.CreatedAt, + ) + return i, err +} + +const GetUnlinkedBookByID = `-- name: GetUnlinkedBookByID :one +SELECT id, device_id, content_id, file_path, title, author, confidence_score, resolved, media_item_id, resolved_at, resolution_method, last_seen_at, created_at FROM unlinked_books WHERE id = $1 +` + +// Get unlinked book by ID +func (q *Queries) GetUnlinkedBookByID(ctx context.Context, id pgtype.UUID) (UnlinkedBooks, error) { + row := q.db.QueryRow(ctx, GetUnlinkedBookByID, id) + var i UnlinkedBooks + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.ContentID, + &i.FilePath, + &i.Title, + &i.Author, + &i.ConfidenceScore, + &i.Resolved, + &i.MediaItemID, + &i.ResolvedAt, + &i.ResolutionMethod, + &i.LastSeenAt, + &i.CreatedAt, + ) + return i, err +} + +const GetUnlinkedBooksByDevice = `-- name: GetUnlinkedBooksByDevice :many +SELECT ub.id, ub.device_id, ub.content_id, ub.file_path, ub.title, ub.author, ub.confidence_score, ub.resolved, ub.media_item_id, ub.resolved_at, ub.resolution_method, ub.last_seen_at, ub.created_at, d.device_name, d.device_type +FROM unlinked_books ub +JOIN devices d ON ub.device_id = d.id +WHERE ub.device_id = $1 AND ub.resolved = false +ORDER BY ub.last_seen_at DESC +` + +type GetUnlinkedBooksByDeviceRow struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + ContentID string `db:"content_id" json:"content_id"` + FilePath pgtype.Text `db:"file_path" json:"file_path"` + Title pgtype.Text `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"` + Resolved pgtype.Bool `db:"resolved" json:"resolved"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + ResolvedAt pgtype.Timestamptz `db:"resolved_at" json:"resolved_at"` + ResolutionMethod pgtype.Text `db:"resolution_method" json:"resolution_method"` + LastSeenAt pgtype.Timestamptz `db:"last_seen_at" json:"last_seen_at"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + DeviceName string `db:"device_name" json:"device_name"` + DeviceType string `db:"device_type" json:"device_type"` +} + +// Get unlinked books for a device +func (q *Queries) GetUnlinkedBooksByDevice(ctx context.Context, deviceID pgtype.UUID) ([]GetUnlinkedBooksByDeviceRow, error) { + rows, err := q.db.Query(ctx, GetUnlinkedBooksByDevice, deviceID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetUnlinkedBooksByDeviceRow{} + for rows.Next() { + var i GetUnlinkedBooksByDeviceRow + if err := rows.Scan( + &i.ID, + &i.DeviceID, + &i.ContentID, + &i.FilePath, + &i.Title, + &i.Author, + &i.ConfidenceScore, + &i.Resolved, + &i.MediaItemID, + &i.ResolvedAt, + &i.ResolutionMethod, + &i.LastSeenAt, + &i.CreatedAt, + &i.DeviceName, + &i.DeviceType, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetUser = `-- name: GetUser :one +SELECT + u.id, + u.email, + u.username, + u.theme, + u.first_name, + u.last_name, + u.role, + u.max_devices, + u.created_at, + u.updated_at, + (SELECT COUNT(*) FROM devices WHERE user_id = u.id) as device_count +FROM users u +WHERE u.id = $1 +` + +type GetUserRow struct { + ID pgtype.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + Theme pgtype.Text `db:"theme" json:"theme"` + FirstName pgtype.Text `db:"first_name" json:"first_name"` + LastName pgtype.Text `db:"last_name" json:"last_name"` + Role string `db:"role" json:"role"` + MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + DeviceCount int64 `db:"device_count" json:"device_count"` +} + +func (q *Queries) GetUser(ctx context.Context, id pgtype.UUID) (GetUserRow, error) { + row := q.db.QueryRow(ctx, GetUser, id) + var i GetUserRow + err := row.Scan( + &i.ID, + &i.Email, + &i.Username, + &i.Theme, + &i.FirstName, + &i.LastName, + &i.Role, + &i.MaxDevices, + &i.CreatedAt, + &i.UpdatedAt, + &i.DeviceCount, + ) + return i, err +} + +const GetUserByEmail = `-- name: GetUserByEmail :one +SELECT id, email, username, theme, first_name, last_name, role, created_at, updated_at FROM users WHERE email = $1 +` + +type GetUserByEmailRow struct { + ID pgtype.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + Theme pgtype.Text `db:"theme" json:"theme"` + FirstName pgtype.Text `db:"first_name" json:"first_name"` + LastName pgtype.Text `db:"last_name" json:"last_name"` + Role string `db:"role" json:"role"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` +} + +func (q *Queries) GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error) { + row := q.db.QueryRow(ctx, GetUserByEmail, email) + var i GetUserByEmailRow + err := row.Scan( + &i.ID, + &i.Email, + &i.Username, + &i.Theme, + &i.FirstName, + &i.LastName, + &i.Role, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetUserByEmailOrUsername = `-- name: GetUserByEmailOrUsername :one +SELECT id, email, username, theme, first_name, last_name, role, created_at, updated_at FROM users WHERE email = $1 OR username = $1 +` + +type GetUserByEmailOrUsernameRow struct { + ID pgtype.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + Theme pgtype.Text `db:"theme" json:"theme"` + FirstName pgtype.Text `db:"first_name" json:"first_name"` + LastName pgtype.Text `db:"last_name" json:"last_name"` + Role string `db:"role" json:"role"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` +} + +func (q *Queries) GetUserByEmailOrUsername(ctx context.Context, email string) (GetUserByEmailOrUsernameRow, error) { + row := q.db.QueryRow(ctx, GetUserByEmailOrUsername, email) + var i GetUserByEmailOrUsernameRow + err := row.Scan( + &i.ID, + &i.Email, + &i.Username, + &i.Theme, + &i.FirstName, + &i.LastName, + &i.Role, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetUserByUsername = `-- name: GetUserByUsername :one +SELECT id, email, username, theme, first_name, last_name, role, created_at, updated_at FROM users WHERE username = $1 +` + +type GetUserByUsernameRow struct { + ID pgtype.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + Theme pgtype.Text `db:"theme" json:"theme"` + FirstName pgtype.Text `db:"first_name" json:"first_name"` + LastName pgtype.Text `db:"last_name" json:"last_name"` + Role string `db:"role" json:"role"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` +} + +func (q *Queries) GetUserByUsername(ctx context.Context, username string) (GetUserByUsernameRow, error) { + row := q.db.QueryRow(ctx, GetUserByUsername, username) + var i GetUserByUsernameRow + err := row.Scan( + &i.ID, + &i.Email, + &i.Username, + &i.Theme, + &i.FirstName, + &i.LastName, + &i.Role, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetUserCollectionsForDashboard = `-- name: GetUserCollectionsForDashboard :many +SELECT c.id, c.user_id, c.name, c.description, c.color, c.icon, c.auto_assign_rules, c.view_settings, c.show_on_dashboard, c.query_type, c.priority, c.is_system_collection, c.created_at FROM collections c +WHERE c.user_id = $1 + AND c.show_on_dashboard = true + AND c.is_system_collection = false +ORDER BY priority ASC +` + +func (q *Queries) GetUserCollectionsForDashboard(ctx context.Context, userID pgtype.UUID) ([]Collections, error) { + rows, err := q.db.Query(ctx, GetUserCollectionsForDashboard, userID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Collections{} + for rows.Next() { + var i Collections + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.Description, + &i.Color, + &i.Icon, + &i.AutoAssignRules, + &i.ViewSettings, + &i.ShowOnDashboard, + &i.QueryType, + &i.Priority, + &i.IsSystemCollection, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetUserDeviceUsage = `-- name: GetUserDeviceUsage :many +SELECT + d.id, + d.device_name, + d.device_type, + COUNT(*) as sync_count, + MAX(rh.created_at) as last_sync, + SUM(rh.time_spent_seconds) as total_time_seconds +FROM devices d +JOIN reading_history rh ON rh.device_id = d.id +WHERE d.user_id = $1 +GROUP BY d.id, d.device_name, d.device_type +ORDER BY sync_count DESC +` + +type GetUserDeviceUsageRow struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceName string `db:"device_name" json:"device_name"` + DeviceType string `db:"device_type" json:"device_type"` + SyncCount int64 `db:"sync_count" json:"sync_count"` + LastSync interface{} `db:"last_sync" json:"last_sync"` + TotalTimeSeconds int64 `db:"total_time_seconds" json:"total_time_seconds"` +} + +// Get user device usage statistics +func (q *Queries) GetUserDeviceUsage(ctx context.Context, userID pgtype.UUID) ([]GetUserDeviceUsageRow, error) { + rows, err := q.db.Query(ctx, GetUserDeviceUsage, userID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetUserDeviceUsageRow{} + for rows.Next() { + var i GetUserDeviceUsageRow + if err := rows.Scan( + &i.ID, + &i.DeviceName, + &i.DeviceType, + &i.SyncCount, + &i.LastSync, + &i.TotalTimeSeconds, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetUserForLogin = `-- name: GetUserForLogin :one +SELECT id, email, username, password_hash, theme, first_name, last_name, role, created_at, updated_at FROM users WHERE email = $1 OR username = $1 +` + +type GetUserForLoginRow struct { + ID pgtype.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + PasswordHash string `db:"password_hash" json:"password_hash"` + Theme pgtype.Text `db:"theme" json:"theme"` + FirstName pgtype.Text `db:"first_name" json:"first_name"` + LastName pgtype.Text `db:"last_name" json:"last_name"` + Role string `db:"role" json:"role"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` +} + +func (q *Queries) GetUserForLogin(ctx context.Context, email string) (GetUserForLoginRow, error) { + row := q.db.QueryRow(ctx, GetUserForLogin, email) + var i GetUserForLoginRow + err := row.Scan( + &i.ID, + &i.Email, + &i.Username, + &i.PasswordHash, + &i.Theme, + &i.FirstName, + &i.LastName, + &i.Role, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const GetUserMediaItemsForSync = `-- name: GetUserMediaItemsForSync :many +SELECT + mi.id, + mi.title, + mi.author, + mi.file_path, + mi.mime_type, + mi.page_count, + mi.format_group, + mi.total_characters, + mi.chapter_count, + mi.entitlement_id, + mi.revision_number, + mi.file_size, + mi.file_sha256 +FROM media_items mi +JOIN library_visibility lv ON mi.library_id = lv.library_id +WHERE lv.user_id = $1 + AND lv.is_visible = true +ORDER BY mi.title ASC +LIMIT 1000 +` + +type GetUserMediaItemsForSyncRow struct { + ID pgtype.UUID `db:"id" json:"id"` + 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"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + 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"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` +} + +func (q *Queries) GetUserMediaItemsForSync(ctx context.Context, userID pgtype.UUID) ([]GetUserMediaItemsForSyncRow, error) { + rows, err := q.db.Query(ctx, GetUserMediaItemsForSync, userID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetUserMediaItemsForSyncRow{} + for rows.Next() { + var i GetUserMediaItemsForSyncRow + if err := rows.Scan( + &i.ID, + &i.Title, + &i.Author, + &i.FilePath, + &i.MimeType, + &i.PageCount, + &i.FormatGroup, + &i.TotalCharacters, + &i.ChapterCount, + &i.EntitlementID, + &i.RevisionNumber, + &i.FileSize, + &i.FileSha256, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetUserPasswordHash = `-- name: GetUserPasswordHash :one +SELECT password_hash FROM users WHERE id = $1 +` + +func (q *Queries) GetUserPasswordHash(ctx context.Context, id pgtype.UUID) (string, error) { + row := q.db.QueryRow(ctx, GetUserPasswordHash, id) + var password_hash string + err := row.Scan(&password_hash) + return password_hash, err +} + +const GetUserProgressForBooks = `-- name: GetUserProgressForBooks :many +SELECT + rp.media_item_id, + rp.user_id, + rp.percentage, + rp.character_offset, + rp.epubcfi, + rp.chapter, + rp.chapter_progress, + rp.current_page, + rp.total_pages, + rp.last_read_at, + rp.last_sync_device, + rp.last_sync_source, + mi.title, + mi.author, + mi.file_path +FROM reading_progress rp +JOIN media_items mi ON rp.media_item_id = mi.id +WHERE rp.user_id = $1 + AND rp.media_item_id = ANY($2::uuid[]) +ORDER BY rp.last_read_at DESC +` + +type GetUserProgressForBooksParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Column2 []pgtype.UUID `db:"column_2" json:"column_2"` +} + +type GetUserProgressForBooksRow struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Percentage pgtype.Float8 `db:"percentage" json:"percentage"` + CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"` + Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"` + Chapter pgtype.Int4 `db:"chapter" json:"chapter"` + ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"` + CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"` + TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"` + LastReadAt pgtype.Timestamptz `db:"last_read_at" json:"last_read_at"` + LastSyncDevice pgtype.Text `db:"last_sync_device" json:"last_sync_device"` + LastSyncSource pgtype.Text `db:"last_sync_source" json:"last_sync_source"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + FilePath string `db:"file_path" json:"file_path"` +} + +func (q *Queries) GetUserProgressForBooks(ctx context.Context, arg GetUserProgressForBooksParams) ([]GetUserProgressForBooksRow, error) { + rows, err := q.db.Query(ctx, GetUserProgressForBooks, arg.UserID, arg.Column2) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetUserProgressForBooksRow{} + for rows.Next() { + var i GetUserProgressForBooksRow + if err := rows.Scan( + &i.MediaItemID, + &i.UserID, + &i.Percentage, + &i.CharacterOffset, + &i.Epubcfi, + &i.Chapter, + &i.ChapterProgress, + &i.CurrentPage, + &i.TotalPages, + &i.LastReadAt, + &i.LastSyncDevice, + &i.LastSyncSource, + &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 GetUserReadingHistory = `-- name: GetUserReadingHistory :many + +SELECT + rh.id, + rh.user_id, + rh.media_item_id, + rh.device_id, + rh.progress_percentage, + rh.reading_session_start, + rh.reading_session_end, + rh.pages_read, + rh.time_spent_seconds, + rh.device_metadata, + rh.created_at, + mi.title, + mi.author, + d.device_name, + d.device_type +FROM reading_history rh +JOIN media_items mi ON rh.media_item_id = mi.id +LEFT JOIN devices d ON rh.device_id = d.id +WHERE rh.user_id = $1 + AND rh.created_at >= $2 + AND rh.created_at <= $3 +ORDER BY rh.created_at DESC +` + +type GetUserReadingHistoryParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + CreatedAt_2 pgtype.Timestamptz `db:"created_at_2" json:"created_at_2"` +} + +type GetUserReadingHistoryRow struct { + ID pgtype.UUID `db:"id" json:"id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + ProgressPercentage pgtype.Float8 `db:"progress_percentage" json:"progress_percentage"` + ReadingSessionStart pgtype.Timestamptz `db:"reading_session_start" json:"reading_session_start"` + ReadingSessionEnd pgtype.Timestamptz `db:"reading_session_end" json:"reading_session_end"` + PagesRead pgtype.Int4 `db:"pages_read" json:"pages_read"` + TimeSpentSeconds pgtype.Int4 `db:"time_spent_seconds" json:"time_spent_seconds"` + DeviceMetadata []byte `db:"device_metadata" json:"device_metadata"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + DeviceName pgtype.Text `db:"device_name" json:"device_name"` + DeviceType pgtype.Text `db:"device_type" json:"device_type"` +} + +// Analytics queries +// Get user reading history for analytics +func (q *Queries) GetUserReadingHistory(ctx context.Context, arg GetUserReadingHistoryParams) ([]GetUserReadingHistoryRow, error) { + rows, err := q.db.Query(ctx, GetUserReadingHistory, arg.UserID, arg.CreatedAt, arg.CreatedAt_2) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetUserReadingHistoryRow{} + for rows.Next() { + var i GetUserReadingHistoryRow + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.MediaItemID, + &i.DeviceID, + &i.ProgressPercentage, + &i.ReadingSessionStart, + &i.ReadingSessionEnd, + &i.PagesRead, + &i.TimeSpentSeconds, + &i.DeviceMetadata, + &i.CreatedAt, + &i.Title, + &i.Author, + &i.DeviceName, + &i.DeviceType, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const GetUserVisibleLibraries = `-- name: GetUserVisibleLibraries :many +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, + COALESCE(lv.is_visible, true) as is_visible +FROM libraries l +JOIN library_types lt ON l.library_type_id = lt.id +LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $1 +WHERE COALESCE(lv.is_visible, true) = true +ORDER BY l.created_at ASC +` + +type GetUserVisibleLibrariesRow struct { + ID pgtype.UUID `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` + LibraryTypeID pgtype.UUID `db:"library_type_id" json:"library_type_id"` + CreatedByAdminID pgtype.UUID `db:"created_by_admin_id" json:"created_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + TypeName string `db:"type_name" json:"type_name"` + TypeDescription pgtype.Text `db:"type_description" json:"type_description"` + IsVisible bool `db:"is_visible" json:"is_visible"` +} + +func (q *Queries) GetUserVisibleLibraries(ctx context.Context, userID pgtype.UUID) ([]GetUserVisibleLibrariesRow, error) { + rows, err := q.db.Query(ctx, GetUserVisibleLibraries, userID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetUserVisibleLibrariesRow{} + for rows.Next() { + var i GetUserVisibleLibrariesRow + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.LibraryTypeID, + &i.CreatedByAdminID, + &i.CreatedAt, + &i.UpdatedAt, + &i.TypeName, + &i.TypeDescription, + &i.IsVisible, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const IncrementSyncQueueAttempts = `-- name: IncrementSyncQueueAttempts :one +UPDATE sync_queue +SET + attempts = attempts + 1, + status = CASE + WHEN attempts + 1 >= max_attempts THEN 'failed' + ELSE 'pending' + END, + error_message = $2 +WHERE id = $1 +RETURNING id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at +` + +type IncrementSyncQueueAttemptsParams struct { + ID pgtype.UUID `db:"id" json:"id"` + ErrorMessage pgtype.Text `db:"error_message" json:"error_message"` +} + +func (q *Queries) IncrementSyncQueueAttempts(ctx context.Context, arg IncrementSyncQueueAttemptsParams) (SyncQueue, error) { + row := q.db.QueryRow(ctx, IncrementSyncQueueAttempts, arg.ID, arg.ErrorMessage) + var i SyncQueue + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.SyncType, + &i.SyncData, + &i.Priority, + &i.Attempts, + &i.MaxAttempts, + &i.Status, + &i.ErrorMessage, + &i.CreatedAt, + &i.ProcessedAt, + ) + return i, err +} + +const IsBookInCollection = `-- name: IsBookInCollection :one +SELECT EXISTS( + SELECT 1 FROM collection_items + WHERE collection_id = $1 AND media_item_id = $2 +) as in_collection +` + +type IsBookInCollectionParams struct { + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` +} + +// Check if book is in collection +func (q *Queries) IsBookInCollection(ctx context.Context, arg IsBookInCollectionParams) (bool, error) { + row := q.db.QueryRow(ctx, IsBookInCollection, arg.CollectionID, arg.MediaItemID) + var in_collection bool + err := row.Scan(&in_collection) + return in_collection, err +} + +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 LinkUnlinkedBook = `-- name: LinkUnlinkedBook :one +UPDATE unlinked_books +SET + media_item_id = $2, + confidence_score = $3, + resolved = true, + resolved_at = NOW(), + resolution_method = 'manual_link' +WHERE id = $1 +RETURNING id, device_id, content_id, file_path, title, author, confidence_score, resolved, media_item_id, resolved_at, resolution_method, last_seen_at, created_at +` + +type LinkUnlinkedBookParams struct { + ID pgtype.UUID `db:"id" json:"id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"` +} + +// Link unlinked book to media item +func (q *Queries) LinkUnlinkedBook(ctx context.Context, arg LinkUnlinkedBookParams) (UnlinkedBooks, error) { + row := q.db.QueryRow(ctx, LinkUnlinkedBook, arg.ID, arg.MediaItemID, arg.ConfidenceScore) + var i UnlinkedBooks + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.ContentID, + &i.FilePath, + &i.Title, + &i.Author, + &i.ConfidenceScore, + &i.Resolved, + &i.MediaItemID, + &i.ResolvedAt, + &i.ResolutionMethod, + &i.LastSeenAt, + &i.CreatedAt, + ) + return i, err +} + +const ListAllSyncQueueItems = `-- name: ListAllSyncQueueItems :many +SELECT + sq.id, sq.device_id, sq.media_item_id, sq.sync_type, sq.sync_data, sq.priority, sq.attempts, sq.max_attempts, sq.status, sq.error_message, sq.created_at, sq.processed_at, + d.device_name, + d.device_type, + u.email as user_email, + mi.title as media_title +FROM sync_queue sq +JOIN devices d ON sq.device_id = d.id +JOIN users u ON d.user_id = u.id +LEFT JOIN media_items mi ON sq.media_item_id = mi.id +ORDER BY sq.priority ASC, sq.created_at DESC +LIMIT $1 OFFSET $2 +` + +type ListAllSyncQueueItemsParams struct { + Limit int32 `db:"limit" json:"limit"` + Offset int32 `db:"offset" json:"offset"` +} + +type ListAllSyncQueueItemsRow struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + SyncType string `db:"sync_type" json:"sync_type"` + SyncData []byte `db:"sync_data" json:"sync_data"` + Priority pgtype.Int4 `db:"priority" json:"priority"` + Attempts pgtype.Int4 `db:"attempts" json:"attempts"` + MaxAttempts pgtype.Int4 `db:"max_attempts" json:"max_attempts"` + Status pgtype.Text `db:"status" json:"status"` + ErrorMessage pgtype.Text `db:"error_message" json:"error_message"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + ProcessedAt pgtype.Timestamptz `db:"processed_at" json:"processed_at"` + DeviceName string `db:"device_name" json:"device_name"` + DeviceType string `db:"device_type" json:"device_type"` + UserEmail string `db:"user_email" json:"user_email"` + MediaTitle pgtype.Text `db:"media_title" json:"media_title"` +} + +func (q *Queries) ListAllSyncQueueItems(ctx context.Context, arg ListAllSyncQueueItemsParams) ([]ListAllSyncQueueItemsRow, error) { + rows, err := q.db.Query(ctx, ListAllSyncQueueItems, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListAllSyncQueueItemsRow{} + for rows.Next() { + var i ListAllSyncQueueItemsRow + if err := rows.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.SyncType, + &i.SyncData, + &i.Priority, + &i.Attempts, + &i.MaxAttempts, + &i.Status, + &i.ErrorMessage, + &i.CreatedAt, + &i.ProcessedAt, + &i.DeviceName, + &i.DeviceType, + &i.UserEmail, + &i.MediaTitle, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +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 +` + +func (q *Queries) ListDevicesByType(ctx context.Context, deviceType string) ([]Devices, error) { + rows, err := q.db.Query(ctx, ListDevicesByType, deviceType) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Devices{} + for rows.Next() { + var i Devices + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.DeviceName, + &i.DeviceType, + &i.DeviceIdentifier, + &i.AuthToken, + &i.LastSync, + &i.LastSeen, + &i.SyncEnabled, + &i.AutoSync, + &i.SyncFrequencyMinutes, + &i.DeviceMetadata, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const ListDevicesByUser = `-- name: ListDevicesByUser :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 user_id = $1 ORDER BY created_at DESC +` + +func (q *Queries) ListDevicesByUser(ctx context.Context, userID pgtype.UUID) ([]Devices, error) { + rows, err := q.db.Query(ctx, ListDevicesByUser, userID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Devices{} + for rows.Next() { + var i Devices + if err := rows.Scan( + &i.ID, + &i.UserID, + &i.DeviceName, + &i.DeviceType, + &i.DeviceIdentifier, + &i.AuthToken, + &i.LastSync, + &i.LastSeen, + &i.SyncEnabled, + &i.AutoSync, + &i.SyncFrequencyMinutes, + &i.DeviceMetadata, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const ListLibraries = `-- name: ListLibraries :many +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 +JOIN library_types lt ON l.library_type_id = lt.id +ORDER BY l.created_at DESC +` + +type ListLibrariesRow struct { + ID pgtype.UUID `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` + LibraryTypeID pgtype.UUID `db:"library_type_id" json:"library_type_id"` + CreatedByAdminID pgtype.UUID `db:"created_by_admin_id" json:"created_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + TypeName string `db:"type_name" json:"type_name"` + TypeDescription pgtype.Text `db:"type_description" json:"type_description"` +} + +func (q *Queries) ListLibraries(ctx context.Context) ([]ListLibrariesRow, error) { + rows, err := q.db.Query(ctx, ListLibraries) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListLibrariesRow{} + for rows.Next() { + var i ListLibrariesRow + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.LibraryTypeID, + &i.CreatedByAdminID, + &i.CreatedAt, + &i.UpdatedAt, + &i.TypeName, + &i.TypeDescription, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +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, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, 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 +ORDER BY mi.created_at DESC LIMIT $1 OFFSET $2 +` + +type ListMediaItemsParams struct { + Limit int32 `db:"limit" json:"limit"` + Offset int32 `db:"offset" json:"offset"` +} + +type ListMediaItemsRow struct { + ID pgtype.UUID `db:"id" json:"id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + Isbn pgtype.Text `db:"isbn" json:"isbn"` + Description pgtype.Text `db:"description" json:"description"` + FilePath string `db:"file_path" json:"file_path"` + FileSize pgtype.Int8 `db:"file_size" json:"file_size"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` + Series pgtype.Text `db:"series" json:"series"` + SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` + Tags []string `db:"tags" json:"tags"` + Asin pgtype.Text `db:"asin" json:"asin"` + DatePublished pgtype.Date `db:"date_published" json:"date_published"` + Publisher pgtype.Text `db:"publisher" json:"publisher"` + Contributors []string `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` + AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + 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"` + TagsSearch []string `db:"tags_search" json:"tags_search"` + ContributorsSearch []string `db:"contributors_search" json:"contributors_search"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"` + OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"` + HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"` + LibraryName string `db:"library_name" json:"library_name"` + LibraryTypeName string `db:"library_type_name" json:"library_type_name"` +} + +func (q *Queries) ListMediaItems(ctx context.Context, arg ListMediaItemsParams) ([]ListMediaItemsRow, error) { + rows, err := q.db.Query(ctx, ListMediaItems, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListMediaItemsRow{} + for rows.Next() { + var i ListMediaItemsRow + if err := rows.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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + &i.LibraryName, + &i.LibraryTypeName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +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, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, 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 +WHERE mi.library_id = $1 +ORDER BY mi.created_at DESC +` + +type ListMediaItemsByLibraryRow struct { + ID pgtype.UUID `db:"id" json:"id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + Isbn pgtype.Text `db:"isbn" json:"isbn"` + Description pgtype.Text `db:"description" json:"description"` + FilePath string `db:"file_path" json:"file_path"` + FileSize pgtype.Int8 `db:"file_size" json:"file_size"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` + Series pgtype.Text `db:"series" json:"series"` + SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` + Tags []string `db:"tags" json:"tags"` + Asin pgtype.Text `db:"asin" json:"asin"` + DatePublished pgtype.Date `db:"date_published" json:"date_published"` + Publisher pgtype.Text `db:"publisher" json:"publisher"` + Contributors []string `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` + AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + 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"` + TagsSearch []string `db:"tags_search" json:"tags_search"` + ContributorsSearch []string `db:"contributors_search" json:"contributors_search"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"` + OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"` + HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"` + LibraryName string `db:"library_name" json:"library_name"` + LibraryTypeName string `db:"library_type_name" json:"library_type_name"` +} + +func (q *Queries) ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype.UUID) ([]ListMediaItemsByLibraryRow, error) { + rows, err := q.db.Query(ctx, ListMediaItemsByLibrary, libraryID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListMediaItemsByLibraryRow{} + for rows.Next() { + var i ListMediaItemsByLibraryRow + if err := rows.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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + &i.LibraryName, + &i.LibraryTypeName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +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, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, 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 +LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $1 +WHERE mi.library_id = $2 + AND COALESCE(lv.is_visible, true) = true + AND ($3 = '' OR mi.author ILIKE $3) + AND ($4 = '' OR mi.series ILIKE $4) + AND ($5 = '' OR mi.genre = $5) + AND ($6 = '' OR mi.language = $6) + AND ($7 = 0 OR mi.copyright_year >= $7) + AND ($8 = 0 OR mi.copyright_year <= $8) + AND ($9 = false OR mi.cover_image_path IS NOT NULL) +ORDER BY + CASE + WHEN $10 = 'title ASC' THEN mi.title + ELSE '' + END ASC, + CASE + WHEN $10 = 'title DESC' THEN mi.title + ELSE '' + END DESC, + CASE + WHEN $10 = 'author ASC' THEN COALESCE(mi.author, '') + ELSE '' + END ASC, + CASE + WHEN $10 = 'author DESC' THEN COALESCE(mi.author, '') + ELSE '' + END DESC, + CASE + WHEN $10 = 'created_at ASC' THEN mi.created_at + ELSE '1970-01-01'::timestamp + END ASC, + CASE + WHEN $10 = 'created_at DESC' THEN mi.created_at + ELSE '1970-01-01'::timestamp + END DESC, + mi.created_at DESC +LIMIT $12 OFFSET $11 +` + +type ListMediaItemsFilteredParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + AuthorFilter interface{} `db:"author_filter" json:"author_filter"` + SeriesFilter interface{} `db:"series_filter" json:"series_filter"` + GenreFilter interface{} `db:"genre_filter" json:"genre_filter"` + LanguageFilter interface{} `db:"language_filter" json:"language_filter"` + YearMin interface{} `db:"year_min" json:"year_min"` + YearMax interface{} `db:"year_max" json:"year_max"` + HasCover interface{} `db:"has_cover" json:"has_cover"` + Sort interface{} `db:"sort" json:"sort"` + Offset pgtype.Int4 `db:"offset" json:"offset"` + Limit pgtype.Int4 `db:"limit" json:"limit"` +} + +type ListMediaItemsFilteredRow struct { + ID pgtype.UUID `db:"id" json:"id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + Isbn pgtype.Text `db:"isbn" json:"isbn"` + Description pgtype.Text `db:"description" json:"description"` + FilePath string `db:"file_path" json:"file_path"` + FileSize pgtype.Int8 `db:"file_size" json:"file_size"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` + Series pgtype.Text `db:"series" json:"series"` + SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` + Tags []string `db:"tags" json:"tags"` + Asin pgtype.Text `db:"asin" json:"asin"` + DatePublished pgtype.Date `db:"date_published" json:"date_published"` + Publisher pgtype.Text `db:"publisher" json:"publisher"` + Contributors []string `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` + AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + 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"` + TagsSearch []string `db:"tags_search" json:"tags_search"` + ContributorsSearch []string `db:"contributors_search" json:"contributors_search"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"` + OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"` + HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"` + LibraryName string `db:"library_name" json:"library_name"` + LibraryTypeName string `db:"library_type_name" json:"library_type_name"` +} + +func (q *Queries) ListMediaItemsFiltered(ctx context.Context, arg ListMediaItemsFilteredParams) ([]ListMediaItemsFilteredRow, error) { + rows, err := q.db.Query(ctx, ListMediaItemsFiltered, + arg.UserID, + arg.LibraryID, + arg.AuthorFilter, + arg.SeriesFilter, + arg.GenreFilter, + arg.LanguageFilter, + arg.YearMin, + arg.YearMax, + arg.HasCover, + arg.Sort, + arg.Offset, + arg.Limit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListMediaItemsFilteredRow{} + for rows.Next() { + var i ListMediaItemsFilteredRow + if err := rows.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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + &i.LibraryName, + &i.LibraryTypeName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +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, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, 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 +WHERE mi.library_id = $1 +ORDER BY + CASE + WHEN $2 = 'title ASC' THEN mi.title + ELSE '' + END ASC, + CASE + WHEN $2 = 'title DESC' THEN mi.title + ELSE '' + END DESC, + CASE + WHEN $2 = 'author ASC' THEN COALESCE(mi.author, '') + ELSE '' + END ASC, + CASE + WHEN $2 = 'author DESC' THEN COALESCE(mi.author, '') + ELSE '' + END DESC, + CASE + WHEN $2 = 'created_at ASC' THEN mi.created_at + ELSE '1970-01-01'::timestamp + END ASC, + CASE + WHEN $2 = 'created_at DESC' THEN mi.created_at + ELSE '1970-01-01'::timestamp + END DESC, + CASE + WHEN $2 = 'series ASC' THEN COALESCE(mi.series, '') + ELSE '' + END ASC, + CASE + WHEN $2 = 'series DESC' THEN COALESCE(mi.series, '') + ELSE '' + END DESC, + CASE + WHEN $2 = 'date_published ASC' THEN COALESCE(mi.date_published::text, '') + ELSE '' + END ASC, + CASE + WHEN $2 = 'date_published DESC' THEN COALESCE(mi.date_published::text, '') + ELSE '' + END DESC, + CASE + WHEN $2 = 'copyright_year ASC' THEN COALESCE(mi.copyright_year::text, '0') + ELSE '' + END ASC, + CASE + WHEN $2 = 'copyright_year DESC' THEN COALESCE(mi.copyright_year::text, '0') + ELSE '' + END DESC, + CASE + WHEN $2 = 'page_count ASC' THEN COALESCE(mi.page_count::text, '0') + ELSE '' + END ASC, + CASE + WHEN $2 = 'page_count DESC' THEN COALESCE(mi.page_count::text, '0') + ELSE '' + END DESC, + CASE + WHEN $2 = 'genre ASC' THEN COALESCE(mi.genre, '') + ELSE '' + END ASC, + CASE + WHEN $2 = 'genre DESC' THEN COALESCE(mi.genre, '') + ELSE '' + END DESC, + mi.created_at DESC + LIMIT $4 OFFSET $3 +` + +type ListMediaItemsSortedParams struct { + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Sort interface{} `db:"sort" json:"sort"` + Offset pgtype.Int4 `db:"offset" json:"offset"` + Limit pgtype.Int4 `db:"limit" json:"limit"` +} + +type ListMediaItemsSortedRow struct { + ID pgtype.UUID `db:"id" json:"id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + Isbn pgtype.Text `db:"isbn" json:"isbn"` + Description pgtype.Text `db:"description" json:"description"` + FilePath string `db:"file_path" json:"file_path"` + FileSize pgtype.Int8 `db:"file_size" json:"file_size"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` + Series pgtype.Text `db:"series" json:"series"` + SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` + Tags []string `db:"tags" json:"tags"` + Asin pgtype.Text `db:"asin" json:"asin"` + DatePublished pgtype.Date `db:"date_published" json:"date_published"` + Publisher pgtype.Text `db:"publisher" json:"publisher"` + Contributors []string `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` + AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + 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"` + TagsSearch []string `db:"tags_search" json:"tags_search"` + ContributorsSearch []string `db:"contributors_search" json:"contributors_search"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"` + OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"` + HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"` + LibraryName string `db:"library_name" json:"library_name"` + LibraryTypeName string `db:"library_type_name" json:"library_type_name"` +} + +func (q *Queries) ListMediaItemsSorted(ctx context.Context, arg ListMediaItemsSortedParams) ([]ListMediaItemsSortedRow, error) { + rows, err := q.db.Query(ctx, ListMediaItemsSorted, + arg.LibraryID, + arg.Sort, + arg.Offset, + arg.Limit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListMediaItemsSortedRow{} + for rows.Next() { + var i ListMediaItemsSortedRow + if err := rows.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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + &i.LibraryName, + &i.LibraryTypeName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const ListPendingSyncQueueItems = `-- name: ListPendingSyncQueueItems :many +SELECT id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at FROM sync_queue +WHERE device_id = $1 AND status = 'pending' +ORDER BY priority ASC, created_at ASC +LIMIT $2 +` + +type ListPendingSyncQueueItemsParams struct { + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + Limit int32 `db:"limit" json:"limit"` +} + +func (q *Queries) ListPendingSyncQueueItems(ctx context.Context, arg ListPendingSyncQueueItemsParams) ([]SyncQueue, error) { + rows, err := q.db.Query(ctx, ListPendingSyncQueueItems, arg.DeviceID, arg.Limit) + if err != nil { + return nil, err + } + defer rows.Close() + items := []SyncQueue{} + for rows.Next() { + var i SyncQueue + if err := rows.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.SyncType, + &i.SyncData, + &i.Priority, + &i.Attempts, + &i.MaxAttempts, + &i.Status, + &i.ErrorMessage, + &i.CreatedAt, + &i.ProcessedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const ListSyncConflictsByMediaItem = `-- name: ListSyncConflictsByMediaItem :many +SELECT id, media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_by, resolved_at, created_at FROM sync_conflicts +WHERE media_item_id = $1 AND user_id = $2 +ORDER BY created_at DESC +` + +type ListSyncConflictsByMediaItemParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` +} + +func (q *Queries) ListSyncConflictsByMediaItem(ctx context.Context, arg ListSyncConflictsByMediaItemParams) ([]SyncConflicts, error) { + rows, err := q.db.Query(ctx, ListSyncConflictsByMediaItem, arg.MediaItemID, arg.UserID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []SyncConflicts{} + for rows.Next() { + var i SyncConflicts + if err := rows.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.ConflictType, + &i.ConflictData, + &i.ResolutionStatus, + &i.ResolutionData, + &i.ResolvedBy, + &i.ResolvedAt, + &i.CreatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const ListSyncConflictsByUser = `-- name: ListSyncConflictsByUser :many +SELECT sc.id, sc.media_item_id, sc.user_id, sc.conflict_type, sc.conflict_data, sc.resolution_status, sc.resolution_data, sc.resolved_by, sc.resolved_at, sc.created_at, mi.title, mi.author +FROM sync_conflicts sc +JOIN media_items mi ON sc.media_item_id = mi.id +WHERE sc.user_id = $1 AND sc.resolution_status = 'unresolved' +ORDER BY sc.created_at DESC +` + +type ListSyncConflictsByUserRow struct { + ID pgtype.UUID `db:"id" json:"id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + ConflictType string `db:"conflict_type" json:"conflict_type"` + ConflictData []byte `db:"conflict_data" json:"conflict_data"` + ResolutionStatus pgtype.Text `db:"resolution_status" json:"resolution_status"` + ResolutionData []byte `db:"resolution_data" json:"resolution_data"` + ResolvedBy pgtype.UUID `db:"resolved_by" json:"resolved_by"` + ResolvedAt pgtype.Timestamptz `db:"resolved_at" json:"resolved_at"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` +} + +func (q *Queries) ListSyncConflictsByUser(ctx context.Context, userID pgtype.UUID) ([]ListSyncConflictsByUserRow, error) { + rows, err := q.db.Query(ctx, ListSyncConflictsByUser, userID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListSyncConflictsByUserRow{} + for rows.Next() { + var i ListSyncConflictsByUserRow + if err := rows.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.ConflictType, + &i.ConflictData, + &i.ResolutionStatus, + &i.ResolutionData, + &i.ResolvedBy, + &i.ResolvedAt, + &i.CreatedAt, + &i.Title, + &i.Author, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const ListUnresolvedUnlinkedBooks = `-- name: ListUnresolvedUnlinkedBooks :many +SELECT ub.id, ub.device_id, ub.content_id, ub.file_path, ub.title, ub.author, ub.confidence_score, ub.resolved, ub.media_item_id, ub.resolved_at, ub.resolution_method, ub.last_seen_at, ub.created_at, d.device_name, d.device_type +FROM unlinked_books ub +JOIN devices d ON ub.device_id = d.id +WHERE ub.resolved = false +ORDER BY ub.last_seen_at DESC +LIMIT $1 OFFSET $2 +` + +type ListUnresolvedUnlinkedBooksParams struct { + Limit int32 `db:"limit" json:"limit"` + Offset int32 `db:"offset" json:"offset"` +} + +type ListUnresolvedUnlinkedBooksRow struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceID pgtype.UUID `db:"device_id" json:"device_id"` + ContentID string `db:"content_id" json:"content_id"` + FilePath pgtype.Text `db:"file_path" json:"file_path"` + Title pgtype.Text `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"` + Resolved pgtype.Bool `db:"resolved" json:"resolved"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + ResolvedAt pgtype.Timestamptz `db:"resolved_at" json:"resolved_at"` + ResolutionMethod pgtype.Text `db:"resolution_method" json:"resolution_method"` + LastSeenAt pgtype.Timestamptz `db:"last_seen_at" json:"last_seen_at"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + DeviceName string `db:"device_name" json:"device_name"` + DeviceType string `db:"device_type" json:"device_type"` +} + +// List unresolved unlinked books with pagination +func (q *Queries) ListUnresolvedUnlinkedBooks(ctx context.Context, arg ListUnresolvedUnlinkedBooksParams) ([]ListUnresolvedUnlinkedBooksRow, error) { + rows, err := q.db.Query(ctx, ListUnresolvedUnlinkedBooks, arg.Limit, arg.Offset) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListUnresolvedUnlinkedBooksRow{} + for rows.Next() { + var i ListUnresolvedUnlinkedBooksRow + if err := rows.Scan( + &i.ID, + &i.DeviceID, + &i.ContentID, + &i.FilePath, + &i.Title, + &i.Author, + &i.ConfidenceScore, + &i.Resolved, + &i.MediaItemID, + &i.ResolvedAt, + &i.ResolutionMethod, + &i.LastSeenAt, + &i.CreatedAt, + &i.DeviceName, + &i.DeviceType, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const ListUsers = `-- name: ListUsers :many +SELECT + u.id, + u.email, + u.username, + u.theme, + u.first_name, + u.last_name, + u.role, + u.max_devices, + u.created_at, + u.updated_at, + (SELECT COUNT(*) FROM devices WHERE user_id = u.id) as device_count +FROM users u +ORDER BY u.created_at DESC +` + +type ListUsersRow struct { + ID pgtype.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + Theme pgtype.Text `db:"theme" json:"theme"` + FirstName pgtype.Text `db:"first_name" json:"first_name"` + LastName pgtype.Text `db:"last_name" json:"last_name"` + Role string `db:"role" json:"role"` + MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + DeviceCount int64 `db:"device_count" json:"device_count"` +} + +func (q *Queries) ListUsers(ctx context.Context) ([]ListUsersRow, error) { + rows, err := q.db.Query(ctx, ListUsers) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListUsersRow{} + for rows.Next() { + var i ListUsersRow + if err := rows.Scan( + &i.ID, + &i.Email, + &i.Username, + &i.Theme, + &i.FirstName, + &i.LastName, + &i.Role, + &i.MaxDevices, + &i.CreatedAt, + &i.UpdatedAt, + &i.DeviceCount, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const QueryMediaItemsByIdentifiers = `-- name: QueryMediaItemsByIdentifiers :many +SELECT + mi.id, + mi.file_sha256, + mi.opf_identifier, + mi.opf_uuid, + mi.isbn, + mi.asin, + mi.title, + mi.author, + mi.file_size, + mi.hash_confidence, + CASE + WHEN $1::uuid IS NOT NULL AND mi.id = $1::uuid THEN 1.0 + WHEN mi.opf_uuid IS NOT NULL AND mi.opf_uuid = $2::text THEN 0.95 + WHEN mi.file_sha256 IS NOT NULL AND mi.file_sha256 = $3::text THEN 0.9 + WHEN mi.opf_identifier IS NOT NULL AND mi.opf_identifier = $4::text THEN 0.85 + WHEN mi.isbn IS NOT NULL AND mi.isbn = $5::text THEN 0.8 + WHEN mi.asin IS NOT NULL AND mi.asin = $6::text THEN 0.8 + ELSE 0.5 + END as confidence_score +FROM media_items mi +WHERE + ($1::uuid IS NULL OR mi.id = $1::uuid) + OR ($2::text IS NULL OR mi.opf_uuid = $2::text) + OR ($3::text IS NULL OR mi.file_sha256 = $3::text) + OR ($4::text IS NULL OR mi.opf_identifier = $4::text) + OR ($5::text IS NULL OR mi.isbn = $5::text) + OR ($6::text IS NULL OR mi.asin = $6::text) + OR ($7::text IS NULL OR mi.title ILIKE '%' || $7::text || '%') +ORDER BY confidence_score DESC +` + +type QueryMediaItemsByIdentifiersParams struct { + Column1 pgtype.UUID `db:"column_1" json:"column_1"` + Column2 string `db:"column_2" json:"column_2"` + Column3 string `db:"column_3" json:"column_3"` + Column4 string `db:"column_4" json:"column_4"` + Column5 string `db:"column_5" json:"column_5"` + Column6 string `db:"column_6" json:"column_6"` + Column7 string `db:"column_7" json:"column_7"` +} + +type QueryMediaItemsByIdentifiersRow struct { + ID pgtype.UUID `db:"id" json:"id"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"` + OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"` + Isbn pgtype.Text `db:"isbn" json:"isbn"` + Asin pgtype.Text `db:"asin" json:"asin"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + FileSize pgtype.Int8 `db:"file_size" json:"file_size"` + HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"` + ConfidenceScore float64 `db:"confidence_score" json:"confidence_score"` +} + +// Query media items by multiple identifiers with confidence scoring +func (q *Queries) QueryMediaItemsByIdentifiers(ctx context.Context, arg QueryMediaItemsByIdentifiersParams) ([]QueryMediaItemsByIdentifiersRow, error) { + rows, err := q.db.Query(ctx, QueryMediaItemsByIdentifiers, + arg.Column1, + arg.Column2, + arg.Column3, + arg.Column4, + arg.Column5, + arg.Column6, + arg.Column7, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []QueryMediaItemsByIdentifiersRow{} + for rows.Next() { + var i QueryMediaItemsByIdentifiersRow + if err := rows.Scan( + &i.ID, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.Isbn, + &i.Asin, + &i.Title, + &i.Author, + &i.FileSize, + &i.HashConfidence, + &i.ConfidenceScore, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const RemoveBookFromCollection = `-- name: RemoveBookFromCollection :exec +DELETE FROM collection_items WHERE collection_id = $1 AND media_item_id = $2 +` + +type RemoveBookFromCollectionParams struct { + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` +} + +// Remove book from collection +func (q *Queries) RemoveBookFromCollection(ctx context.Context, arg RemoveBookFromCollectionParams) error { + _, err := q.db.Exec(ctx, RemoveBookFromCollection, arg.CollectionID, arg.MediaItemID) + return err +} + +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 ResetSystemCollectionMetadata = `-- name: ResetSystemCollectionMetadata :exec +UPDATE collections +SET description = $3, + icon = $4, + color = $5, + priority = $6 +WHERE user_id = $1 + AND name = $2 + AND is_system_collection = true +` + +type ResetSystemCollectionMetadataParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Name string `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` + Icon pgtype.Text `db:"icon" json:"icon"` + Color pgtype.Text `db:"color" json:"color"` + Priority pgtype.Int4 `db:"priority" json:"priority"` +} + +func (q *Queries) ResetSystemCollectionMetadata(ctx context.Context, arg ResetSystemCollectionMetadataParams) error { + _, err := q.db.Exec(ctx, ResetSystemCollectionMetadata, + arg.UserID, + arg.Name, + arg.Description, + arg.Icon, + arg.Color, + arg.Priority, + ) + return err +} + +const ResolveSyncConflict = `-- name: ResolveSyncConflict :one +UPDATE sync_conflicts +SET + resolution_status = $2, + resolution_data = $3, + resolved_by = $4, + resolved_at = NOW() +WHERE id = $1 +RETURNING id, media_item_id, user_id, conflict_type, conflict_data, resolution_status, resolution_data, resolved_by, resolved_at, created_at +` + +type ResolveSyncConflictParams struct { + ID pgtype.UUID `db:"id" json:"id"` + ResolutionStatus pgtype.Text `db:"resolution_status" json:"resolution_status"` + ResolutionData []byte `db:"resolution_data" json:"resolution_data"` + ResolvedBy pgtype.UUID `db:"resolved_by" json:"resolved_by"` +} + +func (q *Queries) ResolveSyncConflict(ctx context.Context, arg ResolveSyncConflictParams) (SyncConflicts, error) { + row := q.db.QueryRow(ctx, ResolveSyncConflict, + arg.ID, + arg.ResolutionStatus, + arg.ResolutionData, + arg.ResolvedBy, + ) + 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 ResolveUnlinkedBook = `-- name: ResolveUnlinkedBook :one +UPDATE unlinked_books +SET + resolved = true, + media_item_id = $2, + resolved_at = NOW(), + resolution_method = $3 +WHERE id = $1 +RETURNING id, device_id, content_id, file_path, title, author, confidence_score, resolved, media_item_id, resolved_at, resolution_method, last_seen_at, created_at +` + +type ResolveUnlinkedBookParams struct { + ID pgtype.UUID `db:"id" json:"id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + ResolutionMethod pgtype.Text `db:"resolution_method" json:"resolution_method"` +} + +// Resolve unlinked book +func (q *Queries) ResolveUnlinkedBook(ctx context.Context, arg ResolveUnlinkedBookParams) (UnlinkedBooks, error) { + row := q.db.QueryRow(ctx, ResolveUnlinkedBook, arg.ID, arg.MediaItemID, arg.ResolutionMethod) + var i UnlinkedBooks + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.ContentID, + &i.FilePath, + &i.Title, + &i.Author, + &i.ConfidenceScore, + &i.Resolved, + &i.MediaItemID, + &i.ResolvedAt, + &i.ResolutionMethod, + &i.LastSeenAt, + &i.CreatedAt, + ) + return i, err +} + +const RevokeAllUserRefreshTokens = `-- name: RevokeAllUserRefreshTokens :exec +UPDATE refresh_tokens SET revoked_at = NOW() WHERE user_id = $1 AND revoked_at IS NULL +` + +func (q *Queries) RevokeAllUserRefreshTokens(ctx context.Context, userID pgtype.UUID) error { + _, err := q.db.Exec(ctx, RevokeAllUserRefreshTokens, userID) + return err +} + +const RevokeDevice = `-- name: RevokeDevice :exec +UPDATE devices +SET + auth_token = NULL, + sync_enabled = false, + updated_at = NOW() +WHERE id = $1 +` + +func (q *Queries) RevokeDevice(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, RevokeDevice, id) + return err +} + +const RevokeOpdsToken = `-- name: RevokeOpdsToken :exec +DELETE FROM opds_tokens WHERE token = $1 +` + +// Revoke OPDS token +func (q *Queries) RevokeOpdsToken(ctx context.Context, token string) error { + _, err := q.db.Exec(ctx, RevokeOpdsToken, token) + return err +} + +const RevokeRefreshToken = `-- name: RevokeRefreshToken :exec +UPDATE refresh_tokens SET revoked_at = NOW() WHERE token = $1 +` + +func (q *Queries) RevokeRefreshToken(ctx context.Context, token pgtype.UUID) error { + _, err := q.db.Exec(ctx, RevokeRefreshToken, token) + return err +} + +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, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, 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 +LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $1 + WHERE COALESCE(lv.is_visible, true) = true + AND ($2 IS NULL OR mi.library_id = $2) + AND ( + mi.title ILIKE $3 OR + mi.author ILIKE $3 OR + mi.series ILIKE $3 OR + $3 = ANY(mi.tags_search) OR + $3 = ANY(mi.contributors_search) + ) + ORDER BY + CASE + WHEN mi.title ILIKE $3 THEN 1 + WHEN mi.author ILIKE $3 THEN 2 + WHEN mi.series ILIKE $3 THEN 3 + WHEN $3 = ANY(mi.tags_search) THEN 4 + ELSE 5 + END, + mi.title ASC +LIMIT $5 OFFSET $4 +` + +type SearchMediaItemsParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + LibraryID interface{} `db:"library_id" json:"library_id"` + SearchPattern pgtype.Text `db:"search_pattern" json:"search_pattern"` + Offset pgtype.Int4 `db:"offset" json:"offset"` + Limit pgtype.Int4 `db:"limit" json:"limit"` +} + +type SearchMediaItemsRow struct { + ID pgtype.UUID `db:"id" json:"id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + Isbn pgtype.Text `db:"isbn" json:"isbn"` + Description pgtype.Text `db:"description" json:"description"` + FilePath string `db:"file_path" json:"file_path"` + FileSize pgtype.Int8 `db:"file_size" json:"file_size"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` + Series pgtype.Text `db:"series" json:"series"` + SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` + Tags []string `db:"tags" json:"tags"` + Asin pgtype.Text `db:"asin" json:"asin"` + DatePublished pgtype.Date `db:"date_published" json:"date_published"` + Publisher pgtype.Text `db:"publisher" json:"publisher"` + Contributors []string `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` + AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + 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"` + TagsSearch []string `db:"tags_search" json:"tags_search"` + ContributorsSearch []string `db:"contributors_search" json:"contributors_search"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"` + OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"` + HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"` + LibraryName string `db:"library_name" json:"library_name"` + LibraryTypeName string `db:"library_type_name" json:"library_type_name"` +} + +// Search Media Items queries +func (q *Queries) SearchMediaItems(ctx context.Context, arg SearchMediaItemsParams) ([]SearchMediaItemsRow, error) { + rows, err := q.db.Query(ctx, SearchMediaItems, + arg.UserID, + arg.LibraryID, + arg.SearchPattern, + arg.Offset, + arg.Limit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []SearchMediaItemsRow{} + for rows.Next() { + var i SearchMediaItemsRow + if err := rows.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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + &i.LibraryName, + &i.LibraryTypeName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +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, mi.entitlement_id, mi.revision_number, mi.kobo_content_id, mi.kobo_metadata, mi.tags_search, mi.contributors_search, mi.file_sha256, mi.opf_identifier, mi.opf_uuid, mi.hash_confidence, 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 +LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = $1 +WHERE COALESCE(lv.is_visible, true) = true + AND ($2 IS NULL OR mi.library_id = $2) + AND ( + word_similarity($3, mi.title) > 0.3 OR + word_similarity($3, COALESCE(mi.author, '')) > 0.3 OR + word_similarity($3, COALESCE(mi.series, '')) > 0.3 OR + EXISTS ( + SELECT 1 FROM unnest(mi.tags_search) AS tag + WHERE word_similarity($3, tag) > 0.3 + LIMIT 1 + ) OR + EXISTS ( + SELECT 1 FROM unnest(mi.contributors_search) AS contributor + WHERE word_similarity($3, contributor) > 0.3 + LIMIT 1 + ) + ) + ORDER BY + GREATEST( + word_similarity($3, mi.title), + word_similarity($3, COALESCE(mi.author, '')), + word_similarity($3, COALESCE(mi.series, '')), + COALESCE( + (SELECT MAX(word_similarity($3, tag)) + FROM unnest(mi.tags_search) AS tag), + 0 + ), + COALESCE( + (SELECT MAX(word_similarity($3, contributor)) + FROM unnest(mi.contributors_search) AS contributor), + 0 + ) + ) DESC, + mi.title ASC + LIMIT $5 OFFSET $4 +` + +type SearchMediaItemsFuzzyParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + LibraryID interface{} `db:"library_id" json:"library_id"` + SearchQuery interface{} `db:"search_query" json:"search_query"` + Offset pgtype.Int4 `db:"offset" json:"offset"` + Limit pgtype.Int4 `db:"limit" json:"limit"` +} + +type SearchMediaItemsFuzzyRow struct { + ID pgtype.UUID `db:"id" json:"id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + Isbn pgtype.Text `db:"isbn" json:"isbn"` + Description pgtype.Text `db:"description" json:"description"` + FilePath string `db:"file_path" json:"file_path"` + FileSize pgtype.Int8 `db:"file_size" json:"file_size"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` + CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` + Series pgtype.Text `db:"series" json:"series"` + SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` + Tags []string `db:"tags" json:"tags"` + Asin pgtype.Text `db:"asin" json:"asin"` + DatePublished pgtype.Date `db:"date_published" json:"date_published"` + Publisher pgtype.Text `db:"publisher" json:"publisher"` + Contributors []string `db:"contributors" json:"contributors"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` + AddedByAdminID pgtype.UUID `db:"added_by_admin_id" json:"added_by_admin_id"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` + 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"` + TagsSearch []string `db:"tags_search" json:"tags_search"` + ContributorsSearch []string `db:"contributors_search" json:"contributors_search"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"` + OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"` + HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"` + LibraryName string `db:"library_name" json:"library_name"` + LibraryTypeName string `db:"library_type_name" json:"library_type_name"` +} + +func (q *Queries) SearchMediaItemsFuzzy(ctx context.Context, arg SearchMediaItemsFuzzyParams) ([]SearchMediaItemsFuzzyRow, error) { + rows, err := q.db.Query(ctx, SearchMediaItemsFuzzy, + arg.UserID, + arg.LibraryID, + arg.SearchQuery, + arg.Offset, + arg.Limit, + ) + if err != nil { + return nil, err + } + defer rows.Close() + items := []SearchMediaItemsFuzzyRow{} + for rows.Next() { + var i SearchMediaItemsFuzzyRow + if err := rows.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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + &i.LibraryName, + &i.LibraryTypeName, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const SetLibraryVisibility = `-- name: SetLibraryVisibility :one +INSERT INTO library_visibility (user_id, library_id, is_visible) +VALUES ($1, $2, $3) +ON CONFLICT (user_id, library_id) +DO UPDATE SET + is_visible = EXCLUDED.is_visible, + updated_at = NOW() +RETURNING id, user_id, library_id, is_visible, created_at, updated_at +` + +type SetLibraryVisibilityParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + IsVisible bool `db:"is_visible" json:"is_visible"` +} + +// Library Visibility queries +func (q *Queries) SetLibraryVisibility(ctx context.Context, arg SetLibraryVisibilityParams) (LibraryVisibility, error) { + row := q.db.QueryRow(ctx, SetLibraryVisibility, arg.UserID, arg.LibraryID, arg.IsVisible) + var i LibraryVisibility + err := row.Scan( + &i.ID, + &i.UserID, + &i.LibraryID, + &i.IsVisible, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const SetSystemConfig = `-- name: SetSystemConfig :one +INSERT INTO system_config (key, value, updated_by) +VALUES ($1, $2, $3) +ON CONFLICT (key) +DO UPDATE SET + value = EXCLUDED.value, + updated_by = EXCLUDED.updated_by, + updated_at = NOW() +RETURNING key, value, updated_at, updated_by +` + +type SetSystemConfigParams struct { + Key string `db:"key" json:"key"` + Value string `db:"value" json:"value"` + UpdatedBy pgtype.UUID `db:"updated_by" json:"updated_by"` +} + +// Set system config +func (q *Queries) SetSystemConfig(ctx context.Context, arg SetSystemConfigParams) (SystemConfig, error) { + row := q.db.QueryRow(ctx, SetSystemConfig, arg.Key, arg.Value, arg.UpdatedBy) + var i SystemConfig + err := row.Scan( + &i.Key, + &i.Value, + &i.UpdatedAt, + &i.UpdatedBy, + ) + return i, err +} + +const UpdateCollection = `-- name: UpdateCollection :one +UPDATE collections +SET + name = $2, + description = $3, + color = $4, + icon = $5, + auto_assign_rules = $6, + view_settings = $7 +WHERE id = $1 +RETURNING id, user_id, name, description, color, icon, auto_assign_rules, view_settings, show_on_dashboard, query_type, priority, is_system_collection, created_at +` + +type UpdateCollectionParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` + Color pgtype.Text `db:"color" json:"color"` + Icon pgtype.Text `db:"icon" json:"icon"` + AutoAssignRules []byte `db:"auto_assign_rules" json:"auto_assign_rules"` + ViewSettings []byte `db:"view_settings" json:"view_settings"` +} + +// Update collection +func (q *Queries) UpdateCollection(ctx context.Context, arg UpdateCollectionParams) (Collections, error) { + row := q.db.QueryRow(ctx, UpdateCollection, + arg.ID, + arg.Name, + arg.Description, + arg.Color, + arg.Icon, + arg.AutoAssignRules, + arg.ViewSettings, + ) + var i Collections + err := row.Scan( + &i.ID, + &i.UserID, + &i.Name, + &i.Description, + &i.Color, + &i.Icon, + &i.AutoAssignRules, + &i.ViewSettings, + &i.ShowOnDashboard, + &i.QueryType, + &i.Priority, + &i.IsSystemCollection, + &i.CreatedAt, + ) + return i, err +} + +const UpdateDashboardPreferences = `-- name: UpdateDashboardPreferences :one +UPDATE user_dashboard_preferences +SET hidden_collections = $2, + collection_order = $3, + items_per_section = $4, + updated_at = NOW() +WHERE user_id = $1 AND library_id = $5 +RETURNING id, user_id, library_id, hidden_collections, collection_order, items_per_section, created_at, updated_at +` + +type UpdateDashboardPreferencesParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + HiddenCollections []string `db:"hidden_collections" json:"hidden_collections"` + CollectionOrder []string `db:"collection_order" json:"collection_order"` + ItemsPerSection pgtype.Int4 `db:"items_per_section" json:"items_per_section"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` +} + +func (q *Queries) UpdateDashboardPreferences(ctx context.Context, arg UpdateDashboardPreferencesParams) (UserDashboardPreferences, error) { + row := q.db.QueryRow(ctx, UpdateDashboardPreferences, + arg.UserID, + arg.HiddenCollections, + arg.CollectionOrder, + arg.ItemsPerSection, + arg.LibraryID, + ) + var i UserDashboardPreferences + err := row.Scan( + &i.ID, + &i.UserID, + &i.LibraryID, + &i.HiddenCollections, + &i.CollectionOrder, + &i.ItemsPerSection, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpdateDevice = `-- name: UpdateDevice :one +UPDATE devices +SET + device_name = $2, + sync_enabled = $3, + auto_sync = $4, + sync_frequency_minutes = $5, + device_metadata = $6, + updated_at = NOW() +WHERE id = $1 +RETURNING 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 +` + +type UpdateDeviceParams struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceName string `db:"device_name" json:"device_name"` + SyncEnabled pgtype.Bool `db:"sync_enabled" json:"sync_enabled"` + AutoSync pgtype.Bool `db:"auto_sync" json:"auto_sync"` + SyncFrequencyMinutes pgtype.Int4 `db:"sync_frequency_minutes" json:"sync_frequency_minutes"` + DeviceMetadata []byte `db:"device_metadata" json:"device_metadata"` +} + +func (q *Queries) UpdateDevice(ctx context.Context, arg UpdateDeviceParams) (Devices, error) { + row := q.db.QueryRow(ctx, UpdateDevice, + arg.ID, + arg.DeviceName, + arg.SyncEnabled, + arg.AutoSync, + arg.SyncFrequencyMinutes, + arg.DeviceMetadata, + ) + var i Devices + err := row.Scan( + &i.ID, + &i.UserID, + &i.DeviceName, + &i.DeviceType, + &i.DeviceIdentifier, + &i.AuthToken, + &i.LastSync, + &i.LastSeen, + &i.SyncEnabled, + &i.AutoSync, + &i.SyncFrequencyMinutes, + &i.DeviceMetadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpdateDeviceAuthToken = `-- name: UpdateDeviceAuthToken :one +UPDATE devices +SET + auth_token = $2, + updated_at = NOW() +WHERE id = $1 +RETURNING 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 +` + +type UpdateDeviceAuthTokenParams struct { + ID pgtype.UUID `db:"id" json:"id"` + AuthToken string `db:"auth_token" json:"auth_token"` +} + +func (q *Queries) UpdateDeviceAuthToken(ctx context.Context, arg UpdateDeviceAuthTokenParams) (Devices, error) { + row := q.db.QueryRow(ctx, UpdateDeviceAuthToken, arg.ID, arg.AuthToken) + var i Devices + err := row.Scan( + &i.ID, + &i.UserID, + &i.DeviceName, + &i.DeviceType, + &i.DeviceIdentifier, + &i.AuthToken, + &i.LastSync, + &i.LastSeen, + &i.SyncEnabled, + &i.AutoSync, + &i.SyncFrequencyMinutes, + &i.DeviceMetadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpdateDeviceCatalogAvailability = `-- name: UpdateDeviceCatalogAvailability :exec +UPDATE device_catalogs +SET available = $2 +WHERE id = $1 +` + +type UpdateDeviceCatalogAvailabilityParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Available pgtype.Bool `db:"available" json:"available"` +} + +// Update device catalog availability +func (q *Queries) UpdateDeviceCatalogAvailability(ctx context.Context, arg UpdateDeviceCatalogAvailabilityParams) error { + _, err := q.db.Exec(ctx, UpdateDeviceCatalogAvailability, arg.ID, arg.Available) + return err +} + +const UpdateDeviceFileAlias = `-- name: UpdateDeviceFileAlias :one +UPDATE device_file_aliases +SET + media_item_id = $2, + file_sha256 = $3, + confidence_score = $4, + last_seen_at = NOW() +WHERE id = $1 +RETURNING id, media_item_id, device_id, file_path, file_sha256, confidence_score, last_seen_at +` + +type UpdateDeviceFileAliasParams struct { + ID pgtype.UUID `db:"id" json:"id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + ConfidenceScore pgtype.Float8 `db:"confidence_score" json:"confidence_score"` +} + +// Update device file alias +func (q *Queries) UpdateDeviceFileAlias(ctx context.Context, arg UpdateDeviceFileAliasParams) (DeviceFileAliases, error) { + row := q.db.QueryRow(ctx, UpdateDeviceFileAlias, + arg.ID, + arg.MediaItemID, + arg.FileSha256, + arg.ConfidenceScore, + ) + var i DeviceFileAliases + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.DeviceID, + &i.FilePath, + &i.FileSha256, + &i.ConfidenceScore, + &i.LastSeenAt, + ) + return i, err +} + +const UpdateDeviceLastSeen = `-- name: UpdateDeviceLastSeen :one +UPDATE devices +SET + last_seen = NOW(), + updated_at = NOW() +WHERE id = $1 +RETURNING 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 +` + +func (q *Queries) UpdateDeviceLastSeen(ctx context.Context, id pgtype.UUID) (Devices, error) { + row := q.db.QueryRow(ctx, UpdateDeviceLastSeen, id) + var i Devices + err := row.Scan( + &i.ID, + &i.UserID, + &i.DeviceName, + &i.DeviceType, + &i.DeviceIdentifier, + &i.AuthToken, + &i.LastSync, + &i.LastSeen, + &i.SyncEnabled, + &i.AutoSync, + &i.SyncFrequencyMinutes, + &i.DeviceMetadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpdateDeviceLastSync = `-- name: UpdateDeviceLastSync :one +UPDATE devices +SET + last_sync = NOW(), + last_seen = NOW(), + updated_at = NOW() +WHERE id = $1 +RETURNING 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 +` + +func (q *Queries) UpdateDeviceLastSync(ctx context.Context, id pgtype.UUID) (Devices, error) { + row := q.db.QueryRow(ctx, UpdateDeviceLastSync, id) + var i Devices + err := row.Scan( + &i.ID, + &i.UserID, + &i.DeviceName, + &i.DeviceType, + &i.DeviceIdentifier, + &i.AuthToken, + &i.LastSync, + &i.LastSeen, + &i.SyncEnabled, + &i.AutoSync, + &i.SyncFrequencyMinutes, + &i.DeviceMetadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpdateDeviceShelfMapping = `-- name: UpdateDeviceShelfMapping :one +UPDATE device_shelf_mappings +SET + device_shelf_name = $2, + sync_direction = $3 +WHERE id = $1 +RETURNING id, collection_id, device_id, device_shelf_name, sync_direction, created_at +` + +type UpdateDeviceShelfMappingParams struct { + ID pgtype.UUID `db:"id" json:"id"` + DeviceShelfName pgtype.Text `db:"device_shelf_name" json:"device_shelf_name"` + SyncDirection pgtype.Text `db:"sync_direction" json:"sync_direction"` +} + +// Update device shelf mapping +func (q *Queries) UpdateDeviceShelfMapping(ctx context.Context, arg UpdateDeviceShelfMappingParams) (DeviceShelfMappings, error) { + row := q.db.QueryRow(ctx, UpdateDeviceShelfMapping, arg.ID, arg.DeviceShelfName, arg.SyncDirection) + var i DeviceShelfMappings + err := row.Scan( + &i.ID, + &i.CollectionID, + &i.DeviceID, + &i.DeviceShelfName, + &i.SyncDirection, + &i.CreatedAt, + ) + return i, err +} + +const UpdateDeviceSyncTimestamp = `-- name: UpdateDeviceSyncTimestamp :one +UPDATE devices +SET + last_sync = NOW(), + last_seen = NOW(), + updated_at = NOW() +WHERE id = $1 +RETURNING 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 +` + +func (q *Queries) UpdateDeviceSyncTimestamp(ctx context.Context, id pgtype.UUID) (Devices, error) { + row := q.db.QueryRow(ctx, UpdateDeviceSyncTimestamp, id) + var i Devices + err := row.Scan( + &i.ID, + &i.UserID, + &i.DeviceName, + &i.DeviceType, + &i.DeviceIdentifier, + &i.AuthToken, + &i.LastSync, + &i.LastSeen, + &i.SyncEnabled, + &i.AutoSync, + &i.SyncFrequencyMinutes, + &i.DeviceMetadata, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpdateEmail = `-- name: UpdateEmail :exec +UPDATE users SET email = $2, updated_at = NOW() WHERE id = $1 +` + +type UpdateEmailParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` +} + +func (q *Queries) UpdateEmail(ctx context.Context, arg UpdateEmailParams) error { + _, err := q.db.Exec(ctx, UpdateEmail, arg.ID, arg.Email) + 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 UpdateKoboShelfCollection = `-- name: UpdateKoboShelfCollection :one +UPDATE kobo_shelves +SET + collection_id = $2, + position_in_collection = $3, + last_synced_at = NOW() +WHERE id = $1 +RETURNING id, device_id, media_item_id, shelf_name, shelf_position, added_at, last_synced_at, collection_id, position_in_collection +` + +type UpdateKoboShelfCollectionParams struct { + ID pgtype.UUID `db:"id" json:"id"` + CollectionID pgtype.UUID `db:"collection_id" json:"collection_id"` + PositionInCollection pgtype.Int4 `db:"position_in_collection" json:"position_in_collection"` +} + +// Update Kobo shelves to support collections +func (q *Queries) UpdateKoboShelfCollection(ctx context.Context, arg UpdateKoboShelfCollectionParams) (KoboShelves, error) { + row := q.db.QueryRow(ctx, UpdateKoboShelfCollection, arg.ID, arg.CollectionID, arg.PositionInCollection) + var i KoboShelves + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.ShelfName, + &i.ShelfPosition, + &i.AddedAt, + &i.LastSyncedAt, + &i.CollectionID, + &i.PositionInCollection, + ) + return i, err +} + +const UpdateLibrary = `-- name: UpdateLibrary :one +UPDATE libraries SET + name = $2, + description = $3, + updated_at = NOW() +WHERE id = $1 +RETURNING id, name, description, library_type_id, created_by_admin_id, created_at, updated_at +` + +type UpdateLibraryParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Description pgtype.Text `db:"description" json:"description"` +} + +func (q *Queries) UpdateLibrary(ctx context.Context, arg UpdateLibraryParams) (Libraries, error) { + row := q.db.QueryRow(ctx, UpdateLibrary, arg.ID, arg.Name, arg.Description) + var i Libraries + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.LibraryTypeID, + &i.CreatedByAdminID, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpdateMediaHighlight = `-- name: UpdateMediaHighlight :one +UPDATE media_highlights SET + selection_text = $2, + start_position = $3, + end_position = $4, + color = $5, + 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 +` + +type UpdateMediaHighlightParams 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"` + NoteID pgtype.UUID `db:"note_id" json:"note_id"` +} + +func (q *Queries) UpdateMediaHighlight(ctx context.Context, arg UpdateMediaHighlightParams) (MediaHighlights, error) { + row := q.db.QueryRow(ctx, UpdateMediaHighlight, + arg.ID, + arg.SelectionText, + arg.StartPosition, + arg.EndPosition, + arg.Color, + arg.NoteID, + ) + 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, + ) + return i, err +} + +const UpdateMediaItem = `-- name: UpdateMediaItem :one +UPDATE media_items SET + title = $2, + author = $3, + isbn = $4, + description = $5, + cover_image_path = $6, + series = $7, + series_number = $8, + tags = $9, + tags_search = $10, + asin = $11, + date_published = $12, + publisher = $13, + contributors = $14, + contributors_search = $15, + language = $16, + edition = $17, + page_count = $18, + genre = $19, + copyright_year = $20, + goodreads_id = $21, + openlibrary_id = $22, + google_books_id = $23, + 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, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence +` + +type UpdateMediaItemParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Title string `db:"title" json:"title"` + Author pgtype.Text `db:"author" json:"author"` + Isbn pgtype.Text `db:"isbn" json:"isbn"` + Description pgtype.Text `db:"description" json:"description"` + CoverImagePath pgtype.Text `db:"cover_image_path" json:"cover_image_path"` + Series pgtype.Text `db:"series" json:"series"` + SeriesNumber pgtype.Int4 `db:"series_number" json:"series_number"` + Tags []string `db:"tags" json:"tags"` + TagsSearch []string `db:"tags_search" json:"tags_search"` + Asin pgtype.Text `db:"asin" json:"asin"` + DatePublished pgtype.Date `db:"date_published" json:"date_published"` + Publisher pgtype.Text `db:"publisher" json:"publisher"` + Contributors []string `db:"contributors" json:"contributors"` + ContributorsSearch []string `db:"contributors_search" json:"contributors_search"` + Language pgtype.Text `db:"language" json:"language"` + Edition pgtype.Text `db:"edition" json:"edition"` + PageCount pgtype.Int4 `db:"page_count" json:"page_count"` + Genre pgtype.Text `db:"genre" json:"genre"` + CopyrightYear pgtype.Int4 `db:"copyright_year" json:"copyright_year"` + GoodreadsID pgtype.Text `db:"goodreads_id" json:"goodreads_id"` + OpenlibraryID pgtype.Text `db:"openlibrary_id" json:"openlibrary_id"` + GoogleBooksID pgtype.Text `db:"google_books_id" json:"google_books_id"` +} + +func (q *Queries) UpdateMediaItem(ctx context.Context, arg UpdateMediaItemParams) (MediaItems, error) { + row := q.db.QueryRow(ctx, UpdateMediaItem, + arg.ID, + arg.Title, + arg.Author, + arg.Isbn, + arg.Description, + arg.CoverImagePath, + arg.Series, + arg.SeriesNumber, + arg.Tags, + arg.TagsSearch, + arg.Asin, + arg.DatePublished, + arg.Publisher, + arg.Contributors, + arg.ContributorsSearch, + arg.Language, + arg.Edition, + arg.PageCount, + arg.Genre, + arg.CopyrightYear, + arg.GoodreadsID, + arg.OpenlibraryID, + arg.GoogleBooksID, + ) + 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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ) + return i, err +} + +const UpdateMediaItemFormat = `-- name: UpdateMediaItemFormat :one +UPDATE media_item_formats +SET + file_path = $2, + file_sha256 = $3, + file_size_bytes = $4, + mime_type = $5 +WHERE id = $1 +RETURNING id, media_item_id, format_type, file_path, file_sha256, file_size_bytes, mime_type, created_at, converted_from_format_id +` + +type UpdateMediaItemFormatParams struct { + ID pgtype.UUID `db:"id" json:"id"` + FilePath pgtype.Text `db:"file_path" json:"file_path"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + FileSizeBytes pgtype.Int8 `db:"file_size_bytes" json:"file_size_bytes"` + MimeType pgtype.Text `db:"mime_type" json:"mime_type"` +} + +// Update media item format +func (q *Queries) UpdateMediaItemFormat(ctx context.Context, arg UpdateMediaItemFormatParams) (MediaItemFormats, error) { + row := q.db.QueryRow(ctx, UpdateMediaItemFormat, + arg.ID, + arg.FilePath, + arg.FileSha256, + arg.FileSizeBytes, + arg.MimeType, + ) + var i MediaItemFormats + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.FormatType, + &i.FilePath, + &i.FileSha256, + &i.FileSizeBytes, + &i.MimeType, + &i.CreatedAt, + &i.ConvertedFromFormatID, + ) + return i, err +} + +const UpdateMediaItemFormatGroup = `-- name: UpdateMediaItemFormatGroup :exec + +UPDATE media_items +SET + format_group = $2, + format_mimetype = $3, + is_reflowable = $4, + has_fixed_layout = $5, + total_characters = $6, + chapter_count = $7, + updated_at = NOW() +WHERE id = $1 +` + +type UpdateMediaItemFormatGroupParams struct { + ID pgtype.UUID `db:"id" json:"id"` + FormatGroup string `db:"format_group" json:"format_group"` + FormatMimetype pgtype.Text `db:"format_mimetype" json:"format_mimetype"` + IsReflowable pgtype.Bool `db:"is_reflowable" json:"is_reflowable"` + HasFixedLayout pgtype.Bool `db:"has_fixed_layout" json:"has_fixed_layout"` + TotalCharacters pgtype.Int8 `db:"total_characters" json:"total_characters"` + ChapterCount pgtype.Int4 `db:"chapter_count" json:"chapter_count"` +} + +// ============================================ +// FORMAT DETECTION & PROGRESS +// ============================================ +// Update media item format group information +func (q *Queries) UpdateMediaItemFormatGroup(ctx context.Context, arg UpdateMediaItemFormatGroupParams) error { + _, err := q.db.Exec(ctx, UpdateMediaItemFormatGroup, + arg.ID, + arg.FormatGroup, + arg.FormatMimetype, + arg.IsReflowable, + arg.HasFixedLayout, + arg.TotalCharacters, + arg.ChapterCount, + ) + return err +} + +const UpdateMediaItemIdentifiers = `-- name: UpdateMediaItemIdentifiers :one + + +UPDATE media_items +SET + file_sha256 = $2, + opf_identifier = $3, + opf_uuid = $4, + hash_confidence = $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, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence +` + +type UpdateMediaItemIdentifiersParams struct { + ID pgtype.UUID `db:"id" json:"id"` + FileSha256 pgtype.Text `db:"file_sha256" json:"file_sha256"` + OpfIdentifier pgtype.Text `db:"opf_identifier" json:"opf_identifier"` + OpfUuid pgtype.Text `db:"opf_uuid" json:"opf_uuid"` + HashConfidence pgtype.Text `db:"hash_confidence" json:"hash_confidence"` +} + +// Media Items Admin Operations +// ============================================ +// UNIVERSAL BOOK IDENTIFIERS +// ============================================ +// Update media item with universal identifiers +func (q *Queries) UpdateMediaItemIdentifiers(ctx context.Context, arg UpdateMediaItemIdentifiersParams) (MediaItems, error) { + row := q.db.QueryRow(ctx, UpdateMediaItemIdentifiers, + arg.ID, + arg.FileSha256, + arg.OpfIdentifier, + arg.OpfUuid, + arg.HashConfidence, + ) + 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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ) + return i, 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, tags_search, contributors_search, file_sha256, opf_identifier, opf_uuid, hash_confidence +` + +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, + &i.TagsSearch, + &i.ContributorsSearch, + &i.FileSha256, + &i.OpfIdentifier, + &i.OpfUuid, + &i.HashConfidence, + ) + return i, err +} + +const UpdateMediaNote = `-- name: UpdateMediaNote :one +UPDATE media_notes SET + content = $2, + 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 +` + +type UpdateMediaNoteParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Content string `db:"content" json:"content"` + Position pgtype.Text `db:"position" json:"position"` +} + +func (q *Queries) UpdateMediaNote(ctx context.Context, arg UpdateMediaNoteParams) (MediaNotes, error) { + row := q.db.QueryRow(ctx, UpdateMediaNote, arg.ID, arg.Content, arg.Position) + 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, + ) + return i, err +} + +const UpdateMediaRating = `-- name: UpdateMediaRating :one +UPDATE media_ratings SET + rating = $3, + updated_at = NOW() +WHERE media_item_id = $1 AND user_id = $2 +RETURNING id, media_item_id, user_id, rating, created_at, updated_at +` + +type UpdateMediaRatingParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Rating int32 `db:"rating" json:"rating"` +} + +func (q *Queries) UpdateMediaRating(ctx context.Context, arg UpdateMediaRatingParams) (MediaRatings, error) { + row := q.db.QueryRow(ctx, UpdateMediaRating, arg.MediaItemID, arg.UserID, arg.Rating) + var i MediaRatings + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.Rating, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpdatePassword = `-- name: UpdatePassword :exec +UPDATE users SET password_hash = $2, updated_at = NOW() WHERE id = $1 +` + +type UpdatePasswordParams struct { + ID pgtype.UUID `db:"id" json:"id"` + PasswordHash string `db:"password_hash" json:"password_hash"` +} + +func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error { + _, err := q.db.Exec(ctx, UpdatePassword, arg.ID, arg.PasswordHash) + return err +} + +const UpdateReadingProgress = `-- name: UpdateReadingProgress :one +INSERT INTO reading_progress (media_item_id, user_id, current_page, total_pages, last_read_at) +VALUES ($1, $2, $3, $4, NOW()) +ON CONFLICT (media_item_id, user_id) +DO UPDATE SET + current_page = EXCLUDED.current_page, + total_pages = EXCLUDED.total_pages, + last_read_at = NOW() +RETURNING id, media_item_id, user_id, current_page, total_pages, last_read_at, percentage, character_offset, epubcfi, chapter, chapter_progress, viewport_x, viewport_y, zoom_level, scroll_position_x, scroll_position_y, panel_number, reading_mode, last_sync_device, last_sync_source, last_sync_timestamp, conflict_detected, conflict_resolved +` + +type UpdateReadingProgressParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"` + TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"` +} + +func (q *Queries) UpdateReadingProgress(ctx context.Context, arg UpdateReadingProgressParams) (ReadingProgress, error) { + row := q.db.QueryRow(ctx, UpdateReadingProgress, + arg.MediaItemID, + arg.UserID, + arg.CurrentPage, + arg.TotalPages, + ) + var i ReadingProgress + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.CurrentPage, + &i.TotalPages, + &i.LastReadAt, + &i.Percentage, + &i.CharacterOffset, + &i.Epubcfi, + &i.Chapter, + &i.ChapterProgress, + &i.ViewportX, + &i.ViewportY, + &i.ZoomLevel, + &i.ScrollPositionX, + &i.ScrollPositionY, + &i.PanelNumber, + &i.ReadingMode, + &i.LastSyncDevice, + &i.LastSyncSource, + &i.LastSyncTimestamp, + &i.ConflictDetected, + &i.ConflictResolved, + ) + return i, err +} + +const UpdateSyncQueueItemStatus = `-- name: UpdateSyncQueueItemStatus :one +UPDATE sync_queue +SET + status = $2, + attempts = attempts + 1, + error_message = $3, + processed_at = CASE WHEN $2::varchar = 'completed' THEN NOW() ELSE NULL END +WHERE id = $1 +RETURNING id, device_id, media_item_id, sync_type, sync_data, priority, attempts, max_attempts, status, error_message, created_at, processed_at +` + +type UpdateSyncQueueItemStatusParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Status pgtype.Text `db:"status" json:"status"` + ErrorMessage pgtype.Text `db:"error_message" json:"error_message"` +} + +func (q *Queries) UpdateSyncQueueItemStatus(ctx context.Context, arg UpdateSyncQueueItemStatusParams) (SyncQueue, error) { + row := q.db.QueryRow(ctx, UpdateSyncQueueItemStatus, arg.ID, arg.Status, arg.ErrorMessage) + var i SyncQueue + err := row.Scan( + &i.ID, + &i.DeviceID, + &i.MediaItemID, + &i.SyncType, + &i.SyncData, + &i.Priority, + &i.Attempts, + &i.MaxAttempts, + &i.Status, + &i.ErrorMessage, + &i.CreatedAt, + &i.ProcessedAt, + ) + return i, err +} + +const UpdateSystemSetting = `-- name: UpdateSystemSetting :exec +UPDATE system_settings SET setting_value = $2, updated_at = NOW() WHERE setting_key = $1 +` + +type UpdateSystemSettingParams struct { + SettingKey string `db:"setting_key" json:"setting_key"` + SettingValue string `db:"setting_value" json:"setting_value"` +} + +func (q *Queries) UpdateSystemSetting(ctx context.Context, arg UpdateSystemSettingParams) error { + _, err := q.db.Exec(ctx, UpdateSystemSetting, arg.SettingKey, arg.SettingValue) + return err +} + +const UpdateUniversalProgress = `-- name: UpdateUniversalProgress :one +INSERT INTO reading_progress ( + media_item_id, + user_id, + percentage, + character_offset, + epubcfi, + chapter, + chapter_progress, + viewport_x, + viewport_y, + zoom_level, + scroll_position_x, + scroll_position_y, + panel_number, + reading_mode, + last_sync_device, + last_sync_source, + last_sync_timestamp, + current_page, + total_pages, + last_read_at +) +VALUES ( + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, NOW(), $17, $18, NOW() +) +ON CONFLICT (media_item_id, user_id) +DO UPDATE SET + percentage = EXCLUDED.percentage, + character_offset = EXCLUDED.character_offset, + epubcfi = EXCLUDED.epubcfi, + chapter = EXCLUDED.chapter, + chapter_progress = EXCLUDED.chapter_progress, + viewport_x = EXCLUDED.viewport_x, + viewport_y = EXCLUDED.viewport_y, + zoom_level = EXCLUDED.zoom_level, + scroll_position_x = EXCLUDED.scroll_position_x, + scroll_position_y = EXCLUDED.scroll_position_y, + panel_number = EXCLUDED.panel_number, + reading_mode = EXCLUDED.reading_mode, + last_sync_device = EXCLUDED.last_sync_device, + last_sync_source = EXCLUDED.last_sync_source, + last_sync_timestamp = EXCLUDED.last_sync_timestamp, + current_page = EXCLUDED.current_page, + total_pages = EXCLUDED.total_pages, + last_read_at = NOW() +RETURNING id, media_item_id, user_id, current_page, total_pages, last_read_at, percentage, character_offset, epubcfi, chapter, chapter_progress, viewport_x, viewport_y, zoom_level, scroll_position_x, scroll_position_y, panel_number, reading_mode, last_sync_device, last_sync_source, last_sync_timestamp, conflict_detected, conflict_resolved +` + +type UpdateUniversalProgressParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + UserID pgtype.UUID `db:"user_id" json:"user_id"` + Percentage pgtype.Float8 `db:"percentage" json:"percentage"` + CharacterOffset pgtype.Int8 `db:"character_offset" json:"character_offset"` + Epubcfi pgtype.Text `db:"epubcfi" json:"epubcfi"` + Chapter pgtype.Int4 `db:"chapter" json:"chapter"` + ChapterProgress pgtype.Float8 `db:"chapter_progress" json:"chapter_progress"` + ViewportX pgtype.Float8 `db:"viewport_x" json:"viewport_x"` + ViewportY pgtype.Float8 `db:"viewport_y" json:"viewport_y"` + ZoomLevel pgtype.Float8 `db:"zoom_level" json:"zoom_level"` + ScrollPositionX pgtype.Float8 `db:"scroll_position_x" json:"scroll_position_x"` + ScrollPositionY pgtype.Float8 `db:"scroll_position_y" json:"scroll_position_y"` + PanelNumber pgtype.Int4 `db:"panel_number" json:"panel_number"` + ReadingMode pgtype.Text `db:"reading_mode" json:"reading_mode"` + LastSyncDevice pgtype.Text `db:"last_sync_device" json:"last_sync_device"` + LastSyncSource pgtype.Text `db:"last_sync_source" json:"last_sync_source"` + CurrentPage pgtype.Int4 `db:"current_page" json:"current_page"` + TotalPages pgtype.Int4 `db:"total_pages" json:"total_pages"` +} + +// Update universal progress +func (q *Queries) UpdateUniversalProgress(ctx context.Context, arg UpdateUniversalProgressParams) (ReadingProgress, error) { + row := q.db.QueryRow(ctx, UpdateUniversalProgress, + arg.MediaItemID, + arg.UserID, + arg.Percentage, + arg.CharacterOffset, + arg.Epubcfi, + arg.Chapter, + arg.ChapterProgress, + arg.ViewportX, + arg.ViewportY, + arg.ZoomLevel, + arg.ScrollPositionX, + arg.ScrollPositionY, + arg.PanelNumber, + arg.ReadingMode, + arg.LastSyncDevice, + arg.LastSyncSource, + arg.CurrentPage, + arg.TotalPages, + ) + var i ReadingProgress + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.UserID, + &i.CurrentPage, + &i.TotalPages, + &i.LastReadAt, + &i.Percentage, + &i.CharacterOffset, + &i.Epubcfi, + &i.Chapter, + &i.ChapterProgress, + &i.ViewportX, + &i.ViewportY, + &i.ZoomLevel, + &i.ScrollPositionX, + &i.ScrollPositionY, + &i.PanelNumber, + &i.ReadingMode, + &i.LastSyncDevice, + &i.LastSyncSource, + &i.LastSyncTimestamp, + &i.ConflictDetected, + &i.ConflictResolved, + ) + return i, err +} + +const UpdateUserMaxDevices = `-- name: UpdateUserMaxDevices :one +UPDATE users SET max_devices = $2, updated_at = NOW() WHERE id = $1 +RETURNING id, email, username, password_hash, first_name, last_name, role, theme, max_devices, created_at, updated_at +` + +type UpdateUserMaxDevicesParams struct { + ID pgtype.UUID `db:"id" json:"id"` + MaxDevices pgtype.Int4 `db:"max_devices" json:"max_devices"` +} + +func (q *Queries) UpdateUserMaxDevices(ctx context.Context, arg UpdateUserMaxDevicesParams) (Users, error) { + row := q.db.QueryRow(ctx, UpdateUserMaxDevices, arg.ID, arg.MaxDevices) + var i Users + err := row.Scan( + &i.ID, + &i.Email, + &i.Username, + &i.PasswordHash, + &i.FirstName, + &i.LastName, + &i.Role, + &i.Theme, + &i.MaxDevices, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const UpdateUserProfile = `-- name: UpdateUserProfile :exec +UPDATE users SET first_name = $2, last_name = $3, updated_at = NOW() WHERE id = $1 +` + +type UpdateUserProfileParams struct { + ID pgtype.UUID `db:"id" json:"id"` + FirstName pgtype.Text `db:"first_name" json:"first_name"` + LastName pgtype.Text `db:"last_name" json:"last_name"` +} + +func (q *Queries) UpdateUserProfile(ctx context.Context, arg UpdateUserProfileParams) error { + _, err := q.db.Exec(ctx, UpdateUserProfile, arg.ID, arg.FirstName, arg.LastName) + return err +} + +const UpdateUserRole = `-- name: UpdateUserRole :one +UPDATE users SET role = $2, updated_at = NOW() WHERE id = $1 +RETURNING id, email, username, role +` + +type UpdateUserRoleParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Role string `db:"role" json:"role"` +} + +type UpdateUserRoleRow struct { + ID pgtype.UUID `db:"id" json:"id"` + Email string `db:"email" json:"email"` + Username string `db:"username" json:"username"` + Role string `db:"role" json:"role"` +} + +func (q *Queries) UpdateUserRole(ctx context.Context, arg UpdateUserRoleParams) (UpdateUserRoleRow, error) { + row := q.db.QueryRow(ctx, UpdateUserRole, arg.ID, arg.Role) + var i UpdateUserRoleRow + err := row.Scan( + &i.ID, + &i.Email, + &i.Username, + &i.Role, + ) + return i, err +} + +const UpdateUserTheme = `-- name: UpdateUserTheme :exec +UPDATE users SET theme = $2, updated_at = NOW() WHERE id = $1 +` + +type UpdateUserThemeParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Theme pgtype.Text `db:"theme" json:"theme"` +} + +func (q *Queries) UpdateUserTheme(ctx context.Context, arg UpdateUserThemeParams) error { + _, err := q.db.Exec(ctx, UpdateUserTheme, arg.ID, arg.Theme) + return err +} + +const UpdateUsername = `-- name: UpdateUsername :exec +UPDATE users SET username = $2, updated_at = NOW() WHERE id = $1 +` + +type UpdateUsernameParams struct { + ID pgtype.UUID `db:"id" json:"id"` + Username string `db:"username" json:"username"` +} + +func (q *Queries) UpdateUsername(ctx context.Context, arg UpdateUsernameParams) error { + _, err := q.db.Exec(ctx, UpdateUsername, arg.ID, arg.Username) + return err +} + +const UpsertDashboardPreferences = `-- name: UpsertDashboardPreferences :one +INSERT INTO user_dashboard_preferences (user_id, library_id, hidden_collections, collection_order, items_per_section) +VALUES ($1, $2, $3, $4, $5) +ON CONFLICT (user_id, library_id) +DO UPDATE SET + hidden_collections = EXCLUDED.hidden_collections, + collection_order = EXCLUDED.collection_order, + items_per_section = EXCLUDED.items_per_section, + updated_at = NOW() +RETURNING id, user_id, library_id, hidden_collections, collection_order, items_per_section, created_at, updated_at +` + +type UpsertDashboardPreferencesParams struct { + UserID pgtype.UUID `db:"user_id" json:"user_id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + HiddenCollections []string `db:"hidden_collections" json:"hidden_collections"` + CollectionOrder []string `db:"collection_order" json:"collection_order"` + ItemsPerSection pgtype.Int4 `db:"items_per_section" json:"items_per_section"` +} + +func (q *Queries) UpsertDashboardPreferences(ctx context.Context, arg UpsertDashboardPreferencesParams) (UserDashboardPreferences, error) { + row := q.db.QueryRow(ctx, UpsertDashboardPreferences, + arg.UserID, + arg.LibraryID, + arg.HiddenCollections, + arg.CollectionOrder, + arg.ItemsPerSection, + ) + var i UserDashboardPreferences + err := row.Scan( + &i.ID, + &i.UserID, + &i.LibraryID, + &i.HiddenCollections, + &i.CollectionOrder, + &i.ItemsPerSection, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/internal/database/queries/queries.sql b/internal/database/queries/queries.sql index ed92681..b56b5a9 100644 --- a/internal/database/queries/queries.sql +++ b/internal/database/queries/queries.sql @@ -397,7 +397,7 @@ JOIN libraries l ON mi.library_id = l.id JOIN library_types lt ON l.library_type_id = lt.id LEFT JOIN library_visibility lv ON l.id = lv.library_id AND lv.user_id = sqlc.narg('user_id') WHERE COALESCE(lv.is_visible, true) = true - AND (sqlc.narg('library_id') IS NULL OR mi.library_id = sqlc.narg('library_id')) + AND (sqlc.narg('library_id')::uuid IS NULL OR mi.library_id = sqlc.narg('library_id')::uuid) AND ( mi.title ILIKE sqlc.narg('search_pattern') OR mi.author ILIKE sqlc.narg('search_pattern') OR