102
									
								
								server.ts
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								server.ts
									
									
									
									
									
								
							@@ -2,11 +2,11 @@ import { Hono } from "hono";
 | 
				
			|||||||
import { serve } from "@hono/node-server";
 | 
					import { serve } from "@hono/node-server";
 | 
				
			||||||
import path from "path";
 | 
					import path from "path";
 | 
				
			||||||
import { config, validateConfig } from "./services/config.js";
 | 
					import { config, validateConfig } from "./services/config.js";
 | 
				
			||||||
import {
 | 
					import { 
 | 
				
			||||||
  fetchAllEpisodes,
 | 
					  fetchAllEpisodes, 
 | 
				
			||||||
  fetchEpisodesWithArticles,
 | 
					  fetchEpisodesWithArticles,
 | 
				
			||||||
  getAllFeeds,
 | 
					  getAllFeeds,
 | 
				
			||||||
  getFeedByUrl,
 | 
					  getFeedByUrl
 | 
				
			||||||
} from "./services/database.js";
 | 
					} from "./services/database.js";
 | 
				
			||||||
import { batchProcess, addNewFeedUrl } from "./scripts/fetch_and_generate.js";
 | 
					import { batchProcess, addNewFeedUrl } from "./scripts/fetch_and_generate.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -35,34 +35,30 @@ app.get("/api/feeds", async (c) => {
 | 
				
			|||||||
app.post("/api/feeds", async (c) => {
 | 
					app.post("/api/feeds", async (c) => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const { feedUrl } = await c.req.json<{ feedUrl: string }>();
 | 
					    const { feedUrl } = await c.req.json<{ feedUrl: string }>();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    if (
 | 
					    if (!feedUrl || typeof feedUrl !== "string" || !feedUrl.startsWith('http')) {
 | 
				
			||||||
      !feedUrl ||
 | 
					 | 
				
			||||||
      typeof feedUrl !== "string" ||
 | 
					 | 
				
			||||||
      !feedUrl.startsWith("http")
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
      return c.json({ error: "Valid feed URL is required" }, 400);
 | 
					      return c.json({ error: "Valid feed URL is required" }, 400);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    console.log("➕ Adding new feed URL:", feedUrl);
 | 
					    console.log("➕ Adding new feed URL:", feedUrl);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    // Check if feed already exists
 | 
					    // Check if feed already exists
 | 
				
			||||||
    const existingFeed = await getFeedByUrl(feedUrl);
 | 
					    const existingFeed = await getFeedByUrl(feedUrl);
 | 
				
			||||||
    if (existingFeed) {
 | 
					    if (existingFeed) {
 | 
				
			||||||
      return c.json({
 | 
					      return c.json({ 
 | 
				
			||||||
        result: "EXISTS",
 | 
					        result: "EXISTS", 
 | 
				
			||||||
        message: "Feed URL already exists",
 | 
					        message: "Feed URL already exists",
 | 
				
			||||||
        feed: existingFeed,
 | 
					        feed: existingFeed 
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    // Add new feed
 | 
					    // Add new feed
 | 
				
			||||||
    await addNewFeedUrl(feedUrl);
 | 
					    await addNewFeedUrl(feedUrl);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    return c.json({
 | 
					    return c.json({ 
 | 
				
			||||||
      result: "CREATED",
 | 
					      result: "CREATED", 
 | 
				
			||||||
      message: "Feed URL added successfully",
 | 
					      message: "Feed URL added successfully",
 | 
				
			||||||
      feedUrl,
 | 
					      feedUrl 
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
    console.error("Error adding feed:", error);
 | 
					    console.error("Error adding feed:", error);
 | 
				
			||||||
@@ -93,18 +89,18 @@ app.get("/api/episodes/simple", async (c) => {
 | 
				
			|||||||
app.post("/api/episodes/:id/regenerate", async (c) => {
 | 
					app.post("/api/episodes/:id/regenerate", async (c) => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const id = c.req.param("id");
 | 
					    const id = c.req.param("id");
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    if (!id || id.trim() === "") {
 | 
					    if (!id || id.trim() === "") {
 | 
				
			||||||
      return c.json({ error: "Episode ID is required" }, 400);
 | 
					      return c.json({ error: "Episode ID is required" }, 400);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    console.log("🔄 Regeneration requested for episode ID:", id);
 | 
					    console.log("🔄 Regeneration requested for episode ID:", id);
 | 
				
			||||||
    // TODO: Implement regeneration logic
 | 
					    // TODO: Implement regeneration logic
 | 
				
			||||||
    return c.json({
 | 
					    return c.json({ 
 | 
				
			||||||
      result: "PENDING",
 | 
					      result: "PENDING", 
 | 
				
			||||||
      episodeId: id,
 | 
					      episodeId: id,
 | 
				
			||||||
      status: "pending",
 | 
					      status: "pending",
 | 
				
			||||||
      message: "Regeneration feature will be implemented in a future update",
 | 
					      message: "Regeneration feature will be implemented in a future update"
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
    console.error("Error requesting regeneration:", error);
 | 
					    console.error("Error requesting regeneration:", error);
 | 
				
			||||||
@@ -117,14 +113,14 @@ app.get("/api/stats", async (c) => {
 | 
				
			|||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const feeds = await getAllFeeds();
 | 
					    const feeds = await getAllFeeds();
 | 
				
			||||||
    const episodes = await fetchAllEpisodes();
 | 
					    const episodes = await fetchAllEpisodes();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    const stats = {
 | 
					    const stats = {
 | 
				
			||||||
      totalFeeds: feeds.length,
 | 
					      totalFeeds: feeds.length,
 | 
				
			||||||
      activeFeeds: feeds.filter((f) => f.active).length,
 | 
					      activeFeeds: feeds.filter(f => f.active).length,
 | 
				
			||||||
      totalEpisodes: episodes.length,
 | 
					      totalEpisodes: episodes.length,
 | 
				
			||||||
      lastUpdated: new Date().toISOString(),
 | 
					      lastUpdated: new Date().toISOString()
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    return c.json(stats);
 | 
					    return c.json(stats);
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
    console.error("Error fetching stats:", error);
 | 
					    console.error("Error fetching stats:", error);
 | 
				
			||||||
@@ -135,16 +131,16 @@ app.get("/api/stats", async (c) => {
 | 
				
			|||||||
app.post("/api/batch/trigger", async (c) => {
 | 
					app.post("/api/batch/trigger", async (c) => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    console.log("🚀 Manual batch process triggered via API");
 | 
					    console.log("🚀 Manual batch process triggered via API");
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    // Run batch process in background
 | 
					    // Run batch process in background
 | 
				
			||||||
    runBatchProcess().catch((error) => {
 | 
					    runBatchProcess().catch(error => {
 | 
				
			||||||
      console.error("❌ Manual batch process failed:", error);
 | 
					      console.error("❌ Manual batch process failed:", error);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    return c.json({
 | 
					    return c.json({ 
 | 
				
			||||||
      result: "TRIGGERED",
 | 
					      result: "TRIGGERED",
 | 
				
			||||||
      message: "Batch process started in background",
 | 
					      message: "Batch process started in background",
 | 
				
			||||||
      timestamp: new Date().toISOString(),
 | 
					      timestamp: new Date().toISOString()
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
    console.error("Error triggering batch process:", error);
 | 
					    console.error("Error triggering batch process:", error);
 | 
				
			||||||
@@ -159,7 +155,7 @@ app.get("/assets/*", async (c) => {
 | 
				
			|||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const filePath = path.join(config.paths.frontendBuildDir, c.req.path);
 | 
					    const filePath = path.join(config.paths.frontendBuildDir, c.req.path);
 | 
				
			||||||
    const file = Bun.file(filePath);
 | 
					    const file = Bun.file(filePath);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    if (await file.exists()) {
 | 
					    if (await file.exists()) {
 | 
				
			||||||
      const contentType = filePath.endsWith(".js")
 | 
					      const contentType = filePath.endsWith(".js")
 | 
				
			||||||
        ? "application/javascript"
 | 
					        ? "application/javascript"
 | 
				
			||||||
@@ -179,18 +175,15 @@ app.get("/assets/*", async (c) => {
 | 
				
			|||||||
app.get("/podcast_audio/*", async (c) => {
 | 
					app.get("/podcast_audio/*", async (c) => {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const audioFileName = c.req.path.substring("/podcast_audio/".length);
 | 
					    const audioFileName = c.req.path.substring("/podcast_audio/".length);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    // Basic security check
 | 
					    // Basic security check
 | 
				
			||||||
    if (audioFileName.includes("..") || audioFileName.includes("/")) {
 | 
					    if (audioFileName.includes("..") || audioFileName.includes("/")) {
 | 
				
			||||||
      return c.notFound();
 | 
					      return c.notFound();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    const audioFilePath = path.join(
 | 
					    const audioFilePath = path.join(config.paths.podcastAudioDir, audioFileName);
 | 
				
			||||||
      config.paths.podcastAudioDir,
 | 
					 | 
				
			||||||
      audioFileName,
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    const file = Bun.file(audioFilePath);
 | 
					    const file = Bun.file(audioFilePath);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    if (await file.exists()) {
 | 
					    if (await file.exists()) {
 | 
				
			||||||
      const blob = await file.arrayBuffer();
 | 
					      const blob = await file.arrayBuffer();
 | 
				
			||||||
      return c.body(blob, 200, { "Content-Type": "audio/mpeg" });
 | 
					      return c.body(blob, 200, { "Content-Type": "audio/mpeg" });
 | 
				
			||||||
@@ -206,7 +199,7 @@ app.get("/podcast.xml", async (c) => {
 | 
				
			|||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const filePath = path.join(config.paths.publicDir, "podcast.xml");
 | 
					    const filePath = path.join(config.paths.publicDir, "podcast.xml");
 | 
				
			||||||
    const file = Bun.file(filePath);
 | 
					    const file = Bun.file(filePath);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    if (await file.exists()) {
 | 
					    if (await file.exists()) {
 | 
				
			||||||
      const blob = await file.arrayBuffer();
 | 
					      const blob = await file.arrayBuffer();
 | 
				
			||||||
      return c.body(blob, 200, {
 | 
					      return c.body(blob, 200, {
 | 
				
			||||||
@@ -214,7 +207,7 @@ app.get("/podcast.xml", async (c) => {
 | 
				
			|||||||
        "Cache-Control": "public, max-age=3600", // Cache for 1 hour
 | 
					        "Cache-Control": "public, max-age=3600", // Cache for 1 hour
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    console.warn("podcast.xml not found");
 | 
					    console.warn("podcast.xml not found");
 | 
				
			||||||
    return c.notFound();
 | 
					    return c.notFound();
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
@@ -225,13 +218,10 @@ app.get("/podcast.xml", async (c) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Legacy endpoint - redirect to new one
 | 
					// Legacy endpoint - redirect to new one
 | 
				
			||||||
app.post("/api/add-feed", async (c) => {
 | 
					app.post("/api/add-feed", async (c) => {
 | 
				
			||||||
  return c.json(
 | 
					  return c.json({ 
 | 
				
			||||||
    {
 | 
					    error: "This endpoint is deprecated. Use POST /api/feeds instead.",
 | 
				
			||||||
      error: "This endpoint is deprecated. Use POST /api/feeds instead.",
 | 
					    newEndpoint: "POST /api/feeds"
 | 
				
			||||||
      newEndpoint: "POST /api/feeds",
 | 
					  }, 410);
 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    410,
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Frontend fallback routes
 | 
					// Frontend fallback routes
 | 
				
			||||||
@@ -239,12 +229,12 @@ async function serveIndex(c: any) {
 | 
				
			|||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const indexPath = path.join(config.paths.frontendBuildDir, "index.html");
 | 
					    const indexPath = path.join(config.paths.frontendBuildDir, "index.html");
 | 
				
			||||||
    const file = Bun.file(indexPath);
 | 
					    const file = Bun.file(indexPath);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    if (await file.exists()) {
 | 
					    if (await file.exists()) {
 | 
				
			||||||
      const blob = await file.arrayBuffer();
 | 
					      const blob = await file.arrayBuffer();
 | 
				
			||||||
      return c.body(blob, 200, { "Content-Type": "text/html; charset=utf-8" });
 | 
					      return c.body(blob, 200, { "Content-Type": "text/html; charset=utf-8" });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    console.error(`index.html not found at ${indexPath}`);
 | 
					    console.error(`index.html not found at ${indexPath}`);
 | 
				
			||||||
    return c.text("Frontend not built. Run 'bun run build:frontend'", 404);
 | 
					    return c.text("Frontend not built. Run 'bun run build:frontend'", 404);
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
@@ -275,9 +265,9 @@ function scheduleFirstBatchProcess() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function scheduleSixHourlyBatchProcess() {
 | 
					function scheduleSixHourlyBatchProcess() {
 | 
				
			||||||
  const SIX_HOURS_MS = 6 * 60 * 60 * 1000; // 6 hours in milliseconds
 | 
					  const SIX_HOURS_MS = 6 * 60 * 60 * 1000; // 6 hours in milliseconds
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
  console.log(
 | 
					  console.log(
 | 
				
			||||||
    `🕕 Next batch process scheduled in 6 hours (${new Date(Date.now() + SIX_HOURS_MS).toLocaleString()})`,
 | 
					    `🕕 Next batch process scheduled in 6 hours (${new Date(Date.now() + SIX_HOURS_MS).toLocaleString()})`
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setTimeout(async () => {
 | 
					  setTimeout(async () => {
 | 
				
			||||||
@@ -295,7 +285,7 @@ function scheduleSixHourlyBatchProcess() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
async function runBatchProcess(): Promise<void> {
 | 
					async function runBatchProcess(): Promise<void> {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    Bun.spawn(["bun", "run", "scripts/fetch_and_generate.ts"]);
 | 
					    await batchProcess();
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
    console.error("Batch process failed:", error);
 | 
					    console.error("Batch process failed:", error);
 | 
				
			||||||
    throw error;
 | 
					    throw error;
 | 
				
			||||||
@@ -312,7 +302,7 @@ serve(
 | 
				
			|||||||
    console.log(`🌟 Server is running on http://localhost:${info.port}`);
 | 
					    console.log(`🌟 Server is running on http://localhost:${info.port}`);
 | 
				
			||||||
    console.log(`📡 Using configuration from: ${config.paths.projectRoot}`);
 | 
					    console.log(`📡 Using configuration from: ${config.paths.projectRoot}`);
 | 
				
			||||||
    console.log(`🗄️  Database: ${config.paths.dbPath}`);
 | 
					    console.log(`🗄️  Database: ${config.paths.dbPath}`);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    // Schedule batch processes
 | 
					    // Schedule batch processes
 | 
				
			||||||
    scheduleFirstBatchProcess();
 | 
					    scheduleFirstBatchProcess();
 | 
				
			||||||
    scheduleSixHourlyBatchProcess();
 | 
					    scheduleSixHourlyBatchProcess();
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user