From 7ee2cc4eeac277c453c958c6b2fb6e135ed7b082 Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 6 Dec 2025 14:46:37 +0100 Subject: [PATCH] feat(db): add API to delete all playlists and related rows Add a new IPC handler_DELETE_ALL_PLAYLIST that removes all and related data from the SQLite database in a safe order that respects foreign key relationships. The handler deletes favorites and recently viewed entries first, then content, categories, and finally playlists. Errors are logged and propagated. Expose the new operation in the renderer preload API as dbDeleteAllPlaylists() to allow UI code to invoke the purge. This provides a single-call way to clear playlists and their dependent records for testing or user-driven reset. --- .../src/app/api/main.preload.ts | 1 + .../src/app/events/database.events.ts | 28 ++++++++++++ libs/m3u-state/src/lib/effects.ts | 43 ++++++++++++++++--- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/apps/electron-backend/src/app/api/main.preload.ts b/apps/electron-backend/src/app/api/main.preload.ts index b91fafabb..777495ce6 100644 --- a/apps/electron-backend/src/app/api/main.preload.ts +++ b/apps/electron-backend/src/app/api/main.preload.ts @@ -136,4 +136,5 @@ contextBridge.exposeInMainWorld('electron', { ipcRenderer.invoke('DB_REMOVE_RECENT_ITEM', contentId, playlistId), dbGetContentByXtreamId: (xtreamId: number, playlistId: string) => ipcRenderer.invoke('DB_GET_CONTENT_BY_XTREAM_ID', xtreamId, playlistId), + dbDeleteAllPlaylists: () => ipcRenderer.invoke('DB_DELETE_ALL_PLAYLISTS'), }); diff --git a/apps/electron-backend/src/app/events/database.events.ts b/apps/electron-backend/src/app/events/database.events.ts index 0e19f1548..82fad2b88 100644 --- a/apps/electron-backend/src/app/events/database.events.ts +++ b/apps/electron-backend/src/app/events/database.events.ts @@ -985,3 +985,31 @@ ipcMain.handle( } } ); + +/** + * Delete all playlists and related data from SQLite + */ +ipcMain.handle('DB_DELETE_ALL_PLAYLISTS', async () => { + try { + const db = await getDatabase(); + + // Delete in order respecting foreign key constraints + // First delete favorites and recently_viewed (they reference content) + await db.delete(schema.favorites); + await db.delete(schema.recentlyViewed); + + // Then delete content (references categories) + await db.delete(schema.content); + + // Then delete categories (references playlists) + await db.delete(schema.categories); + + // Finally delete playlists + await db.delete(schema.playlists); + + return { success: true }; + } catch (error) { + console.error('Error deleting all playlists:', error); + throw error; + } +}); diff --git a/libs/m3u-state/src/lib/effects.ts b/libs/m3u-state/src/lib/effects.ts index bc8d44346..749d91b05 100644 --- a/libs/m3u-state/src/lib/effects.ts +++ b/libs/m3u-state/src/lib/effects.ts @@ -17,6 +17,7 @@ import { } from 'rxjs'; import { DataService, EpgService, PlaylistsService } from 'services'; import { + GLOBAL_FAVORITES_PLAYLIST_ID, OPEN_MPV_PLAYER, OPEN_VLC_PLAYER, Playlist, @@ -71,6 +72,10 @@ export class PlaylistEffects { return this.actions$.pipe( ofType(PlaylistActions.setFavorites), combineLatestWith(this.store.select(selectActivePlaylistId)), + filter( + ([, playlistId]) => + playlistId !== GLOBAL_FAVORITES_PLAYLIST_ID + ), switchMap(([action, playlistId]) => this.playlistsService.setFavorites( playlistId, @@ -207,9 +212,25 @@ export class PlaylistEffects { () => { return this.actions$.pipe( ofType(PlaylistActions.removePlaylist), - switchMap((action) => - this.playlistsService.deletePlaylist(action.playlistId) - ) + switchMap(async (action) => { + // Delete from IndexedDB + await firstValueFrom( + this.playlistsService.deletePlaylist(action.playlistId) + ); + // Also delete from SQLite if running in Electron + if ((window as any).electron?.dbDeletePlaylist) { + try { + await (window as any).electron.dbDeletePlaylist( + action.playlistId + ); + } catch (error) { + console.error( + 'Error deleting playlist from SQLite:', + error + ); + } + } + }) ); }, { dispatch: false } @@ -350,8 +371,20 @@ export class PlaylistEffects { () => { return this.actions$.pipe( ofType(PlaylistActions.removeAllPlaylists), - switchMap(() => this.playlistsService.removeAll()), - tap(() => { + switchMap(async () => { + // Delete from IndexedDB + await firstValueFrom(this.playlistsService.removeAll()); + // Also delete from SQLite if running in Electron + if ((window as any).electron?.dbDeleteAllPlaylists) { + try { + await (window as any).electron.dbDeleteAllPlaylists(); + } catch (error) { + console.error( + 'Error deleting playlists from SQLite:', + error + ); + } + } this.snackBar.open( this.translate.instant('SETTINGS.PLAYLISTS_REMOVED'), undefined,