mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-21 14:23:48 +00:00
refactor: tighten DDD boundaries on the Feed aggregate
Address five modeling tensions in one pass: - Encapsulation: the Feed aggregate no longer exposes raw config/metadata (a shallow Readonly still leaked mutable arrays). It now offers intention-revealing accessors that return copies, plus toConfigSnapshot/toMetadataSnapshot for the repository and summary() for the global registry. - feeds:list consistency: FeedRepository.save/saveConfig upsert the registry entry from feed.summary(), so services no longer mirror title/description/ expiry by hand (the old add/updateInList footgun is gone). - domain/feed.ts: drop the dead applySenderPolicy, internalise resolveExpiresAt and trimToByteBudget into the aggregate; feed.ts keeps only the shared isExpired predicate used by the read-model routes. - Single edit path: remove editDetails; edit(patch, deps) is the sole config mutation, with a systematic expired guard. Renaming an expired feed now 403s. - FeedId flows through the application and infrastructure signatures; fromTrusted/parse happen once at the edge, .value only at the serialisation boundaries (urls, feed-generator, feed-keys, logs, JSON). 347 tests green, tsc clean, Worker bundle builds. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -209,8 +209,9 @@ apiApp.openapi(
|
||||
async (c) => {
|
||||
const env = c.env;
|
||||
const { feedId } = c.req.valid("param");
|
||||
const id = FeedId.fromTrusted(feedId);
|
||||
const body = c.req.valid("json");
|
||||
const result = await editFeed(env, feedId, {
|
||||
const result = await editFeed(env, id, {
|
||||
title: body.title,
|
||||
description: body.description,
|
||||
language: body.language,
|
||||
@@ -222,9 +223,7 @@ apiApp.openapi(
|
||||
return c.json({ error: "Feed not found" }, 404);
|
||||
if (result.status === "expired")
|
||||
return c.json({ error: "Feed has expired and cannot be modified" }, 409);
|
||||
const metadata = await FeedRepository.from(env).getMetadata(
|
||||
FeedId.fromTrusted(feedId),
|
||||
);
|
||||
const metadata = await FeedRepository.from(env).getMetadata(id);
|
||||
return c.json(
|
||||
toFeed(feedId, result.config, metadata?.emails.length ?? 0, env),
|
||||
200,
|
||||
@@ -249,8 +248,10 @@ apiApp.openapi(
|
||||
async (c) => {
|
||||
const env = c.env;
|
||||
const { feedId } = c.req.valid("param");
|
||||
const removed = await deleteFeedRecord(env, feedId, (p) =>
|
||||
waitUntilSafe(c, p),
|
||||
const removed = await deleteFeedRecord(
|
||||
env,
|
||||
FeedId.fromTrusted(feedId),
|
||||
(p) => waitUntilSafe(c, p),
|
||||
);
|
||||
if (!removed) return c.json({ error: "Feed not found" }, 404);
|
||||
return c.json({ ok: true }, 200);
|
||||
@@ -359,9 +360,7 @@ apiApp.openapi(
|
||||
const { feedId, entryId } = c.req.valid("param");
|
||||
const receivedAt = parseInt(entryId, 10);
|
||||
const feed = await repo.load(FeedId.fromTrusted(feedId));
|
||||
const metaEntry = feed?.metadata.emails.find(
|
||||
(e) => e.receivedAt === receivedAt,
|
||||
);
|
||||
const metaEntry = feed?.emails.find((e) => e.receivedAt === receivedAt);
|
||||
if (!feed || !metaEntry) return c.json({ error: "Email not found" }, 404);
|
||||
|
||||
await repo.deleteEmail(metaEntry.key);
|
||||
|
||||
Reference in New Issue
Block a user