refactor(domain): add FeedId, EmailAddress and Domain value objects

Encapsulate the email/domain/feed-id parsing that was scattered as ad-hoc
regexes and split("@") calls into three small immutable value objects under
src/domain/value-objects/. EmailParser.extractFeedId and generateFeedId now
delegate to FeedId; the sender policy, favicon domain extraction and the admin
SenderField parse through EmailAddress/Domain.

Left as-is on purpose: forwardemail's multi-address free-text extraction and the
admin allow/block list normaliser, which operate on mixed email-or-domain input
that the single-address value objects would reject.

Behaviour-preserving; adds unit tests for each value object.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Julien Herr
2026-05-24 00:05:46 +02:00
parent 8f036cf223
commit c65aabe7f4
11 changed files with 198 additions and 35 deletions
+4 -7
View File
@@ -9,6 +9,7 @@ import {
import { FeedRepository } from "../../domain/feed-repository";
import { feedRssUrl, feedAtomUrl, feedEmailAddress } from "../../utils/urls";
import { formatBytes } from "../../utils/format";
import { EmailAddress } from "../../domain/value-objects/email-address";
import { emailsPageScript } from "../../scripts/generated/emails-page";
type AppEnv = { Bindings: Env };
@@ -71,19 +72,15 @@ const CopyField = ({ label, value, display }: CopyFieldProps) => (
</div>
);
function extractSenderEmail(from: string): string {
const match = from.match(/<([^>]+@[^>]+)>/);
return match ? match[1].trim().toLowerCase() : from.trim().toLowerCase();
}
type SenderFieldProps = {
from: string;
feedId: string;
};
const SenderField = ({ from, feedId }: SenderFieldProps) => {
const senderEmail = extractSenderEmail(from);
const senderDomain = senderEmail.split("@")[1] || "";
const parsed = EmailAddress.parse(from);
const senderEmail = parsed?.normalized ?? from.trim().toLowerCase();
const senderDomain = parsed?.domain.value ?? "";
return (
<div class="copyable">