From 059955be7257d8a8165a5b272cd2fc84fbb4ebd5 Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sun, 12 Apr 2026 20:43:46 -0400 Subject: [PATCH] chore(db): Regenerate database code from processing issues queries - Add ProcessingIssues model struct - Update Querier interface with processing issues methods - Add generated query implementations for CreateProcessingIssue, ListProcessingIssuesByLibrary, GetProcessingIssueStats, ResolveProcessingIssue, DeleteProcessingIssue - Add GetLibraryWithType query for fetching library with type information --- internal/database/models.go | 13 ++ internal/database/querier.go | 12 ++ internal/database/queries.sql.go | 245 +++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+) diff --git a/internal/database/models.go b/internal/database/models.go index fcd10b5..b20110a 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -334,6 +334,19 @@ type PanelData struct { UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` } +// Tracks media items that cannot be properly processed in their assigned library due to format mismatches or other issues +type ProcessingIssues struct { + ID pgtype.UUID `db:"id" json:"id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + IssueType string `db:"issue_type" json:"issue_type"` + IssueDescription string `db:"issue_description" json:"issue_description"` + Severity string `db:"severity" json:"severity"` + Resolved pgtype.Bool `db:"resolved" json:"resolved"` + ResolvedAt pgtype.Timestamptz `db:"resolved_at" json:"resolved_at"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` +} + type ReaderSettings struct { ID pgtype.UUID `db:"id" json:"id"` UserID pgtype.UUID `db:"user_id" json:"user_id"` diff --git a/internal/database/querier.go b/internal/database/querier.go index 4fd6601..248f6bd 100644 --- a/internal/database/querier.go +++ b/internal/database/querier.go @@ -70,6 +70,10 @@ type Querier interface { // KOBO ENTITLEMENT QUERIES // ============================================ CreateOrUpdateKoboEntitlement(ctx context.Context, arg CreateOrUpdateKoboEntitlementParams) (KoboEntitlements, error) + // ============================================================================ + // PROCESSING ISSUES QUERIES + // ============================================================================ + CreateProcessingIssue(ctx context.Context, arg CreateProcessingIssueParams) (ProcessingIssues, error) // Create reading history entry CreateReadingHistory(ctx context.Context, arg CreateReadingHistoryParams) (ReadingHistory, error) CreateReadingSpeed(ctx context.Context, arg CreateReadingSpeedParams) (ReadingSpeed, error) @@ -108,6 +112,7 @@ type Querier interface { DeleteMediaItemFormat(ctx context.Context, id pgtype.UUID) error DeleteMediaNote(ctx context.Context, id pgtype.UUID) error DeleteMediaRating(ctx context.Context, arg DeleteMediaRatingParams) error + DeleteProcessingIssue(ctx context.Context, id pgtype.UUID) (ProcessingIssues, error) DeleteReadingProgress(ctx context.Context, arg DeleteReadingProgressParams) error DeleteSavedFilter(ctx context.Context, arg DeleteSavedFilterParams) (SavedFilters, error) DeleteSyncConflict(ctx context.Context, id pgtype.UUID) error @@ -179,6 +184,10 @@ type Querier interface { // Library Types queries GetLibraryTypes(ctx context.Context) ([]LibraryTypes, error) GetLibraryVisibility(ctx context.Context, arg GetLibraryVisibilityParams) (LibraryVisibility, error) + // ============================================================================ + // LIBRARY WITH TYPE INFO QUERIES + // ============================================================================ + GetLibraryWithType(ctx context.Context, id pgtype.UUID) (GetLibraryWithTypeRow, error) GetMediaBookmarks(ctx context.Context, arg GetMediaBookmarksParams) ([]MediaBookmarks, error) GetMediaHighlight(ctx context.Context, id pgtype.UUID) (MediaHighlights, error) GetMediaHighlights(ctx context.Context, arg GetMediaHighlightsParams) ([]MediaHighlights, error) @@ -215,6 +224,7 @@ type Querier interface { GetPanelData(ctx context.Context, arg GetPanelDataParams) (PanelData, error) // Get most popular books for a user GetPopularBooks(ctx context.Context, arg GetPopularBooksParams) ([]GetPopularBooksRow, error) + GetProcessingIssueStats(ctx context.Context, libraryID pgtype.UUID) (GetProcessingIssueStatsRow, error) GetReaderSettings(ctx context.Context, userID pgtype.UUID) ([]byte, error) // Get reading history for a user and book GetReadingHistory(ctx context.Context, arg GetReadingHistoryParams) ([]ReadingHistory, error) @@ -273,6 +283,7 @@ type Querier interface { ListMediaItemsByLibrary(ctx context.Context, libraryID pgtype.UUID) ([]ListMediaItemsByLibraryRow, error) ListMediaItemsSorted(ctx context.Context, arg ListMediaItemsSortedParams) ([]ListMediaItemsSortedRow, error) ListPendingSyncQueueItems(ctx context.Context, arg ListPendingSyncQueueItemsParams) ([]SyncQueue, error) + ListProcessingIssuesByLibrary(ctx context.Context, libraryID pgtype.UUID) ([]ListProcessingIssuesByLibraryRow, error) ListSyncConflictsByMediaItem(ctx context.Context, arg ListSyncConflictsByMediaItemParams) ([]SyncConflicts, error) ListSyncConflictsByUser(ctx context.Context, userID pgtype.UUID) ([]ListSyncConflictsByUserRow, error) // List unresolved unlinked books with pagination @@ -284,6 +295,7 @@ type Querier interface { RemoveBookFromCollection(ctx context.Context, arg RemoveBookFromCollectionParams) error RemoveBookFromKoboShelf(ctx context.Context, arg RemoveBookFromKoboShelfParams) error ResetSystemCollectionMetadata(ctx context.Context, arg ResetSystemCollectionMetadataParams) error + ResolveProcessingIssue(ctx context.Context, arg ResolveProcessingIssueParams) (ProcessingIssues, error) ResolveSyncConflict(ctx context.Context, arg ResolveSyncConflictParams) (SyncConflicts, error) // Resolve unlinked book ResolveUnlinkedBook(ctx context.Context, arg ResolveUnlinkedBookParams) (UnlinkedBooks, error) diff --git a/internal/database/queries.sql.go b/internal/database/queries.sql.go index f94f988..3467be9 100644 --- a/internal/database/queries.sql.go +++ b/internal/database/queries.sql.go @@ -1001,6 +1001,52 @@ func (q *Queries) CreateOrUpdateKoboEntitlement(ctx context.Context, arg CreateO return i, err } +const CreateProcessingIssue = `-- name: CreateProcessingIssue :one + +INSERT INTO processing_issues (media_item_id, library_id, issue_type, issue_description, severity) +VALUES ($1, $2, $3, $4, $5) +ON CONFLICT (media_item_id, issue_type) +DO UPDATE SET issue_description = EXCLUDED.issue_description, + severity = EXCLUDED.severity, + resolved = false, + resolved_at = NULL +RETURNING id, media_item_id, library_id, issue_type, issue_description, severity, resolved, resolved_at, created_at +` + +type CreateProcessingIssueParams struct { + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + LibraryID pgtype.UUID `db:"library_id" json:"library_id"` + IssueType string `db:"issue_type" json:"issue_type"` + IssueDescription string `db:"issue_description" json:"issue_description"` + Severity string `db:"severity" json:"severity"` +} + +// ============================================================================ +// PROCESSING ISSUES QUERIES +// ============================================================================ +func (q *Queries) CreateProcessingIssue(ctx context.Context, arg CreateProcessingIssueParams) (ProcessingIssues, error) { + row := q.db.QueryRow(ctx, CreateProcessingIssue, + arg.MediaItemID, + arg.LibraryID, + arg.IssueType, + arg.IssueDescription, + arg.Severity, + ) + var i ProcessingIssues + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.LibraryID, + &i.IssueType, + &i.IssueDescription, + &i.Severity, + &i.Resolved, + &i.ResolvedAt, + &i.CreatedAt, + ) + return i, err +} + const CreateReadingHistory = `-- name: CreateReadingHistory :one INSERT INTO reading_history ( user_id, @@ -1583,6 +1629,29 @@ func (q *Queries) DeleteMediaRating(ctx context.Context, arg DeleteMediaRatingPa return err } +const DeleteProcessingIssue = `-- name: DeleteProcessingIssue :one +DELETE FROM processing_issues +WHERE id = $1 +RETURNING id, media_item_id, library_id, issue_type, issue_description, severity, resolved, resolved_at, created_at +` + +func (q *Queries) DeleteProcessingIssue(ctx context.Context, id pgtype.UUID) (ProcessingIssues, error) { + row := q.db.QueryRow(ctx, DeleteProcessingIssue, id) + var i ProcessingIssues + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.LibraryID, + &i.IssueType, + &i.IssueDescription, + &i.Severity, + &i.Resolved, + &i.ResolvedAt, + &i.CreatedAt, + ) + return i, err +} + const DeleteReadingProgress = `-- name: DeleteReadingProgress :exec DELETE FROM reading_progress WHERE media_item_id = $1 AND user_id = $2 ` @@ -3432,6 +3501,52 @@ func (q *Queries) GetLibraryVisibility(ctx context.Context, arg GetLibraryVisibi return i, err } +const GetLibraryWithType = `-- name: GetLibraryWithType :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, + lt.allowed_extensions +FROM libraries l +JOIN library_types lt ON l.library_type_id = lt.id +WHERE l.id = $1 +` + +type GetLibraryWithTypeRow 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"` + AllowedExtensions []string `db:"allowed_extensions" json:"allowed_extensions"` +} + +// ============================================================================ +// LIBRARY WITH TYPE INFO QUERIES +// ============================================================================ +func (q *Queries) GetLibraryWithType(ctx context.Context, id pgtype.UUID) (GetLibraryWithTypeRow, error) { + row := q.db.QueryRow(ctx, GetLibraryWithType, id) + var i GetLibraryWithTypeRow + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.LibraryTypeID, + &i.CreatedByAdminID, + &i.CreatedAt, + &i.UpdatedAt, + &i.TypeName, + &i.TypeDescription, + &i.AllowedExtensions, + ) + return i, err +} + const GetMediaBookmarks = `-- name: GetMediaBookmarks :many SELECT id, media_item_id, user_id, page_number, chapter_number, cfi_position, title, position, notes, created_at FROM media_bookmarks WHERE media_item_id = $1 AND user_id = $2 @@ -4638,6 +4753,28 @@ func (q *Queries) GetPopularBooks(ctx context.Context, arg GetPopularBooksParams return items, nil } +const GetProcessingIssueStats = `-- name: GetProcessingIssueStats :one +SELECT + COUNT(*) FILTER (WHERE severity = 'error' AND resolved = false) as error_count, + COUNT(*) FILTER (WHERE severity = 'warning' AND resolved = false) as warning_count, + COUNT(*) FILTER (WHERE severity = 'info' AND resolved = false) as info_count +FROM processing_issues +WHERE library_id = $1 +` + +type GetProcessingIssueStatsRow struct { + ErrorCount int64 `db:"error_count" json:"error_count"` + WarningCount int64 `db:"warning_count" json:"warning_count"` + InfoCount int64 `db:"info_count" json:"info_count"` +} + +func (q *Queries) GetProcessingIssueStats(ctx context.Context, libraryID pgtype.UUID) (GetProcessingIssueStatsRow, error) { + row := q.db.QueryRow(ctx, GetProcessingIssueStats, libraryID) + var i GetProcessingIssueStatsRow + err := row.Scan(&i.ErrorCount, &i.WarningCount, &i.InfoCount) + return i, err +} + const GetReaderSettings = `-- name: GetReaderSettings :one SELECT setting_value FROM reader_settings WHERE user_id = $1 AND setting_key = 'reader_settings' @@ -6986,6 +7123,83 @@ func (q *Queries) ListPendingSyncQueueItems(ctx context.Context, arg ListPending return items, nil } +const ListProcessingIssuesByLibrary = `-- name: ListProcessingIssuesByLibrary :many +SELECT + pi.id, + pi.media_item_id, + pi.issue_type, + pi.issue_description, + pi.severity, + pi.resolved, + pi.resolved_at, + pi.created_at, + mi.title, + mi.file_path, + mi.format_group, + lt.name as library_type_name +FROM processing_issues pi +JOIN media_items mi ON pi.media_item_id = mi.id +JOIN libraries l ON pi.library_id = l.id +JOIN library_types lt ON l.library_type_id = lt.id +WHERE pi.library_id = $1 + AND pi.resolved = false +ORDER BY + CASE pi.severity + WHEN 'error' THEN 1 + WHEN 'warning' THEN 2 + WHEN 'info' THEN 3 + END, + pi.created_at DESC +` + +type ListProcessingIssuesByLibraryRow struct { + ID pgtype.UUID `db:"id" json:"id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` + IssueType string `db:"issue_type" json:"issue_type"` + IssueDescription string `db:"issue_description" json:"issue_description"` + Severity string `db:"severity" json:"severity"` + Resolved pgtype.Bool `db:"resolved" json:"resolved"` + ResolvedAt pgtype.Timestamptz `db:"resolved_at" json:"resolved_at"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + Title string `db:"title" json:"title"` + FilePath string `db:"file_path" json:"file_path"` + FormatGroup string `db:"format_group" json:"format_group"` + LibraryTypeName string `db:"library_type_name" json:"library_type_name"` +} + +func (q *Queries) ListProcessingIssuesByLibrary(ctx context.Context, libraryID pgtype.UUID) ([]ListProcessingIssuesByLibraryRow, error) { + rows, err := q.db.Query(ctx, ListProcessingIssuesByLibrary, libraryID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListProcessingIssuesByLibraryRow{} + for rows.Next() { + var i ListProcessingIssuesByLibraryRow + if err := rows.Scan( + &i.ID, + &i.MediaItemID, + &i.IssueType, + &i.IssueDescription, + &i.Severity, + &i.Resolved, + &i.ResolvedAt, + &i.CreatedAt, + &i.Title, + &i.FilePath, + &i.FormatGroup, + &i.LibraryTypeName, + ); 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 @@ -7375,6 +7589,37 @@ func (q *Queries) ResetSystemCollectionMetadata(ctx context.Context, arg ResetSy return err } +const ResolveProcessingIssue = `-- name: ResolveProcessingIssue :one +UPDATE processing_issues +SET resolved = true, + resolved_at = NOW() +WHERE id = $1 + AND media_item_id = $2 +RETURNING id, media_item_id, library_id, issue_type, issue_description, severity, resolved, resolved_at, created_at +` + +type ResolveProcessingIssueParams struct { + ID pgtype.UUID `db:"id" json:"id"` + MediaItemID pgtype.UUID `db:"media_item_id" json:"media_item_id"` +} + +func (q *Queries) ResolveProcessingIssue(ctx context.Context, arg ResolveProcessingIssueParams) (ProcessingIssues, error) { + row := q.db.QueryRow(ctx, ResolveProcessingIssue, arg.ID, arg.MediaItemID) + var i ProcessingIssues + err := row.Scan( + &i.ID, + &i.MediaItemID, + &i.LibraryID, + &i.IssueType, + &i.IssueDescription, + &i.Severity, + &i.Resolved, + &i.ResolvedAt, + &i.CreatedAt, + ) + return i, err +} + const ResolveSyncConflict = `-- name: ResolveSyncConflict :one UPDATE sync_conflicts SET