mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 20:56:19 -08:00
refactor(portals): lift the VOD route's orchestration out of the component
The details route had grown to 864 lines — the repository's hard maximum is 400, and while the file predates the rule, a baselined exemption is not a budget to spend. Three component-provided services now hold what the component was accumulating: `VodDetailsMultiSourceUiService` (the playback-evidence latch, the caption, the primary button's position, source actions and the failover toast), `VodDetailsSimilarService` (the rail and its cross-portal lookup), and `VodDetailsDownloadsService`. The component keeps its public API, so the template and the existing specs are untouched. 864 -> 566 lines. The downloads move also fixes a latent bug: `downloadVod` and `playFromLocal` read `route.snapshot.params`, which is stale once the router reuses this component for detail-to-detail navigation (the Similar rail) — so a download started from a film reached that way fetched the previous one. They now read the same route-params signal everything else uses, with a regression test. Also from review: pins are applied on the FRESH-import path too. A new playlist has no content when the archive is read, so its user state is parked and replayed after the import — the merge path I wired first never ran there, and every pin was dropped. A failed pin write now propagates instead of being ignored: the backup entry is reported failed, and the parked state is kept so a transient failure can be retried rather than silently losing the preference. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7eaa681352
commit
a25412ea61
12 files changed
+761
-356
No files matched your search
@@ -867,13 +867,22 @@ export class PlaylistBackupService {
|
||||
// The match key identifies the film and survives untouched; only the
|
||||
// playlist has a new id in this installation.
|
||||
for (const pin of state.sourcePins ?? []) {
|
||||
await this.vodSourcePinService.set({
|
||||
const written = await this.vodSourcePinService.set({
|
||||
matchKey: pin.matchKey,
|
||||
playlistId,
|
||||
contentId: pin.contentId,
|
||||
portalType: 'xtream',
|
||||
...(pin.updatedAt ? { updatedAt: pin.updatedAt } : {}),
|
||||
});
|
||||
|
||||
// `set` reports a failed write rather than throwing, so ignoring
|
||||
// it would drop the preference while the summary claims the
|
||||
// import succeeded.
|
||||
if (!written) {
|
||||
throw new PlaylistBackupError(
|
||||
`Restoring the pinned source for "${pin.matchKey}" failed.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -254,6 +254,31 @@ describe('PlaylistBackupService Xtream hidden categories (issue #1017)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('reports a pin that could not be written', async () => {
|
||||
const collaborators = createRestoreCollaborators();
|
||||
const service = createPlaylistBackupService({
|
||||
...collaborators,
|
||||
vodSourcePinService: {
|
||||
listForPlaylist: jest.fn().mockResolvedValue([]),
|
||||
// `set` reports failure rather than throwing, so ignoring the
|
||||
// result would drop the preference while the summary claims
|
||||
// the import succeeded.
|
||||
set: jest.fn().mockResolvedValue(false),
|
||||
},
|
||||
});
|
||||
|
||||
const manifest = createXtreamManifest(
|
||||
[],
|
||||
[{ matchKey: 'tmdb:603', contentId: 501 }]
|
||||
);
|
||||
|
||||
const summary = await service.importBackup(JSON.stringify(manifest));
|
||||
|
||||
expect(summary).toEqual(
|
||||
expect.objectContaining({ merged: 0, failed: 1 })
|
||||
);
|
||||
});
|
||||
|
||||
it('imports an archive written before pins existed', async () => {
|
||||
const collaborators = createRestoreCollaborators();
|
||||
const setPin = jest.fn().mockResolvedValue(true);
|
||||
|
||||
Reference in new issue
Block a user