Popular on this server; changelog follows Keep a Changelog; 0.3.0
- Add feed lists what other accounts subscribe to, most subscribers
first, and subscribes you by id (GET /api/popular, POST
/api/popular/{id}). Rows never carry a URL. Feeds from an OPML and
anything that looks private (a login, credentials in the URL, a key
such as auth= or token=) are never listed, and the subscribe route
checks the id against the same list.
- CHANGELOG.md follows Keep a Changelog 1.1.0: 0.1.0 (2026-09-09, the
CLI), 0.2.0 (2026-09-10, the web UI), 0.3.0 (2026-09-11, accounts and
sharing). The long-form entries moved unchanged to docs/history.md.
- Cargo.toml is 0.3.0. CLAUDE.md says how to add an entry and cut a
release.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016HdTEWQNrzyFULijigkmMn
This commit is contained in:
@@ -10,8 +10,8 @@ test.beforeEach(async ({ page }) => {
|
||||
test('the page loads and lists the configured feeds', async ({ page }) => {
|
||||
// Regression: a ReferenceError in the script left the shell rendered and the sidebar
|
||||
// empty, with every handler below the error dead. Server-side checks all passed.
|
||||
// Three top-level feeds in the fixture config; the OPML's children are inside a closed folder.
|
||||
await expect(page.locator('.feed')).toHaveCount(4, { timeout: 15_000 });
|
||||
// Four top-level feeds in the fixture config; the OPML's children are inside a closed folder.
|
||||
await expect(page.locator('.feed')).toHaveCount(5, { timeout: 15_000 });
|
||||
await expect(page.getByText('Test Show')).toBeVisible();
|
||||
const errors = [];
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
@@ -544,3 +544,44 @@ test('ipx import subscribes the admin, and ipx export writes the feeds out', asy
|
||||
expect(xml).toContain('http://127.0.0.1:8792/one.xml');
|
||||
expect(xml).toContain('http://127.0.0.1:8792/two.xml');
|
||||
});
|
||||
|
||||
test('Add feed offers what other people here read, but never a private feed', async ({ browser }) => {
|
||||
const { execFileSync } = require('child_process');
|
||||
const setup = require('./global-setup');
|
||||
const env = {
|
||||
...process.env,
|
||||
IPX_CONFIG: `${setup.root}/config/config.toml`,
|
||||
IPX_DATA_DIR: `${setup.root}/data`,
|
||||
};
|
||||
try {
|
||||
execFileSync('./target/debug/ipx', ['user', 'add', 'piper'], { input: 'piperpassword', env });
|
||||
} catch (e) {
|
||||
if (!String(e.stderr || e.stdout).includes('already exists')) throw e;
|
||||
}
|
||||
const ctx = await browser.newContext();
|
||||
const piper = await ctx.newPage();
|
||||
await piper.goto('/login');
|
||||
await piper.locator('#name').fill('piper');
|
||||
await piper.locator('#pw').fill('piperpassword');
|
||||
await piper.locator('button[type=submit]').click();
|
||||
await expect(piper.locator('#feedlist')).toContainText('No feeds.');
|
||||
|
||||
await piper.locator('#addFeed').click();
|
||||
const offered = piper.locator('#popular .childrow');
|
||||
await expect(offered.filter({ hasText: 'Test Show' })).toBeVisible({ timeout: 20_000 });
|
||||
// An OPML's own feeds ride on the OPML, and a key in a URL marks someone's paid feed.
|
||||
await expect(offered.filter({ hasText: /Grouped Show|grouped-show/ })).toHaveCount(0);
|
||||
await expect(offered.filter({ hasText: /Paid Show|paid-show/ })).toHaveCount(0);
|
||||
|
||||
// No URL reaches the page at all, so neither can a key, and the server holds the same line.
|
||||
const listed = await (await piper.request.get('/api/popular')).text();
|
||||
expect(listed).not.toContain('secret123');
|
||||
expect(listed).not.toContain('.xml');
|
||||
expect((await piper.request.post('/api/popular/paid-show')).status()).toBe(400);
|
||||
|
||||
await offered.filter({ hasText: 'Test Show' }).locator('button', { hasText: 'Subscribe' }).click();
|
||||
await expect(piper.locator('#feedlist .feed', { hasText: 'Test Show' })).toBeVisible({ timeout: 20_000 });
|
||||
// Once it is yours, it is no longer offered.
|
||||
expect(await (await piper.request.get('/api/popular')).text()).not.toContain('"test-show"');
|
||||
await ctx.close();
|
||||
});
|
||||
|
||||
5
tests/ui/fixtures/paid.xml
Normal file
5
tests/ui/fixtures/paid.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<rss version="2.0"><channel><title>Paid Show</title><link>http://127.0.0.1:8792/</link>
|
||||
<description>Subscribed with a key in its URL, so it must never be offered to anyone else.</description>
|
||||
<item><title>Paid Ep</title><guid>paid-1</guid><description>x</description></item>
|
||||
</channel></rss>
|
||||
@@ -55,6 +55,11 @@ auto_download = true
|
||||
[feeds.test-subscriptions]
|
||||
url = "http://127.0.0.1:8792/subs.opml"
|
||||
auto_download = false
|
||||
|
||||
# A key in its URL, like a Patreon feed: someone's paid subscription, never offered to others.
|
||||
[feeds.paid-show]
|
||||
url = "http://127.0.0.1:8792/paid.xml?auth=secret123"
|
||||
auto_download = false
|
||||
`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user