From 6236274ce83314c6ec7abb4a492be91e3afb6a73 Mon Sep 17 00:00:00 2001 From: Julien Herr Date: Mon, 25 May 2026 17:25:52 +0200 Subject: [PATCH] refactor(app): derive native-feed base from EmailAddress.siteBaseUrl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delete the `iconBase` local helper (which mishandled display-name form like `Name `) and replace it with `EmailAddress.parse(input.from) ?.siteBaseUrl()` — the domain-layer VO that already handles bare and display-name addresses correctly. Adds TEST C to lock the display-name + relative-href absolutization fix. Co-Authored-By: Claude Sonnet 4.6 --- src/application/email-processor.test.ts | 24 ++++++++++++++++++++++++ src/application/email-processor.ts | 13 +++++-------- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/application/email-processor.test.ts b/src/application/email-processor.test.ts index fec8b5c..1cb8bf9 100644 --- a/src/application/email-processor.test.ts +++ b/src/application/email-processor.test.ts @@ -1047,4 +1047,28 @@ describe("native feed detection on ingest", () => { )) as { nativeFeeds?: Record }; expect(metadata.nativeFeeds).toBeUndefined(); }); + + it("absolutizes a relative feed href using the display-name sender's domain (TEST C)", async () => { + const result = await processEmail( + makeInput({ + from: "Blog Name ", + senders: ["news@blog.example.com"], + content: + 'hi', + }), + env as any, + ); + + expect(result.ok).toBe(true); + + const metadata = (await env.EMAIL_STORAGE.get( + `feed:${VALID_FEED_ID}:metadata`, + "json", + )) as { + nativeFeeds?: Record>; + }; + expect(Object.values(metadata.nativeFeeds!).flat()).toEqual([ + { url: "https://blog.example.com/atom.xml", type: "atom" }, + ]); + }); }); diff --git a/src/application/email-processor.ts b/src/application/email-processor.ts index c3f3ab1..e954415 100644 --- a/src/application/email-processor.ts +++ b/src/application/email-processor.ts @@ -1,4 +1,5 @@ import { MailboxId } from "../domain/value-objects/mailbox-id"; +import { EmailAddress } from "../domain/value-objects/email-address"; import { AttachmentData, EmailMetadata, Env } from "../types"; import { bumpCounters } from "../application/stats"; import { dispatchFeedEvents } from "../application/feed-events"; @@ -20,13 +21,6 @@ import { Feed } from "../domain/feed.aggregate"; import { logger } from "../infrastructure/logger"; import { FEED_MAX_BYTES } from "../config/constants"; -// Best-effort site base for absolutizing a sender's relative feed link. -function iconBase(from: string): string { - const at = from.lastIndexOf("@"); - const domain = at >= 0 ? from.slice(at + 1).trim() : ""; - return domain ? `https://${domain}` : ""; -} - export interface RawAttachment { filename: string; contentType: string; @@ -203,7 +197,10 @@ async function storeEmail( }); const nativeFeedList = detectNativeFeeds( - extractFeedLinks(input.content, iconBase(input.from)), + extractFeedLinks( + input.content, + EmailAddress.parse(input.from)?.siteBaseUrl() ?? "", + ), ); const attachmentBucket = getAttachmentBucket(env);