mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-20 22:03:48 +00:00
refactor: extract url helpers, add EMAIL_DOMAIN support
- Add src/utils/urls.ts with baseUrl, feedRssUrl, feedAtomUrl, feedUrl, feedEmailAddress, feedTopicPattern - Add optional EMAIL_DOMAIN env var so web domain and email domain can differ (e.g. demo.kill-the.news serves feeds, @kill-the.news receives mail) - Replace all inline domain template literals with the new helpers - Remove unused site_url/feed_url fields from FeedConfig - Remove unused feedPath param from fetchFeedData - Extract verifyCallback() to deduplicate verifyAndStoreSubscription / verifyAndDeleteSubscription Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import { ADMIN_COOKIE_MAX_AGE } from "../config/constants";
|
||||
import { logger } from "../lib/logger";
|
||||
import { Layout, clampText } from "./admin/ui";
|
||||
import { listAllFeeds, updateFeedInList } from "./admin/helpers";
|
||||
import { feedRssUrl, feedAtomUrl, feedEmailAddress } from "../utils/urls";
|
||||
import { feedsRouter } from "./admin/feeds";
|
||||
import { emailsRouter } from "./admin/emails";
|
||||
import { dashboardScript } from "../scripts/generated/dashboard";
|
||||
@@ -601,9 +602,9 @@ app.get("/", async (c) => {
|
||||
</thead>
|
||||
<tbody id="feed-table-body">
|
||||
{feedsWithConfig.map((feed) => {
|
||||
const emailAddress = `${feed.id}@${env.DOMAIN}`;
|
||||
const rssUrl = `https://${env.DOMAIN}/rss/${feed.id}`;
|
||||
const atomUrl = `https://${env.DOMAIN}/atom/${feed.id}`;
|
||||
const emailAddress = feedEmailAddress(feed.id, env);
|
||||
const rssUrl = feedRssUrl(feed.id, env);
|
||||
const atomUrl = feedAtomUrl(feed.id, env);
|
||||
const titleDisplay = clampText(feed.title, 160);
|
||||
const titleHover = clampText(feed.title, 1000);
|
||||
const sortTitle = titleHover.toLowerCase();
|
||||
@@ -712,9 +713,9 @@ app.get("/", async (c) => {
|
||||
|
||||
<ul class="feed-list">
|
||||
{feedsWithConfig.map((feed) => {
|
||||
const emailAddress = `${feed.id}@${env.DOMAIN}`;
|
||||
const rssUrl = `https://${env.DOMAIN}/rss/${feed.id}`;
|
||||
const atomUrl = `https://${env.DOMAIN}/atom/${feed.id}`;
|
||||
const emailAddress = feedEmailAddress(feed.id, env);
|
||||
const rssUrl = feedRssUrl(feed.id, env);
|
||||
const atomUrl = feedAtomUrl(feed.id, env);
|
||||
const titleDisplay = clampText(feed.title, 140);
|
||||
const titleHover = clampText(feed.title, 1000);
|
||||
const descDisplay = clampText(feed.description || "", 240);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { logger } from "../../lib/logger";
|
||||
import { Layout, clampText } from "./ui";
|
||||
import { deleteKeysWithConcurrency } from "./helpers";
|
||||
import { feedRssUrl, feedAtomUrl, feedEmailAddress } from "../../utils/urls";
|
||||
import { emailsPageScript } from "../../scripts/generated/emails-page";
|
||||
|
||||
type AppEnv = { Bindings: Env };
|
||||
@@ -91,9 +92,9 @@ emailsRouter.get("/feeds/:feedId/emails", async (c) => {
|
||||
return c.text("Feed not found", 404);
|
||||
}
|
||||
|
||||
const emailAddress = `${feedId}@${env.DOMAIN}`;
|
||||
const rssUrl = `https://${env.DOMAIN}/rss/${feedId}`;
|
||||
const atomUrl = `https://${env.DOMAIN}/atom/${feedId}`;
|
||||
const emailAddress = feedEmailAddress(feedId, env);
|
||||
const rssUrl = feedRssUrl(feedId, env);
|
||||
const atomUrl = feedAtomUrl(feedId, env);
|
||||
|
||||
return c.html(
|
||||
<Layout title={`${feedConfig.title} - Emails`}>
|
||||
@@ -426,7 +427,7 @@ emailsRouter.get("/emails/:emailKey", async (c) => {
|
||||
<CopyField label="From:" value={emailData.from} />
|
||||
<CopyField
|
||||
label="To:"
|
||||
value={`${feedId}@${env.DOMAIN}`}
|
||||
value={feedEmailAddress(feedId, env)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { z } from "zod";
|
||||
import { Env, FeedConfig, FeedMetadata, EmailData } from "../../types";
|
||||
import { generateFeedId } from "../../utils/id-generator";
|
||||
import { waitUntilSafe } from "../../utils/worker";
|
||||
import { feedRssUrl, feedEmailAddress } from "../../utils/urls";
|
||||
import { logger } from "../../lib/logger";
|
||||
import { Layout } from "./ui";
|
||||
import {
|
||||
@@ -192,8 +193,6 @@ feedsRouter.post("/create", async (c) => {
|
||||
title: parsedData.title,
|
||||
description: parsedData.description,
|
||||
language: parsedData.language,
|
||||
site_url: `https://${env.DOMAIN}/rss/${feedId}`,
|
||||
feed_url: `https://${env.DOMAIN}/rss/${feedId}`,
|
||||
allowed_senders: parsedData.allowedSenders,
|
||||
created_at: Date.now(),
|
||||
updated_at: Date.now(),
|
||||
@@ -216,8 +215,8 @@ feedsRouter.post("/create", async (c) => {
|
||||
if (isJson) {
|
||||
return c.json({
|
||||
feedId,
|
||||
email: `${feedId}@${env.DOMAIN}`,
|
||||
feedUrl: feedConfig.feed_url,
|
||||
email: feedEmailAddress(feedId, env),
|
||||
feedUrl: feedRssUrl(feedId, env),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -81,8 +81,6 @@ describe("Atom Feed Route", () => {
|
||||
JSON.stringify({
|
||||
title: "Atom Test Feed",
|
||||
description: "Integration test",
|
||||
site_url: "https://test.getmynews.app/rss/test-feed-atom",
|
||||
feed_url: "https://test.getmynews.app/rss/test-feed-atom",
|
||||
language: "en",
|
||||
created_at: 1700000000000,
|
||||
}),
|
||||
|
||||
+6
-5
@@ -2,6 +2,7 @@ import { Context } from "hono";
|
||||
import { Env } from "../types";
|
||||
import { generateAtomFeed } from "../utils/feed-generator";
|
||||
import { fetchFeedData } from "../utils/feed-fetcher";
|
||||
import { baseUrl, feedAtomUrl } from "../utils/urls";
|
||||
|
||||
export async function handle(c: Context<{ Bindings: Env }>): Promise<Response> {
|
||||
try {
|
||||
@@ -10,23 +11,23 @@ export async function handle(c: Context<{ Bindings: Env }>): Promise<Response> {
|
||||
return new Response("Feed ID is required", { status: 400 });
|
||||
}
|
||||
|
||||
const feedData = await fetchFeedData(feedId, c.env, "atom");
|
||||
const feedData = await fetchFeedData(feedId, c.env);
|
||||
if (!feedData) {
|
||||
return new Response("Feed not found", { status: 404 });
|
||||
}
|
||||
|
||||
const baseUrl = `https://${c.env.DOMAIN}`;
|
||||
const base = baseUrl(c.env);
|
||||
const selfUrl = new URL(c.req.url).origin + `/atom/${feedId}`;
|
||||
const atomXml = generateAtomFeed(
|
||||
feedData.feedConfig,
|
||||
feedData.emails,
|
||||
baseUrl,
|
||||
base,
|
||||
feedId,
|
||||
selfUrl,
|
||||
);
|
||||
const linkHeader = [
|
||||
`<${baseUrl}/hub>; rel="hub"`,
|
||||
`<${baseUrl}/atom/${feedId}>; rel="self"`,
|
||||
`<${base}/hub>; rel="hub"`,
|
||||
`<${feedAtomUrl(feedId, c.env)}>; rel="self"`,
|
||||
].join(", ");
|
||||
|
||||
return new Response(atomXml, {
|
||||
|
||||
+2
-3
@@ -6,6 +6,7 @@ import {
|
||||
} from "../utils/websub";
|
||||
import { waitUntilSafe } from "../utils/worker";
|
||||
import { DEFAULT_LEASE_SECONDS, MAX_LEASE_SECONDS } from "../config/constants";
|
||||
import { feedTopicPattern } from "../utils/urls";
|
||||
|
||||
type AppEnv = { Bindings: Env };
|
||||
|
||||
@@ -60,9 +61,7 @@ hubRouter.post("/", async (c) => {
|
||||
}
|
||||
|
||||
// Validate that topic matches a known RSS or Atom feed on this hub
|
||||
const topicPattern = new RegExp(
|
||||
`^https://${env.DOMAIN.replaceAll(".", "\\.")}/(rss|atom)/([^/]+)$`,
|
||||
);
|
||||
const topicPattern = feedTopicPattern(env);
|
||||
const match = topic.match(topicPattern);
|
||||
if (!match) {
|
||||
return c.text(
|
||||
|
||||
+6
-5
@@ -2,6 +2,7 @@ import { Context } from "hono";
|
||||
import { Env } from "../types";
|
||||
import { generateRssFeed } from "../utils/feed-generator";
|
||||
import { fetchFeedData } from "../utils/feed-fetcher";
|
||||
import { baseUrl, feedRssUrl } from "../utils/urls";
|
||||
|
||||
export async function handle(c: Context<{ Bindings: Env }>): Promise<Response> {
|
||||
try {
|
||||
@@ -10,23 +11,23 @@ export async function handle(c: Context<{ Bindings: Env }>): Promise<Response> {
|
||||
return new Response("Feed ID is required", { status: 400 });
|
||||
}
|
||||
|
||||
const feedData = await fetchFeedData(feedId, c.env, "rss");
|
||||
const feedData = await fetchFeedData(feedId, c.env);
|
||||
if (!feedData) {
|
||||
return new Response("Feed not found", { status: 404 });
|
||||
}
|
||||
|
||||
const baseUrl = `https://${c.env.DOMAIN}`;
|
||||
const base = baseUrl(c.env);
|
||||
const selfUrl = new URL(c.req.url).origin + `/rss/${feedId}`;
|
||||
const rssXml = generateRssFeed(
|
||||
feedData.feedConfig,
|
||||
feedData.emails,
|
||||
baseUrl,
|
||||
base,
|
||||
feedId,
|
||||
selfUrl,
|
||||
);
|
||||
const linkHeader = [
|
||||
`<${baseUrl}/hub>; rel="hub"`,
|
||||
`<${baseUrl}/rss/${feedId}>; rel="self"`,
|
||||
`<${base}/hub>; rel="hub"`,
|
||||
`<${feedRssUrl(feedId, c.env)}>; rel="self"`,
|
||||
].join(", ");
|
||||
|
||||
return new Response(rssXml, {
|
||||
|
||||
Reference in New Issue
Block a user