feat(feeds): site-scoped RSS add infers account, parser and poll

Adding a feed from a site page now only asks for the URL, purpose
(discover / track / promotion) and an optional note. POST /feed-sources
with siteId infers the site account (passkey holder first), records the
site definition version as parserVersion, defaults the name to
"<site> RSS" and polling to 30 minutes, and returns non-blocking
warnings when the URL host does not match the site. The dialog shows the
redacted URL, fetches once right after saving and reports the item count
or the error.

The generic feed form on the sources page suggests linking a registered
site when the URL host matches (can be undone). name is now optional.

Takes effect after the stack is rebuilt.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-29 14:43:19 +08:00
1 parent 00ebdda7ed
commit 4a92dfd2dc
10 files changed
+512 -17

No files matched your search

+28
View File
@@ -29,6 +29,34 @@ export function useFeeds(f: { siteId?: string; health?: FeedHealth } = {}) {
});
}
/** 单个 Feed;poll 为真时每 2 秒刷新(站点内添加后等首抓结果) */
export function useFeed(id: string | null, poll = false) {
return useQuery({
queryKey: [...feedKeys.all, "one", id] as const,
enabled: !!id,
queryFn: ({ signal }) =>
apiParsed(FeedSource, "/api/v1/feed-sources/:id", { pathParams: { id: id ?? "" }, signal }),
refetchInterval: poll ? 2 * TTL_SECOND : false,
});
}
/** 同站判定的主机键(与服务端 feedDefaultsForSite 一致):去 www.,取最后两级;IP 原样 */
export function feedHostKey(u: string): string | null {
try {
const host = new URL(u).hostname.toLowerCase().replace(/^www\./, "");
if (/^[\d.]+$/.test(host) || host.includes(":")) return host;
return host.split(".").slice(-2).join(".");
} catch {
return null;
}
}
/** 游标窗口里的条目数(最近一次成功抓取的 Feed 条目数);没抓过为 null */
export function feedWindowCount(f: FeedSource): number | null {
const w = f.cursor.window as { lastGuidSet?: unknown[] } | undefined;
return Array.isArray(w?.lastGuidSet) ? w.lastGuidSet.length : null;
}
export function useCreateFeed() {
const qc = useQueryClient();
return useMutation({