feat(episodes): let owners remove episodes from the RSS feed
Adds an "unlisted" flag on episodes rather than reusing the existing hard-delete route, since a hard delete cascades to purchases/earnings (ON DELETE CASCADE) and would wipe a producer's sales history and any unwithdrawn earnings for that episode. Unlisting only affects feed.xml output — the episode, its purchases, and reseller listings all stay intact and it can be relisted at any time. Wires up the missing frontend for it too: the podcast settings page had no episode list or management controls at all before this, despite the backend already exposing full episode CRUD. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -153,6 +153,7 @@ describe('login allowlist', () => {
|
||||
|
||||
describe('podcasts, episodes, feed', () => {
|
||||
let podcastId: string;
|
||||
let episodeId: string;
|
||||
const sha = 'c'.repeat(64);
|
||||
|
||||
it('creates a podcast', async () => {
|
||||
@@ -188,6 +189,7 @@ describe('podcasts, episodes, feed', () => {
|
||||
});
|
||||
expect(res.statusCode).toBe(201);
|
||||
expect(res.json().enclosure_url).toContain(`${sha}.mp4`);
|
||||
episodeId = res.json().id;
|
||||
} finally {
|
||||
vi.unstubAllGlobals();
|
||||
}
|
||||
@@ -229,6 +231,35 @@ describe('podcasts, episodes, feed', () => {
|
||||
});
|
||||
expect(cached.statusCode).toBe(304);
|
||||
});
|
||||
|
||||
it('removing an episode from the feed hides it from feed.xml but keeps it in the owner list', async () => {
|
||||
const unlist = await app.inject({
|
||||
method: 'PUT',
|
||||
url: `/api/podcasts/${podcastId}/episodes/${episodeId}`,
|
||||
headers: { cookie },
|
||||
payload: { unlisted: true },
|
||||
});
|
||||
expect(unlist.statusCode).toBe(200);
|
||||
expect(unlist.json().unlisted).toBe(1);
|
||||
|
||||
const feed = await app.inject({ method: 'GET', url: `/feeds/${podcastId}/feed.xml` });
|
||||
expect(feed.body).not.toContain(`${sha}.mp4`);
|
||||
|
||||
const owned = await app.inject({ method: 'GET', url: `/api/podcasts/${podcastId}`, headers: { cookie } });
|
||||
expect(owned.json().episodes.some((e: { id: string }) => e.id === episodeId)).toBe(true);
|
||||
|
||||
const relist = await app.inject({
|
||||
method: 'PUT',
|
||||
url: `/api/podcasts/${podcastId}/episodes/${episodeId}`,
|
||||
headers: { cookie },
|
||||
payload: { unlisted: false },
|
||||
});
|
||||
expect(relist.statusCode).toBe(200);
|
||||
expect(relist.json().unlisted).toBe(0);
|
||||
|
||||
const feedAgain = await app.inject({ method: 'GET', url: `/feeds/${podcastId}/feed.xml` });
|
||||
expect(feedAgain.body).toContain(`${sha}.mp4`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('streams + mediamtx auth webhook', () => {
|
||||
|
||||
Reference in New Issue
Block a user