Update category management and RSS endpoint handling
This commit is contained in:
133
server.ts
133
server.ts
@ -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");
|
||||
|
Reference in New Issue
Block a user