mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-13 22:37:29 +00:00
fix(feeds): enforce freshness window (#52)
This commit is contained in:
parent
90fffb124e
commit
be530ab687
2 changed files with 44 additions and 0 deletions
|
|
@ -15,6 +15,7 @@ export async function discoverFeeds(query: FeedDiscoveryQuery, options: { provid
|
||||||
q: query.q.trim(),
|
q: query.q.trim(),
|
||||||
type: query.type ?? "all",
|
type: query.type ?? "all",
|
||||||
limit: clampLimit(query.limit),
|
limit: clampLimit(query.limit),
|
||||||
|
freshnessDays: normalizeFreshnessDays(query.freshnessDays),
|
||||||
includeUnvalidated: query.includeUnvalidated ?? false,
|
includeUnvalidated: query.includeUnvalidated ?? false,
|
||||||
includeDeadFeeds: query.includeDeadFeeds ?? false
|
includeDeadFeeds: query.includeDeadFeeds ?? false
|
||||||
};
|
};
|
||||||
|
|
@ -54,6 +55,7 @@ export async function discoverFeeds(query: FeedDiscoveryQuery, options: { provid
|
||||||
const validated = await validateCandidates(candidates.slice(0, Math.max((resolvedQuery.limit ?? 25) * 2, 25)), resolvedQuery, config);
|
const validated = await validateCandidates(candidates.slice(0, Math.max((resolvedQuery.limit ?? 25) * 2, 25)), resolvedQuery, config);
|
||||||
const scored = validated
|
const scored = validated
|
||||||
.filter((feed) => !resolvedQuery.type || resolvedQuery.type === "all" || feed.kind === resolvedQuery.type)
|
.filter((feed) => !resolvedQuery.type || resolvedQuery.type === "all" || feed.kind === resolvedQuery.type)
|
||||||
|
.filter((feed) => isWithinFreshnessWindow(feed.lastPublishedAt, resolvedQuery.freshnessDays))
|
||||||
.map((feed) => scoreFeed(feed, resolvedQuery));
|
.map((feed) => scoreFeed(feed, resolvedQuery));
|
||||||
const results = dedupeFeeds(scored).slice(0, resolvedQuery.limit ?? 25);
|
const results = dedupeFeeds(scored).slice(0, resolvedQuery.limit ?? 25);
|
||||||
|
|
||||||
|
|
@ -111,6 +113,23 @@ function clampLimit(limit: number | undefined) {
|
||||||
return Math.min(Math.max(Math.trunc(limit), 1), 100);
|
return Math.min(Math.max(Math.trunc(limit), 1), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeFreshnessDays(freshnessDays: number | undefined) {
|
||||||
|
return typeof freshnessDays === "number" && Number.isFinite(freshnessDays) && freshnessDays > 0
|
||||||
|
? freshnessDays
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWithinFreshnessWindow(lastPublishedAt: string | undefined, freshnessDays: number | undefined) {
|
||||||
|
if (freshnessDays === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!lastPublishedAt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const ageMs = Date.now() - Date.parse(lastPublishedAt);
|
||||||
|
return Number.isFinite(ageMs) && ageMs >= 0 && ageMs <= freshnessDays * 86_400_000;
|
||||||
|
}
|
||||||
|
|
||||||
function safeCanonical(url: string) {
|
function safeCanonical(url: string) {
|
||||||
try {
|
try {
|
||||||
return canonicalizeUrl(url);
|
return canonicalizeUrl(url);
|
||||||
|
|
|
||||||
|
|
@ -100,4 +100,29 @@ describe("discovery orchestration", () => {
|
||||||
expect(response.providerErrors).toEqual([{ provider: "broken-provider", error: "provider unavailable" }]);
|
expect(response.providerErrors).toEqual([{ provider: "broken-provider", error: "provider unavailable" }]);
|
||||||
expect(response.results).toHaveLength(1);
|
expect(response.results).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("enforces the requested freshness window", async () => {
|
||||||
|
const provider: FeedDiscoveryProvider = {
|
||||||
|
id: "freshness-provider",
|
||||||
|
name: "Freshness",
|
||||||
|
enabledByDefault: true,
|
||||||
|
requiresApiKey: false,
|
||||||
|
async search() {
|
||||||
|
return [
|
||||||
|
{ ...baseFeed, feedUrl: "https://example.com/fresh.xml", lastPublishedAt: new Date(Date.now() - 5 * 86_400_000).toISOString() },
|
||||||
|
{ ...baseFeed, feedUrl: "https://example.com/stale.xml", lastPublishedAt: new Date(Date.now() - 60 * 86_400_000).toISOString() },
|
||||||
|
{ ...baseFeed, feedUrl: "https://example.com/unknown.xml", lastPublishedAt: undefined },
|
||||||
|
{ ...baseFeed, feedUrl: "https://example.com/invalid.xml", lastPublishedAt: "not-a-date" },
|
||||||
|
{ ...baseFeed, feedUrl: "https://example.com/future.xml", lastPublishedAt: new Date(Date.now() + 86_400_000).toISOString() }
|
||||||
|
];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await discoverFeeds(
|
||||||
|
{ q: "microsaas", freshnessDays: 30, includeUnvalidated: true },
|
||||||
|
{ providers: [provider] }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.results.map((feed) => feed.feedUrl)).toEqual(["https://example.com/fresh.xml"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue