diff --git a/server.ts b/server.ts index 7216707..99f8ec7 100644 --- a/server.ts +++ b/server.ts @@ -129,15 +129,33 @@ app.get("/podcast.xml", async (c) => { } }); -// Category-specific RSS feeds +// Category-specific RSS feeds - try static file first, then generate dynamically 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"); + // Try to serve static file first + const safeCategory = encodeURIComponent(category); + const staticFilePath = path.join( + config.paths.publicDir, + `podcast_category_${safeCategory}.xml`, + ); + const staticFile = Bun.file(staticFilePath); + + if (await staticFile.exists()) { + const blob = await staticFile.arrayBuffer(); + return c.body(blob, 200, { + "Content-Type": "application/xml; charset=utf-8", + "Cache-Control": "public, max-age=3600", // Cache for 1 hour + }); + } + + // Fallback to dynamic generation + console.log(`📄 Static category RSS not found for "${category}", generating dynamically...`); + const { generateCategoryRSS } = await import("./services/podcast.js"); const rssXml = await generateCategoryRSS(category); return c.body(rssXml, 200, { @@ -146,22 +164,39 @@ app.get("/podcast/category/:category.xml", async (c) => { }); } catch (error) { console.error( - `Error generating category RSS for "${c.req.param("category")}":`, + `Error serving category RSS for "${c.req.param("category")}":`, error, ); return c.notFound(); } }); -// Feed-specific RSS feeds +// Feed-specific RSS feeds - try static file first, then generate dynamically 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"); + // Try to serve static file first + const staticFilePath = path.join( + config.paths.publicDir, + `podcast_feed_${feedId}.xml`, + ); + const staticFile = Bun.file(staticFilePath); + + if (await staticFile.exists()) { + const blob = await staticFile.arrayBuffer(); + return c.body(blob, 200, { + "Content-Type": "application/xml; charset=utf-8", + "Cache-Control": "public, max-age=3600", // Cache for 1 hour + }); + } + + // Fallback to dynamic generation + console.log(`📄 Static feed RSS not found for "${feedId}", generating dynamically...`); + const { generateFeedRSS } = await import("./services/podcast.js"); const rssXml = await generateFeedRSS(feedId); return c.body(rssXml, 200, { @@ -170,7 +205,7 @@ app.get("/podcast/feed/:feedId.xml", async (c) => { }); } catch (error) { console.error( - `Error generating feed RSS for "${c.req.param("feedId")}":`, + `Error serving feed RSS for "${c.req.param("feedId")}":`, error, ); return c.notFound(); diff --git a/services/podcast.ts b/services/podcast.ts index 1ad5b43..73d23c4 100644 --- a/services/podcast.ts +++ b/services/podcast.ts @@ -139,20 +139,118 @@ export async function updatePodcastRSS(): Promise { } } +/** + * Generate all category RSS files as static files + */ +export async function generateAllCategoryRSSFiles(): Promise { + try { + const { getAllEpisodeCategories } = await import("./database.js"); + const categories = await getAllEpisodeCategories(); + + console.log(`🔄 Generating ${categories.length} category RSS files...`); + + for (const category of categories) { + try { + await saveCategoryRSSFile(category); + } catch (error) { + console.error(`❌ Failed to generate RSS for category "${category}":`, error); + } + } + + console.log(`✅ Generated category RSS files for ${categories.length} categories`); + } catch (error) { + console.error("❌ Error generating category RSS files:", error); + throw error; + } +} + +/** + * Generate all feed RSS files as static files + */ +export async function generateAllFeedRSSFiles(): Promise { + try { + const { fetchActiveFeeds } = await import("./database.js"); + const feeds = await fetchActiveFeeds(); + + console.log(`🔄 Generating ${feeds.length} feed RSS files...`); + + for (const feed of feeds) { + try { + await saveFeedRSSFile(feed.id); + } catch (error) { + console.error(`❌ Failed to generate RSS for feed "${feed.id}":`, error); + } + } + + console.log(`✅ Generated feed RSS files for ${feeds.length} feeds`); + } catch (error) { + console.error("❌ Error generating feed RSS files:", error); + throw error; + } +} + +/** + * Save category RSS as static file with URL-safe filename + */ +export async function saveCategoryRSSFile(category: string): Promise { + try { + const rssXml = await generateCategoryRSS(category); + const safeCategory = encodeURIComponent(category); + const outputPath = path.join( + config.paths.publicDir, + `podcast_category_${safeCategory}.xml`, + ); + + // Ensure directory exists + await fs.mkdir(dirname(outputPath), { recursive: true }); + await fs.writeFile(outputPath, rssXml); + + console.log(`📄 Category RSS saved: podcast_category_${safeCategory}.xml`); + } catch (error) { + console.error(`❌ Error saving category RSS for "${category}":`, error); + throw error; + } +} + +/** + * Save feed RSS as static file + */ +export async function saveFeedRSSFile(feedId: string): Promise { + try { + const rssXml = await generateFeedRSS(feedId); + const outputPath = path.join( + config.paths.publicDir, + `podcast_feed_${feedId}.xml`, + ); + + // Ensure directory exists + await fs.mkdir(dirname(outputPath), { recursive: true }); + await fs.writeFile(outputPath, rssXml); + + console.log(`📄 Feed RSS saved: podcast_feed_${feedId}.xml`); + } catch (error) { + console.error(`❌ Error saving feed RSS for "${feedId}":`, error); + throw error; + } +} + /** * Regenerate all static files on startup * This ensures that podcast.xml and other generated files are up-to-date */ export async function regenerateStartupFiles(): Promise { try { - console.log("🔄 Regenerating static files on startup..."); + console.log("🔄 Regenerating all static files on startup..."); // Regenerate main podcast.xml await updatePodcastRSS(); console.log("✅ podcast.xml regenerated successfully"); - // Note: Category and feed-specific RSS files are generated dynamically on request - // This is more efficient and ensures they're always up-to-date with current data + // Generate all category RSS files + await generateAllCategoryRSSFiles(); + + // Generate all feed RSS files + await generateAllFeedRSSFiles(); console.log("✅ All startup files regenerated successfully"); } catch (error) {