From ad528238b2049ca7f3062bad6bd6de9ea96a5f4b Mon Sep 17 00:00:00 2001 From: 4gray Date: Wed, 22 Apr 2026 22:20:48 +0200 Subject: [PATCH] feat: add backdrop_url to content schema and related database operations --- global.d.ts | 18 ++++- .../src/lib/database-electron.service.ts | 71 ++++++++++++++++--- libs/shared/database/src/lib/connection.ts | 3 + libs/shared/database/src/lib/schema.ts | 1 + 4 files changed, 82 insertions(+), 11 deletions(-) diff --git a/global.d.ts b/global.d.ts index 7b43cdba5..80b615bb3 100644 --- a/global.d.ts +++ b/global.d.ts @@ -222,14 +222,21 @@ declare global { ) => Promise; dbGetGlobalRecentlyAdded: ( kind: 'all' | 'vod' | 'series', - limit?: number + limit?: number, + playlistType?: + | 'xtream' + | 'stalker' + | 'm3u-file' + | 'm3u-text' + | 'm3u-url' ) => Promise; dbGetRecentlyViewed: () => Promise; dbClearRecentlyViewed: () => Promise<{ success: boolean }>; // Favorites dbAddFavorite: ( contentId: number, - playlistId: string + playlistId: string, + backdropUrl?: string ) => Promise<{ success: boolean }>; dbRemoveFavorite: ( contentId: number, @@ -249,7 +256,8 @@ declare global { dbGetRecentItems: (playlistId: string) => Promise; dbAddRecentItem: ( contentId: number, - playlistId: string + playlistId: string, + backdropUrl?: string ) => Promise<{ success: boolean }>; dbClearPlaylistRecentItems: ( playlistId: string @@ -263,6 +271,10 @@ declare global { playlistId: string, contentType?: 'live' | 'movie' | 'series' ) => Promise; + dbSetContentBackdropIfMissing: ( + contentId: number, + backdropUrl?: string + ) => Promise<{ success: boolean }>; dbGetAppState: (key: string) => Promise; dbSetAppState: ( key: string, diff --git a/libs/services/src/lib/database-electron.service.ts b/libs/services/src/lib/database-electron.service.ts index b4000caad..6905855aa 100644 --- a/libs/services/src/lib/database-electron.service.ts +++ b/libs/services/src/lib/database-electron.service.ts @@ -28,6 +28,7 @@ export interface XtreamContent { rating: string; added: string; poster_url: string; + backdrop_url?: string | null; epg_channel_id?: string | null; tv_archive?: number | null; tv_archive_duration?: number | null; @@ -124,6 +125,13 @@ export function isDbAbortError(error: unknown): boolean { export type GlobalRecentlyAddedKind = 'all' | 'vod' | 'series'; +export type GlobalRecentlyAddedPlaylistType = + | 'xtream' + | 'stalker' + | 'm3u-file' + | 'm3u-text' + | 'm3u-url'; + export interface GlobalRecentlyAddedItem extends XtreamContent { playlist_id: string; playlist_name: string; @@ -581,15 +589,20 @@ export class DatabaseService { /** * Get recently added VOD and series items across all Xtream playlists. + * When `playlistType` is supplied, the LIMIT window is applied only to + * rows from playlists of that type, so Xtream items cannot be squeezed + * out by newer M3U/Stalker content. */ async getGlobalRecentlyAdded( kind: GlobalRecentlyAddedKind, - limit = 200 + limit = 200, + playlistType?: GlobalRecentlyAddedPlaylistType ): Promise { try { const items = await window.electron.dbGetGlobalRecentlyAdded( kind, - limit + limit, + playlistType ); return items || []; } catch (error) { @@ -671,14 +684,22 @@ export class DatabaseService { } /** - * Add content to favorites + * Add content to favorites. + * @param backdropUrl optionally persisted to `content.backdrop_url` when + * the row doesn't already have one. Lets the dashboard hero surface a + * cinematic backdrop without a separate round-trip. */ async addToFavorites( contentId: number, - playlistId: string + playlistId: string, + backdropUrl?: string ): Promise { try { - await window.electron.dbAddFavorite(contentId, playlistId); + await window.electron.dbAddFavorite( + contentId, + playlistId, + backdropUrl + ); return true; } catch (error) { console.error('Error adding to favorites:', error); @@ -739,14 +760,19 @@ export class DatabaseService { } /** - * Add item to recently viewed + * Add item to recently viewed. See `addToFavorites` for `backdropUrl`. */ async addRecentItem( contentId: number, - playlistId: string + playlistId: string, + backdropUrl?: string ): Promise { try { - await window.electron.dbAddRecentItem(contentId, playlistId); + await window.electron.dbAddRecentItem( + contentId, + playlistId, + backdropUrl + ); return true; } catch (error) { console.error('Error adding recent item:', error); @@ -754,6 +780,35 @@ export class DatabaseService { } } + /** + * Persist a backdrop URL onto an Xtream content row without touching + * recently viewed ordering or timestamps. + */ + async setContentBackdropIfMissing( + contentId: number, + backdropUrl?: string + ): Promise { + const normalizedBackdropUrl = backdropUrl?.trim(); + if (!normalizedBackdropUrl) { + return true; + } + + if (!window.electron?.dbSetContentBackdropIfMissing) { + return true; + } + + try { + await window.electron.dbSetContentBackdropIfMissing( + contentId, + normalizedBackdropUrl + ); + return true; + } catch (error) { + console.error('Error backfilling content backdrop:', error); + return false; + } + } + /** * Clear recently viewed for a specific playlist */ diff --git a/libs/shared/database/src/lib/connection.ts b/libs/shared/database/src/lib/connection.ts index 0b170007b..9ca1e003b 100644 --- a/libs/shared/database/src/lib/connection.ts +++ b/libs/shared/database/src/lib/connection.ts @@ -112,6 +112,7 @@ const CREATE_TABLE_STATEMENTS = [ rating TEXT, added TEXT, poster_url TEXT, + backdrop_url TEXT, epg_channel_id TEXT, tv_archive INTEGER, tv_archive_duration INTEGER, @@ -269,6 +270,8 @@ const COLUMN_MIGRATION_STATEMENTS = [ `ALTER TABLE content ADD COLUMN tv_archive INTEGER`, `ALTER TABLE content ADD COLUMN tv_archive_duration INTEGER`, `ALTER TABLE content ADD COLUMN direct_source TEXT`, + // v1.5.0 -> v1.6.0: Cinematic backdrop persisted on first detail fetch + `ALTER TABLE content ADD COLUMN backdrop_url TEXT`, ]; const INDEX_MIGRATION_STATEMENTS = [ diff --git a/libs/shared/database/src/lib/schema.ts b/libs/shared/database/src/lib/schema.ts index d033e9e8f..d34a53ef8 100644 --- a/libs/shared/database/src/lib/schema.ts +++ b/libs/shared/database/src/lib/schema.ts @@ -87,6 +87,7 @@ export const content = sqliteTable( rating: text('rating'), added: text('added'), posterUrl: text('poster_url'), + backdropUrl: text('backdrop_url'), epgChannelId: text('epg_channel_id'), tvArchive: integer('tv_archive'), tvArchiveDuration: integer('tv_archive_duration'),