Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions migrations-pg/0068_idea_feed_links.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- 0068_idea_feed_links.sql (Postgres). See migrations/0068_idea_feed_links.sql for the reasoning.
UPDATE idea_asks
SET url = 'https://rssamplifier.com/' || sub || '/read?p=' || substr(post_id, length('feed:' || sub || ':') + 1)
WHERE url NOT LIKE 'http%'
AND post_id LIKE 'feed:%';
12 changes: 12 additions & 0 deletions migrations/0068_idea_feed_links.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- 0068_idea_feed_links.sql: idea evidence links that pointed at our own origin.
--
-- Podcast feeds in the RSS Amplifier directory (Starter Story above all) carry
-- no link per episode, only a guid such as a bare UUID. The reader fell back to
-- that guid as the url, the ideas board rendered it as an href, and the browser
-- resolved it against outreachgraph.com: a 404 on every Starter Story idea. The
-- reader now sends those to the directory's reader page; this rewrites the rows
-- it already filed. post_id is 'feed:<slug>:<guid>', so the guid is recoverable.
UPDATE idea_asks
SET url = 'https://rssamplifier.com/' || sub || '/read?p=' || substr(post_id, length('feed:' || sub || ':') + 1)
WHERE url NOT LIKE 'http%'
AND post_id LIKE 'feed:%';
35 changes: 34 additions & 1 deletion packages/ideas/src/feeds.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { describe, expect, test } from 'bun:test';
import { worthScore, rivalsFor, specificLabel } from './demand';
import { classifySignal, cleanFeeds, feedPostId, readFeed, DEFAULT_FEEDS, PAYS } from './feeds';
import {
classifySignal,
cleanFeeds,
feedPostId,
postUrl,
readFeed,
DEFAULT_FEEDS,
PAYS,
} from './feeds';

describe('classifySignal', () => {
test('a case study with revenue is a paid signal naming the product', () => {
Expand Down Expand Up @@ -157,4 +165,29 @@ describe('specificLabel', () => {
])
expect(specificLabel(label)).toBe(true);
});

test('a podcast episode with no link opens in the directory reader, not on our origin', async () => {
const got = await readFeed(
{ slug: 'starter-story-2', role: 'signals', name: 'Starter Story' },
async () => ({
items: [
{
guid: '3e12d0da-b5a2-11f0-98c9-e7999e01f92c',
url: null,
title: 'Bro simply rebuilt Skype and now makes $14,000 per month',
},
],
}),
'https://rssamplifier.com',
);
expect(got.posts[0]?.url).toBe(
'https://rssamplifier.com/starter-story-2/read?p=3e12d0da-b5a2-11f0-98c9-e7999e01f92c',
);
});

test('postUrl keeps a real link and falls back to an http guid', () => {
expect(postUrl('https://b.dev/1', 'g1', 'b', 'https://x.com')).toBe('https://b.dev/1');
expect(postUrl(null, 'https://b.dev/2', 'b', 'https://x.com')).toBe('https://b.dev/2');
expect(postUrl('', 'g 1', 'b', 'https://x.com')).toBe('https://x.com/b/read?p=g%201');
});
});
16 changes: 14 additions & 2 deletions packages/ideas/src/feeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ export function feedPostId(slug: string, guid: string): string {
return `feed:${slug}:${guid}`.slice(0, 400);
}

/**
* Where a post can be opened. Podcast feeds (Starter Story) often carry no link,
* only a guid like a bare UUID, which an href resolves against our own origin
* and 404s. Those go to the directory's reader page, which plays the episode.
*/
export function postUrl(url: unknown, guid: string, slug: string, base = FEED_BASE): string {
for (const candidate of [url, guid]) {
if (typeof candidate === 'string' && /^https?:\/\//i.test(candidate)) return candidate;
}
return `${base}/${encodeURIComponent(slug)}/read?p=${encodeURIComponent(guid)}`;
}

/** One feed's recent posts from the RSS Amplifier directory. */
export async function readFeed(
feed: IdeaFeed,
Expand All @@ -109,7 +121,7 @@ export async function readFeed(
freshness?: string;
items?: Array<{
guid?: string;
url?: string;
url?: string | null;
title?: string;
summary?: string;
author?: string;
Expand All @@ -125,7 +137,7 @@ export async function readFeed(
sub: feed.slug,
title: decode(String(item.title ?? '')),
text: decode(String(item.summary ?? '')),
url: String(item.url ?? guid),
url: postUrl(item.url, guid, feed.slug, base),
// A blog's posts are one voice: the feed, not a crowd, so it counts once.
author: String(item.author ?? '').trim() || `feed:${feed.slug}`,
postedAt: item.publishedAt ?? new Date(0).toISOString(),
Expand Down
Loading