mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 22:26:16 -08:00
fix(server-storage): refuse user state for deleted playlists
PUT /v1/user-state/:namespace/:playlistId answers 404 playlist_missing when the server has no such playlist, so an edit queued on another device (including its conflict replay with If-None-Match: *) cannot recreate the state of a playlist deleted meanwhile. The client drops such an edit and its cache entry instead of retrying; page-hide writes report it as gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
716580cb16
commit
f956758cce
8 files changed
+114
-8
No files matched your search
@@ -205,6 +205,25 @@ describe('server storage routes', () => {
|
||||
})
|
||||
));
|
||||
|
||||
it('refuses user state for a playlist it does not have', () =>
|
||||
withTempStorage((storage) =>
|
||||
withServer(app(storage), async (baseUrl) => {
|
||||
const entry = `${baseUrl}/v1/user-state/xtream-favorites/gone`;
|
||||
const put = await fetch(
|
||||
entry,
|
||||
json({ value: ['live:1'] }, { method: 'PUT' })
|
||||
);
|
||||
expect(put.status).toBe(404);
|
||||
await expect(put.json()).resolves.toMatchObject({
|
||||
code: 'playlist_missing',
|
||||
});
|
||||
// Removing what is not there stays harmless.
|
||||
expect((await fetch(entry, { method: 'DELETE' })).status).toBe(
|
||||
200
|
||||
);
|
||||
})
|
||||
));
|
||||
|
||||
it('refuses a playlist write built on a replaced version', () =>
|
||||
withTempStorage((storage) =>
|
||||
withServer(app(storage), async (baseUrl) => {
|
||||
@@ -235,6 +254,10 @@ describe('server storage routes', () => {
|
||||
it('writes user state only on the tag it was read at', () =>
|
||||
withTempStorage((storage) =>
|
||||
withServer(app(storage), async (baseUrl) => {
|
||||
await fetch(
|
||||
`${baseUrl}/v1/playlists/p1`,
|
||||
json(m3uDocument('p1'), { method: 'PUT' })
|
||||
);
|
||||
const entry = `${baseUrl}/v1/user-state/xtream-favorites/p1`;
|
||||
// Created only while absent.
|
||||
const created = await fetch(
|
||||
|
||||
@@ -25,9 +25,20 @@ export function createUserStateRouter(storage: ServerStorage): Router {
|
||||
});
|
||||
|
||||
const write = (req: Request, res: Response, value: unknown) => {
|
||||
const namespace = requireNamespace(String(req.params['namespace']));
|
||||
const playlistId = String(req.params['playlistId']);
|
||||
// An edit another device queued before the playlist was deleted
|
||||
// must not bring its user state back.
|
||||
if (value !== undefined && !storage.playlists.exists(playlistId)) {
|
||||
throw new StorageRequestError(
|
||||
404,
|
||||
'playlist_missing',
|
||||
'The playlist does not exist.'
|
||||
);
|
||||
}
|
||||
const result = storage.userState.writeIf(
|
||||
requireNamespace(String(req.params['namespace'])),
|
||||
String(req.params['playlistId']),
|
||||
namespace,
|
||||
playlistId,
|
||||
value,
|
||||
expectedTag(req)
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user