Update category management and RSS endpoint handling

This commit is contained in:
2025-06-08 21:50:31 +09:00
parent 4aa1b5c56a
commit cd0e4065fc
13 changed files with 1171 additions and 70 deletions

133
server.ts
View File

@ -1,7 +1,7 @@
import path from "path";
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { batchScheduler } from "./services/batch-scheduler.js";
import "./services/batch-scheduler.js";
import { config, validateConfig } from "./services/config.js";
import { closeBrowser } from "./services/content-extractor.js";
@ -120,6 +120,48 @@ app.get("/podcast.xml", async (c) => {
}
});
// Category-specific RSS feeds
app.get("/podcast/category/:category.xml", async (c) => {
try {
const category = decodeURIComponent(c.req.param("category") || "");
if (!category) {
return c.notFound();
}
const { generateCategoryRSS } = await import("./services/podcast.js");
const rssXml = await generateCategoryRSS(category);
return c.body(rssXml, 200, {
"Content-Type": "application/xml; charset=utf-8",
"Cache-Control": "public, max-age=3600", // Cache for 1 hour
});
} catch (error) {
console.error(`Error generating category RSS for "${c.req.param("category")}":`, error);
return c.notFound();
}
});
// Feed-specific RSS feeds
app.get("/podcast/feed/:feedId.xml", async (c) => {
try {
const feedId = c.req.param("feedId");
if (!feedId) {
return c.notFound();
}
const { generateFeedRSS } = await import("./services/podcast.js");
const rssXml = await generateFeedRSS(feedId);
return c.body(rssXml, 200, {
"Content-Type": "application/xml; charset=utf-8",
"Cache-Control": "public, max-age=3600", // Cache for 1 hour
});
} catch (error) {
console.error(`Error generating feed RSS for "${c.req.param("feedId")}":`, error);
return c.notFound();
}
});
app.get("/default-thumbnail.svg", async (c) => {
try {
const filePath = path.join(config.paths.publicDir, "default-thumbnail.svg");
@ -561,6 +603,95 @@ app.post("/api/feed-requests", async (c) => {
}
});
// Episode category API endpoints
app.get("/api/episode-categories", async (c) => {
try {
const { getAllEpisodeCategories } = await import("./services/database.js");
const categories = await getAllEpisodeCategories();
return c.json({ categories });
} catch (error) {
console.error("Error fetching episode categories:", error);
return c.json({ error: "Failed to fetch episode categories" }, 500);
}
});
app.get("/api/episodes/by-category", async (c) => {
try {
const category = c.req.query("category");
const { getEpisodesByCategory } = await import("./services/database.js");
const episodes = await getEpisodesByCategory(category);
return c.json({ episodes });
} catch (error) {
console.error("Error fetching episodes by category:", error);
return c.json({ error: "Failed to fetch episodes by category" }, 500);
}
});
app.get("/api/episodes/grouped-by-category", async (c) => {
try {
const { getEpisodesGroupedByCategory } = await import("./services/database.js");
const groupedEpisodes = await getEpisodesGroupedByCategory();
return c.json({ groupedEpisodes });
} catch (error) {
console.error("Error fetching episodes grouped by category:", error);
return c.json({ error: "Failed to fetch episodes grouped by category" }, 500);
}
});
app.get("/api/episode-category-stats", async (c) => {
try {
const { getEpisodeCategoryStats } = await import("./services/database.js");
const stats = await getEpisodeCategoryStats();
return c.json({ stats });
} catch (error) {
console.error("Error fetching episode category stats:", error);
return c.json({ error: "Failed to fetch episode category stats" }, 500);
}
});
// RSS endpoints information API
app.get("/api/rss-endpoints", async (c) => {
try {
const {
getAllEpisodeCategories,
fetchActiveFeeds
} = await import("./services/database.js");
const [episodeCategories, activeFeeds] = await Promise.all([
getAllEpisodeCategories(),
fetchActiveFeeds()
]);
const protocol = c.req.header("x-forwarded-proto") || "http";
const host = c.req.header("host") || "localhost:3000";
const baseUrl = `${protocol}://${host}`;
const endpoints = {
main: {
title: "全エピソード",
url: `${baseUrl}/podcast.xml`,
description: "すべてのエピソードを含むメインRSSフィード"
},
categories: episodeCategories.map(category => ({
title: `カテゴリ: ${category}`,
url: `${baseUrl}/podcast/category/${encodeURIComponent(category)}.xml`,
description: `${category}」カテゴリのエピソードのみ`
})),
feeds: activeFeeds.map(feed => ({
title: `フィード: ${feed.title || feed.url}`,
url: `${baseUrl}/podcast/feed/${feed.id}.xml`,
description: `${feed.title || feed.url}」からのエピソードのみ`,
feedCategory: feed.category
}))
};
return c.json({ endpoints });
} catch (error) {
console.error("Error fetching RSS endpoints:", error);
return c.json({ error: "Failed to fetch RSS endpoints" }, 500);
}
});
// Episode page with OG metadata - must be before catch-all
app.get("/episode/:episodeId", async (c) => {
const episodeId = c.req.param("episodeId");