Skip to content

Commit 02674ae

Browse files
authored
Merge pull request #528 from openRin/agent/developer/ce3e3728
FRO-3: fix feed edit lookup by numeric id
2 parents 83ea541 + b4a08e1 commit 02674ae

4 files changed

Lines changed: 103 additions & 12 deletions

File tree

server/src/services/__tests__/clear-feed-cache.test.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ describe('clearFeedCache', () => {
2626
]);
2727
expect(deletedKeys).toEqual([
2828
{ key: 'feed_42', save: false },
29-
{ key: 'feed_about', save: false }
29+
{ key: 'feed_id_42', save: false },
30+
{ key: 'feed_about', save: false },
31+
{ key: 'feed_alias_about', save: false }
3032
]);
3133
});
3234

@@ -44,8 +46,11 @@ describe('clearFeedCache', () => {
4446

4547
expect(deletedKeys).toEqual([
4648
{ key: 'feed_42', save: false },
49+
{ key: 'feed_id_42', save: false },
4750
{ key: 'feed_about', save: false },
48-
{ key: 'feed_about-us', save: false }
51+
{ key: 'feed_alias_about', save: false },
52+
{ key: 'feed_about-us', save: false },
53+
{ key: 'feed_alias_about-us', save: false }
4954
]);
5055
});
5156

@@ -69,7 +74,9 @@ describe('clearFeedCache', () => {
6974
'prefix:feeds_',
7075
'prefix:search_',
7176
'delete:feed_42:false',
77+
'delete:feed_id_42:false',
7278
'delete:feed_about:false',
79+
'delete:feed_alias_about:false',
7380
'prefix:42_previous_feed',
7481
'prefix:42_next_feed',
7582
'save'

server/src/services/__tests__/feed.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,76 @@ describe('FeedService', () => {
166166
expect(data.title).toBe('Test Feed');
167167
});
168168

169+
it('should prefer a numeric feed id over another feed numeric alias', async () => {
170+
const firstRes = await app.request('/', {
171+
method: 'POST',
172+
headers: {
173+
'Authorization': 'Bearer mock_token_1',
174+
'Content-Type': 'application/json',
175+
},
176+
body: JSON.stringify({
177+
title: 'Previous Feed',
178+
alias: '2',
179+
content: 'Previous Content',
180+
listed: true,
181+
draft: false,
182+
tags: [],
183+
}),
184+
}, env);
185+
expect(firstRes.status).toBe(200);
186+
187+
const secondRes = await app.request('/', {
188+
method: 'POST',
189+
headers: {
190+
'Authorization': 'Bearer mock_token_1',
191+
'Content-Type': 'application/json',
192+
},
193+
body: JSON.stringify({
194+
title: 'Target Feed',
195+
content: 'Target Content',
196+
listed: true,
197+
draft: false,
198+
tags: [],
199+
}),
200+
}, env);
201+
expect(secondRes.status).toBe(200);
202+
const secondData = await secondRes.json() as any;
203+
204+
const getRes = await app.request(`/${secondData.insertedId}`, { method: 'GET' }, env);
205+
206+
expect(getRes.status).toBe(200);
207+
const data = await getRes.json() as any;
208+
expect(data.id).toBe(secondData.insertedId);
209+
expect(data.title).toBe('Target Feed');
210+
expect(data.content).toBe('Target Content');
211+
});
212+
213+
it('should return feed by non-numeric alias', async () => {
214+
const createRes = await app.request('/', {
215+
method: 'POST',
216+
headers: {
217+
'Authorization': 'Bearer mock_token_1',
218+
'Content-Type': 'application/json',
219+
},
220+
body: JSON.stringify({
221+
title: 'Alias Feed',
222+
alias: 'custom-slug',
223+
content: 'Alias Content',
224+
listed: true,
225+
draft: false,
226+
tags: [],
227+
}),
228+
}, env);
229+
230+
expect(createRes.status).toBe(200);
231+
232+
const getRes = await app.request('/custom-slug', { method: 'GET' }, env);
233+
234+
expect(getRes.status).toBe(200);
235+
const data = await getRes.json() as any;
236+
expect(data.title).toBe('Alias Feed');
237+
});
238+
169239
it('should return AI summary generation status for a queued feed', async () => {
170240
await serverConfig.set('ai_summary.enabled', 'true', false);
171241
await serverConfig.set('ai_summary.provider', 'worker-ai', false);

server/src/services/clear-feed-cache.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ export async function clearFeedCache(cache: CacheImpl, id: number, alias: string
44
await cache.deletePrefix('feeds_');
55
await cache.deletePrefix('search_');
66

7-
const detailKeys = new Set([`feed_${id}`]);
8-
if (alias) detailKeys.add(`feed_${alias}`);
9-
if (newAlias && newAlias !== alias) detailKeys.add(`feed_${newAlias}`);
7+
const detailKeys = new Set([`feed_${id}`, `feed_id_${id}`]);
8+
if (alias) {
9+
detailKeys.add(`feed_${alias}`);
10+
detailKeys.add(`feed_alias_${alias}`);
11+
}
12+
if (newAlias && newAlias !== alias) {
13+
detailKeys.add(`feed_${newAlias}`);
14+
detailKeys.add(`feed_alias_${newAlias}`);
15+
}
1016

1117
for (const key of detailKeys) {
1218
await cache.delete(key, false);

server/src/services/feed.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ export { clearFeedCache } from "./clear-feed-cache";
1414
let XMLParser: any;
1515
let html2md: any;
1616

17+
function parseFeedId(value: string): number | null {
18+
if (!/^[1-9]\d*$/.test(value)) {
19+
return null;
20+
}
21+
22+
const id = Number(value);
23+
return Number.isSafeInteger(id) ? id : null;
24+
}
25+
1726
async function initWPModules() {
1827
if (!XMLParser) {
1928
const fxp = await import("fast-xml-parser");
@@ -194,11 +203,12 @@ export function FeedService(): Hono<{
194203
const admin = c.get('admin');
195204
const uid = c.get('uid');
196205
const id = c.req.param('id');
197-
const id_num = parseInt(id);
198-
const cacheKey = `feed_${id}`;
206+
const id_num = parseFeedId(id);
207+
const cacheKey = id_num === null ? `feed_alias_${id}` : `feed_id_${id_num}`;
208+
const where = id_num === null ? eq(feeds.alias, id) : eq(feeds.id, id_num);
199209

200210
const feed = await profileAsync(c, 'feed_detail_cache_db', () => cache.getOrSet(cacheKey, () => db.query.feeds.findFirst({
201-
where: or(eq(feeds.id, id_num), eq(feeds.alias, id)),
211+
where,
202212
with: {
203213
hashtags: {
204214
columns: {},
@@ -275,16 +285,14 @@ export function FeedService(): Hono<{
275285
const db = c.get('db');
276286
const cache = c.get('cache');
277287
const id = c.req.param('id');
278-
let id_num: number;
288+
let id_num = parseFeedId(id);
279289

280-
if (isNaN(parseInt(id))) {
290+
if (id_num === null) {
281291
const aliasRecord = await profileAsync(c, 'feed_adjacent_alias_lookup', () => db.select({ id: feeds.id }).from(feeds).where(eq(feeds.alias, id)));
282292
if (aliasRecord.length === 0) {
283293
return c.text("Not found", 404);
284294
}
285295
id_num = aliasRecord[0].id;
286-
} else {
287-
id_num = parseInt(id);
288296
}
289297

290298
const feed = await profileAsync(c, 'feed_adjacent_current', () => db.query.feeds.findFirst({

0 commit comments

Comments
 (0)