diff --git a/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.spec.ts b/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.spec.ts index 6098810e9..a68063a27 100644 --- a/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.spec.ts +++ b/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.spec.ts @@ -392,6 +392,49 @@ describe('DashboardDataService', () => { ); }); + it('removes PWA Xtream favorites through the data source when Electron DB APIs are unavailable', async () => { + Object.defineProperty(window, 'electron', { + value: {} as Window['electron'], + configurable: true, + }); + xtreamDataSourceMock.getFavorites + .mockResolvedValueOnce([ + { + id: 20203, + stream_id: 20203, + category_id: 206, + title: 'PWA Movie', + type: 'movie', + poster_url: 'https://example.com/pwa-movie.png', + xtream_id: 20203, + added: '1774298340', + }, + ]) + .mockResolvedValueOnce([]); + + await service.reloadGlobalFavorites(); + const xtreamFavorite = service + .globalFavoriteItems() + .find((item) => item.source === 'xtream'); + + if (!xtreamFavorite) { + throw new Error('Expected PWA Xtream favorite'); + } + + await service.removeGlobalFavorite(xtreamFavorite); + + expect(dbServiceMock.removeFromFavorites).not.toHaveBeenCalled(); + expect(xtreamDataSourceMock.removeFavorite).toHaveBeenCalledWith( + 20203, + 'xtream-1' + ); + expect( + service + .globalFavoriteItems() + .some((item) => item.source === 'xtream') + ).toBe(false); + }); + it('builds the M3U favorites route', async () => { await service.reloadGlobalFavorites(); const m3uItem = service @@ -528,6 +571,47 @@ describe('DashboardDataService', () => { ); }); + it('removes PWA Xtream recent items through the data source when Electron DB APIs are unavailable', async () => { + Object.defineProperty(window, 'electron', { + value: {} as Window['electron'], + configurable: true, + }); + xtreamDataSourceMock.getRecentItems + .mockResolvedValueOnce([ + { + id: 20203, + stream_id: 20203, + category_id: 206, + title: 'PWA Recent Movie', + type: 'movie', + poster_url: 'https://example.com/pwa-recent.png', + xtream_id: 20203, + viewed_at: '2026-05-15T15:04:54.176Z', + }, + ]) + .mockResolvedValueOnce([]); + + await service.reloadGlobalRecentItems(); + const xtreamRecent = service + .globalRecentItems() + .find((item) => item.source === 'xtream'); + + if (!xtreamRecent) { + throw new Error('Expected PWA Xtream recent item'); + } + + await service.removeGlobalRecentItem(xtreamRecent); + + expect(dbServiceMock.removeRecentItem).not.toHaveBeenCalled(); + expect(xtreamDataSourceMock.removeRecentItem).toHaveBeenCalledWith( + 20203, + 'xtream-1' + ); + expect( + service.globalRecentItems().some((item) => item.source === 'xtream') + ).toBe(false); + }); + it('prioritizes recently used Xtream sources over newer imported M3U sources', async () => { playlistsSignal.set([ { diff --git a/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.ts b/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.ts index b8a92391e..a48a929eb 100644 --- a/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.ts +++ b/libs/workspace/dashboard/data-access/src/lib/dashboard-data.service.ts @@ -612,6 +612,19 @@ export class DashboardDataService { return typeof window.electron?.dbGetAllGlobalFavorites === 'function'; } + private getXtreamContentId( + item: Pick + ): number | null { + for (const candidate of [item.xtream_id, item.id]) { + const value = Number(candidate); + if (Number.isFinite(value) && value > 0) { + return value; + } + } + + return null; + } + private async reloadM3uGlobalFavorites(): Promise { const m3uPlaylists = this.playlists().filter( (playlist) => @@ -770,10 +783,20 @@ export class DashboardDataService { async removeGlobalRecentItem(item: GlobalRecentItem): Promise { if (item.source === 'xtream') { - await this.dbService.removeRecentItem( - item.id as number, - item.playlist_id - ); + if (this.hasElectronGlobalRecentApi()) { + await this.dbService.removeRecentItem( + item.id as number, + item.playlist_id + ); + } else { + const contentId = this.getXtreamContentId(item); + if (contentId != null) { + await this.xtreamDataSource.removeRecentItem( + contentId, + item.playlist_id + ); + } + } await this.reloadGlobalRecentItems(); return; } @@ -924,10 +947,20 @@ export class DashboardDataService { async removeGlobalFavorite(item: DashboardFavoriteItem): Promise { if (item.source === 'xtream') { - await this.dbService.removeFromFavorites( - item.id as number, - item.playlist_id - ); + if (this.hasElectronGlobalFavoritesApi()) { + await this.dbService.removeFromFavorites( + item.id as number, + item.playlist_id + ); + } else { + const contentId = this.getXtreamContentId(item); + if (contentId != null) { + await this.xtreamDataSource.removeFavorite( + contentId, + item.playlist_id + ); + } + } await this.reloadGlobalFavorites(); return; }